/* * PL/Proxy - easy access to partitioned database. * * Copyright (c) 2006-2020 PL/Proxy Authors * * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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. */ /* * Caches I/O info about scalar values. */ #include "plproxy.h" /* * Checks if we can safely use binary. */ static bool usable_binary(Oid oid) { /* * We need to properly track if remote server has: * - same major:minor version * - same server_encoding * - same integer_timestamps * * Currently plproxy does the decision too early, * thus no safe types are left. Disable is totally, * until lazy decision-making is possible. */ if (1) return false; switch (oid) { case BOOLOID: case INT2OID: case INT4OID: case INT8OID: case FLOAT4OID: case FLOAT8OID: case NUMERICOID: case BYTEAOID: return true; /* client_encoding issue */ case TEXTOID: case BPCHAROID: case VARCHAROID: /* integer vs. float issue */ case TIMESTAMPOID: case TIMESTAMPTZOID: case DATEOID: case TIMEOID: /* interval binary fmt changed in 8.1 */ case INTERVALOID: default: return false; } } bool plproxy_composite_valid(ProxyComposite *type) { HeapTuple type_tuple; HeapTuple rel_tuple; Form_pg_type pg_type; Oid oid = type->tupdesc->tdtypeid; bool res; if (!type->alterable) return true; type_tuple = SearchSysCache(TYPEOID, ObjectIdGetDatum(oid), 0, 0, 0); if (!HeapTupleIsValid(type_tuple)) elog(ERROR, "cache lookup failed for type %u", oid); pg_type = (Form_pg_type) GETSTRUCT(type_tuple); rel_tuple = SearchSysCache(RELOID, ObjectIdGetDatum(pg_type->typrelid), 0, 0, 0); if (!HeapTupleIsValid(rel_tuple)) elog(ERROR, "cache lookup failed for type relation %u", pg_type->typrelid); res = plproxy_check_stamp(&type->stamp, rel_tuple); ReleaseSysCache(rel_tuple); ReleaseSysCache(type_tuple); return res; } /* * Collects info about fields of a composite type. * * Based on TupleDescGetAttInMetadata. */ ProxyComposite * plproxy_composite_info(ProxyFunction *func, TupleDesc tupdesc) { int i, natts = tupdesc->natts; ProxyComposite *ret; MemoryContext old_ctx; Form_pg_attribute a; ProxyType *type; const char *name; Oid oid = tupdesc->tdtypeid; old_ctx = MemoryContextSwitchTo(func->ctx); ret = palloc(sizeof(*ret)); ret->type_list = palloc(sizeof(ProxyType *) * natts); ret->name_list = palloc0(sizeof(char *) * natts); ret->tupdesc = BlessTupleDesc(tupdesc); ret->use_binary = 1; ret->alterable = 0; if (oid != RECORDOID) { HeapTuple type_tuple; HeapTuple rel_tuple; Form_pg_type pg_type; type_tuple = SearchSysCache(TYPEOID, ObjectIdGetDatum(oid), 0, 0, 0); if (!HeapTupleIsValid(type_tuple)) elog(ERROR, "cache lookup failed for type %u", oid); pg_type = (Form_pg_type) GETSTRUCT(type_tuple); rel_tuple = SearchSysCache(RELOID, ObjectIdGetDatum(pg_type->typrelid), 0, 0, 0); if (!HeapTupleIsValid(rel_tuple)) elog(ERROR, "cache lookup failed for type relation %u", pg_type->typrelid); plproxy_set_stamp(&ret->stamp, rel_tuple); ReleaseSysCache(rel_tuple); ReleaseSysCache(type_tuple); ret->alterable = 1; if (ret->tupdesc->tdtypeid != oid) elog(ERROR, "lost oid"); } MemoryContextSwitchTo(old_ctx); ret->nfields = 0; for (i = 0; i < natts; i++) { a = TupleDescAttr(tupdesc, i); if (a->attisdropped) { ret->name_list[i] = NULL; ret->type_list[i] = NULL; continue; } ret->nfields++; name = quote_identifier(NameStr(a->attname)); ret->name_list[i] = plproxy_func_strdup(func, name); type = plproxy_find_type_info(func, a->atttypid, 0); ret->type_list[i] = type; if (!type->has_recv) ret->use_binary = 0; } return ret; } void plproxy_free_composite(ProxyComposite *rec) { int i; int natts = rec->tupdesc->natts; for (i = 0; i < natts; i++) { plproxy_free_type(rec->type_list[i]); if (rec->name_list[i]) pfree(rec->name_list[i]); } pfree(rec->type_list); pfree(rec->name_list); FreeTupleDesc(rec->tupdesc); pfree(rec); } void plproxy_free_type(ProxyType *type) { if (type == NULL) return; if (type->name) pfree(type->name); if (type->elem_type_t) plproxy_free_type(type->elem_type_t); /* hopefully I/O functions do not use ->fn_extra */ pfree(type); } /* * Build result tuple from binary or CString values. * * Based on BuildTupleFromCStrings. */ HeapTuple plproxy_recv_composite(ProxyComposite *meta, char **values, int *lengths, int *fmts) { TupleDesc tupdesc = meta->tupdesc; int natts = tupdesc->natts; Datum *dvalues; bool *nulls; int i; HeapTuple tuple; dvalues = (Datum *) palloc(natts * sizeof(Datum)); nulls = (bool *) palloc(natts * sizeof(bool)); /* Call the recv function for each attribute */ for (i = 0; i < natts; i++) { if (TupleDescAttr(tupdesc, i)->attisdropped) { dvalues[i] = (Datum)NULL; nulls[i] = true; continue; } dvalues[i] = plproxy_recv_type(meta->type_list[i], values[i], lengths[i], fmts[i]); nulls[i] = (values[i] == NULL); } /* Form a tuple */ tuple = heap_form_tuple(tupdesc, dvalues, nulls); /* * Release locally palloc'd space. */ for (i = 0; i < natts; i++) { if (nulls[i]) continue; if (meta->type_list[i]->by_value) continue; pfree(DatumGetPointer(dvalues[i])); } pfree(dvalues); pfree(nulls); return tuple; } /* Find info about scalar type */ ProxyType * plproxy_find_type_info(ProxyFunction *func, Oid oid, bool for_send) { ProxyType *type; HeapTuple t_type, t_nsp; Form_pg_type s_type; Form_pg_namespace s_nsp; char namebuf[NAMEDATALEN * 4 + 2 + 1 + 2 + 1]; Oid nsoid; /* fetch pg_type row */ t_type = SearchSysCache(TYPEOID, ObjectIdGetDatum(oid), 0, 0, 0); if (!HeapTupleIsValid(t_type)) plproxy_error(func, "cache lookup failed for type %u", oid); /* typname, typnamespace, PG_CATALOG_NAMESPACE, PG_PUBLIC_NAMESPACE */ s_type = (Form_pg_type) GETSTRUCT(t_type); nsoid = s_type->typnamespace; if (nsoid != PG_CATALOG_NAMESPACE) { t_nsp = SearchSysCache(NAMESPACEOID, ObjectIdGetDatum(nsoid), 0, 0, 0); if (!HeapTupleIsValid(t_nsp)) plproxy_error(func, "cache lookup failed for namespace %u", nsoid); s_nsp = (Form_pg_namespace) GETSTRUCT(t_nsp); snprintf(namebuf, sizeof(namebuf), "%s.%s", quote_identifier(NameStr(s_nsp->nspname)), quote_identifier(NameStr(s_type->typname))); ReleaseSysCache(t_nsp); } else { snprintf(namebuf, sizeof(namebuf), "%s", quote_identifier(NameStr(s_type->typname))); } /* sanity check */ switch (s_type->typtype) { default: plproxy_error(func, "unsupported type code: %s (%u)", namebuf, oid); break; case TYPTYPE_PSEUDO: if (oid != VOIDOID) plproxy_error(func, "unsupported pseudo type: %s (%u)", namebuf, oid); break; case TYPTYPE_BASE: case TYPTYPE_COMPOSITE: case TYPTYPE_DOMAIN: case TYPTYPE_ENUM: case TYPTYPE_RANGE: break; } /* allocate & fill structure */ type = plproxy_func_alloc(func, sizeof(*type)); memset(type, 0, sizeof(*type)); type->type_oid = oid; type->io_param = getTypeIOParam(t_type); type->for_send = for_send; type->by_value = s_type->typbyval; type->name = plproxy_func_strdup(func, namebuf); type->is_array = (s_type->typelem != 0 && s_type->typlen == -1); type->elem_type_oid = s_type->typelem; type->elem_type_t = NULL; type->alignment = s_type->typalign; type->length = s_type->typlen; /* decide what function is needed */ if (for_send) { fmgr_info_cxt(s_type->typoutput, &type->io.out.output_func, func->ctx); if (OidIsValid(s_type->typsend) && usable_binary(oid)) { fmgr_info_cxt(s_type->typsend, &type->io.out.send_func, func->ctx); type->has_send = 1; } } else { fmgr_info_cxt(s_type->typinput, &type->io.in.input_func, func->ctx); if (OidIsValid(s_type->typreceive) && usable_binary(oid)) { fmgr_info_cxt(s_type->typreceive, &type->io.in.recv_func, func->ctx); type->has_recv = 1; } } ReleaseSysCache(t_type); return type; } /* Get cached type info for array elems */ ProxyType *plproxy_get_elem_type(ProxyFunction *func, ProxyType *type, bool for_send) { if (!type->elem_type_t) type->elem_type_t = plproxy_find_type_info(func, type->elem_type_oid, for_send); return type->elem_type_t; } /* Convert a Datum to parameter for libpq */ char * plproxy_send_type(ProxyType *type, Datum val, bool allow_bin, int *len, int *fmt) { bytea *bin; char *res; Assert(type->for_send == 1); if (allow_bin && type->has_send) { bin = SendFunctionCall(&type->io.out.send_func, val); res = VARDATA(bin); *len = VARSIZE(bin) - VARHDRSZ; *fmt = 1; } else { res = OutputFunctionCall(&type->io.out.output_func, val); *len = 0; *fmt = 0; } return res; } /* * Point StringInfo to fixed buffer. * * Supposedly StringInfo wants 0 at the end. * Luckily libpq already provides it so all is fine. * * Although it should not matter to binary I/O functions. */ static void setFixedStringInfo(StringInfo str, void *data, int len) { str->data = data; str->maxlen = len; str->len = len; str->cursor = 0; } /* Convert a libpq result to Datum */ Datum plproxy_recv_type(ProxyType *type, char *val, int len, bool bin) { Datum res; StringInfoData buf; Assert(type->for_send == 0); if (bin) { if (!type->has_recv) elog(ERROR, "PL/Proxy: type %u recv not supported", type->type_oid); /* avoid unnecessary copy */ setFixedStringInfo(&buf, val, len); res = ReceiveFunctionCall(&type->io.in.recv_func, &buf, type->io_param, -1); } else { res = InputFunctionCall(&type->io.in.input_func, val, type->io_param, -1); } return res; }