/* Look up all of the information that SerializeTuple() and DeserializeTuple() * need to perform their jobs quickly. Also, scratchpad space is allocated * for serialization and desrialization of datum values, and for formation/ * deformation of tuples themselves. * * NOTE: This function allocates various data-structures, but it assumes that * the current memory-context is acceptable. So the caller should set * the desired memory-context before calling this function. */ void InitSerTupInfo(TupleDesc tupdesc, SerTupInfo * pSerInfo) { int i, numAttrs; AssertArg(tupdesc != NULL); AssertArg(pSerInfo != NULL); if (s_tupSerMemCtxt == NULL) { /* Create tuple-serialization memory context. */ s_tupSerMemCtxt = AllocSetContextCreate(TopMemoryContext, "TupSerMemCtxt", ALLOCSET_DEFAULT_INITSIZE, /* always have some memory */ ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); } /* Set contents to all 0, just to make things clean and easy. */ memset(pSerInfo, 0, sizeof(SerTupInfo)); /* Store the tuple-descriptor so we can use it later. */ pSerInfo->tupdesc = tupdesc; pSerInfo->chunkCache.len = 0; pSerInfo->chunkCache.items = NULL; pSerInfo->has_record_types = false; /* * If we have some attributes, go ahead and prepare the information for * each attribute in the descriptor. Otherwise, we can return right away. */ numAttrs = tupdesc->natts; if (numAttrs <= 0) return; pSerInfo->myinfo = (SerAttrInfo *) palloc0(numAttrs * sizeof(SerAttrInfo)); pSerInfo->values = (Datum *) palloc(numAttrs * sizeof(Datum)); pSerInfo->nulls = (bool *) palloc(numAttrs * sizeof(bool)); for (i = 0; i < numAttrs; i++) { SerAttrInfo *attrInfo = pSerInfo->myinfo + i; /* * Get attribute's data-type Oid. This lets us shortcut the comm * operations for some attribute-types. */ attrInfo->atttypid = tupdesc->attrs[i]->atttypid; /* * Serialization will be performed at a high level abstraction, * we only care about whether it's toasted or pass-by-value or * a CString, so only track the high level type information. */ { HeapTuple typeTuple; Form_pg_type pt; typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(attrInfo->atttypid)); if (!HeapTupleIsValid(typeTuple)) elog(ERROR, "cache lookup failed for type %u", attrInfo->atttypid); pt = (Form_pg_type) GETSTRUCT(typeTuple); /* Consider any non-basic types as potential containers of record types */ if (pt->typtype != TYPTYPE_BASE) pSerInfo->has_record_types = true; if (!pt->typisdefined) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("type %s is only a shell", format_type_be(attrInfo->atttypid)))); attrInfo->typlen = pt->typlen; attrInfo->typbyval = pt->typbyval; ReleaseSysCache(typeTuple); } } }
/* * Delete a single constraint record. */ void RemoveConstraintById(Oid conId) { Relation conDesc; HeapTuple tup; Form_pg_constraint con; conDesc = heap_open(ConstraintRelationId, RowExclusiveLock); tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(conId)); if (!HeapTupleIsValid(tup)) /* should not happen */ elog(ERROR, "cache lookup failed for constraint %u", conId); con = (Form_pg_constraint) GETSTRUCT(tup); /* * Special processing depending on what the constraint is for. */ if (OidIsValid(con->conrelid)) { Relation rel; /* * If the constraint is for a relation, open and exclusive-lock the * relation it's for. */ rel = heap_open(con->conrelid, AccessExclusiveLock); /* * We need to update the relcheck count if it is a check constraint * being dropped. This update will force backends to rebuild relcache * entries when we commit. */ if (con->contype == CONSTRAINT_CHECK) { Relation pgrel; HeapTuple relTup; Form_pg_class classForm; pgrel = heap_open(RelationRelationId, RowExclusiveLock); relTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(con->conrelid)); if (!HeapTupleIsValid(relTup)) elog(ERROR, "cache lookup failed for relation %u", con->conrelid); classForm = (Form_pg_class) GETSTRUCT(relTup); if (classForm->relchecks == 0) /* should not happen */ elog(ERROR, "relation \"%s\" has relchecks = 0", RelationGetRelationName(rel)); classForm->relchecks--; simple_heap_update(pgrel, &relTup->t_self, relTup); CatalogUpdateIndexes(pgrel, relTup); heap_freetuple(relTup); heap_close(pgrel, RowExclusiveLock); } /* Keep lock on constraint's rel until end of xact */ heap_close(rel, NoLock); } else if (OidIsValid(con->contypid)) { /* * XXX for now, do nothing special when dropping a domain constraint * * Probably there should be some form of locking on the domain type, * but we have no such concept at the moment. */ } else elog(ERROR, "constraint %u is not of a known type", conId); /* Fry the constraint itself */ simple_heap_delete(conDesc, &tup->t_self); /* Clean up */ ReleaseSysCache(tup); heap_close(conDesc, RowExclusiveLock); }
entry = (TSDictionaryCacheEntry *) hash_search(TSDictionaryCacheHash, (void *) &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,
/* * 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. * In fact, they shouldn't call this function at all when they are dealing * with the default locale. That can save quite a bit in hotspots. * 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)); /* Return 0 for "default" collation, just in case caller forgets */ if (collid == DEFAULT_COLLATION_OID) 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 */ #ifdef HAVE_LOCALE_T HeapTuple tp; Form_pg_collation collform; const char *collcollate; const char *collctype; locale_t result; tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid)); if (!HeapTupleIsValid(tp)) elog(ERROR, "cache lookup failed for collation %u", collid); collform = (Form_pg_collation) GETSTRUCT(tp); collcollate = NameStr(collform->collcollate); collctype = NameStr(collform->collctype); if (strcmp(collcollate, collctype) == 0) { /* Normal case where they're the same */ #ifndef WIN32 result = newlocale(LC_COLLATE_MASK | LC_CTYPE_MASK, collcollate, NULL); #else result = _create_locale(LC_ALL, collcollate); #endif if (!result) report_newlocale_failure(collcollate); } else { #ifndef WIN32 /* We need two newlocale() steps */ locale_t loc1; loc1 = newlocale(LC_COLLATE_MASK, collcollate, NULL); if (!loc1) report_newlocale_failure(collcollate); result = newlocale(LC_CTYPE_MASK, collctype, loc1); if (!result) 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 } cache_entry->locale = result; ReleaseSysCache(tp); #else /* not HAVE_LOCALE_T */ /* * For platforms that don't support locale_t, we can't do anything * with non-default collations. */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("nondefault collations are not supported on this platform"))); #endif /* not HAVE_LOCALE_T */ } return cache_entry->locale; }
/* * TupleDescInitEntry * This function initializes a single attribute structure in * a previously allocated tuple descriptor. * * If attributeName is NULL, the attname field is set to an empty string * (this is for cases where we don't know or need a name for the field). * Also, some callers use this function to change the datatype-related fields * in an existing tupdesc; they pass attributeName = NameStr(att->attname) * to indicate that the attname field shouldn't be modified. * * Note that attcollation is set to the default for the specified datatype. * If a nondefault collation is needed, insert it afterwards using * TupleDescInitEntryCollation. */ void TupleDescInitEntry(TupleDesc desc, AttrNumber attributeNumber, const char *attributeName, Oid oidtypeid, int32 typmod, int attdim) { HeapTuple tuple; Form_pg_type typeForm; Form_pg_attribute att; /* * sanity checks */ AssertArg(PointerIsValid(desc)); AssertArg(attributeNumber >= 1); AssertArg(attributeNumber <= desc->natts); /* * initialize the attribute fields */ att = desc->attrs[attributeNumber - 1]; att->attrelid = 0; /* dummy value */ /* * Note: attributeName can be NULL, because the planner doesn't always * fill in valid resname values in targetlists, particularly for resjunk * attributes. Also, do nothing if caller wants to re-use the old attname. */ if (attributeName == NULL) MemSet(NameStr(att->attname), 0, NAMEDATALEN); else if (attributeName != NameStr(att->attname)) namestrcpy(&(att->attname), attributeName); att->attstattarget = -1; att->attcacheoff = -1; att->atttypmod = typmod; att->attnum = attributeNumber; att->attndims = attdim; att->attnotnull = false; att->atthasdef = false; att->attisdropped = false; att->attislocal = true; att->attinhcount = 0; /* attacl, attoptions and attfdwoptions are not present in tupledescs */ tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(oidtypeid)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for type %u", oidtypeid); typeForm = (Form_pg_type) GETSTRUCT(tuple); att->atttypid = oidtypeid; att->attlen = typeForm->typlen; att->attbyval = typeForm->typbyval; att->attalign = typeForm->typalign; att->attstorage = typeForm->typstorage; att->attcollation = typeForm->typcollation; ReleaseSysCache(tuple); }
/* * 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'); 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); }
Datum exec_sql_using(PG_FUNCTION_ARGS) { HeapTuple procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fcinfo->flinfo->fn_oid)); if (!HeapTupleIsValid(procedureTuple)) ereport(ERROR, (errmsg("cache lookup failed for function %u", fcinfo->flinfo->fn_oid))); Oid* types = NULL; char** names = NULL; char* modes = NULL; int nargs = get_func_arg_info(procedureTuple, &types, &names, &modes); Oid resultTypeOid; TupleDesc tupleDesc; TypeFuncClass resultType = get_call_result_type(fcinfo, &resultTypeOid, &tupleDesc); bool returnTypeIsByValue; int16 returnTypeLen; get_typlenbyval(resultTypeOid, &returnTypeLen, &returnTypeIsByValue); if (resultType != TYPEFUNC_SCALAR && resultType != TYPEFUNC_COMPOSITE) ereport(ERROR, ( errmsg("function \"%s\" has indeterminable result type", format_procedure(fcinfo->flinfo->fn_oid)) )); bool returnVoid = resultTypeOid == VOIDOID; ReleaseSysCache(procedureTuple); if (nargs < 2) ereport(ERROR, ( errmsg("function \"%s\" has less than 2 arguments", format_procedure(fcinfo->flinfo->fn_oid)) )); else if (modes != NULL) for (int i = 0; i < nargs; i++) { if (modes[i] != PROARGMODE_IN) ereport(ERROR, ( errmsg("function \"%s\" has non-IN arguments", format_procedure(fcinfo->flinfo->fn_oid)) )); } else if (PG_ARGISNULL(0)) ereport(ERROR, ( errmsg("function \"%s\" called with NULL as first argument", format_procedure(fcinfo->flinfo->fn_oid)) )); char* stmt = NULL; if (types[0] == TEXTOID) stmt = DatumGetCString( DirectFunctionCall1(textout, PG_GETARG_DATUM(0))); else if (types[0] == VARCHAROID) stmt = DatumGetCString( DirectFunctionCall1(varcharout, PG_GETARG_DATUM(0))); else ereport(ERROR, ( errmsg("function \"%s\" does not have a leading VARCHAR/TEXT " "argument", format_procedure(fcinfo->flinfo->fn_oid)) )); char* nulls = NULL; for (int i = 1; i < nargs; i++) if (PG_ARGISNULL(i)) { if (nulls == NULL) { nulls = palloc0(sizeof(char) * (nargs - 1)); memset(nulls, ' ', nargs - 1); } nulls[i - 1] = 'n'; } SPI_connect(); SPIPlanPtr plan = SPI_prepare(stmt, nargs - 1, &types[1]); if (plan == NULL) ereport(ERROR, ( errmsg("function \"%s\" could not obtain execution plan for " "SQL statement", format_procedure(fcinfo->flinfo->fn_oid)) )); int result = SPI_execute_plan(plan, &fcinfo->arg[1], nulls, false, returnVoid ? 0 : 1); Datum returnValue = 0; bool returnNull = false; if (!returnVoid) { if (result != SPI_OK_SELECT && result != SPI_OK_INSERT_RETURNING && result != SPI_OK_DELETE_RETURNING && result == SPI_OK_UPDATE_RETURNING) ereport(ERROR, ( errmsg("function \"%s\" could not obtain result from query", format_procedure(fcinfo->flinfo->fn_oid)) )); else if (SPI_tuptable->tupdesc->natts != 1) ereport(ERROR, ( errmsg("function \"%s\" retrieved more than one column from " "query", format_procedure(fcinfo->flinfo->fn_oid)) )); else if (resultTypeOid != SPI_gettypeid(SPI_tuptable->tupdesc, 1)) ereport(ERROR, ( errmsg("function \"%s\" has different return type OID than " "what query returned", format_procedure(fcinfo->flinfo->fn_oid)) )); /* It is important to copy the value into the upper executor context, * i.e., the memory context that was current when SPI_connect was * called */ returnValue = SPI_getbinval(SPI_copytuple(SPI_tuptable->vals[0]), SPI_tuptable->tupdesc, 1, &returnNull); } SPI_freeplan(plan); if (nulls) pfree(nulls); SPI_finish(); if (result < 0) ereport(ERROR, ( errmsg("function \"%s\" encountered error %d during SQL execution", format_procedure(fcinfo->flinfo->fn_oid), result) )); if (returnVoid) PG_RETURN_VOID(); else if (returnNull) PG_RETURN_NULL(); else return returnValue; }
/* oper() -- search for a binary operator * Given operator name, types of arg1 and arg2, return oper struct. * * IMPORTANT: the returned operator (if any) is only promised to be * coercion-compatible with the input datatypes. Do not use this if * you need an exact- or binary-compatible match; see compatible_oper. * * If no matching operator found, return NULL if noError is true, * raise an error if it is false. pstate and location are used only to report * the error position; pass NULL/-1 if not available. * * NOTE: on success, the returned object is a syscache entry. The caller * must ReleaseSysCache() the entry when done with it. */ Operator oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId, bool noError, int location) { Oid operOid; OprCacheKey key; bool key_ok; FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND; HeapTuple tup = NULL; /* * Try to find the mapping in the lookaside cache. */ key_ok = make_oper_cache_key(pstate, &key, opname, ltypeId, rtypeId, location); if (key_ok) { operOid = find_oper_cache_entry(&key); if (OidIsValid(operOid)) { tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); if (HeapTupleIsValid(tup)) return (Operator) tup; } } /* * First try for an "exact" match. */ operOid = binary_oper_exact(opname, ltypeId, rtypeId); if (!OidIsValid(operOid)) { /* * Otherwise, search for the most suitable candidate. */ FuncCandidateList clist; /* Get binary operators of given name */ clist = OpernameGetCandidates(opname, 'b', false); /* No operators found? Then fail... */ if (clist != NULL) { /* * Unspecified type for one of the arguments? then use the other * (XXX this is probably dead code?) */ Oid inputOids[2]; if (rtypeId == InvalidOid) rtypeId = ltypeId; else if (ltypeId == InvalidOid) ltypeId = rtypeId; inputOids[0] = ltypeId; inputOids[1] = rtypeId; fdresult = oper_select_candidate(2, inputOids, clist, &operOid); } } if (OidIsValid(operOid)) tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); if (HeapTupleIsValid(tup)) { if (key_ok) make_oper_cache_entry(&key, operOid); } else if (!noError) op_error(pstate, opname, 'b', ltypeId, rtypeId, fdresult, location); return (Operator) tup; }
/* right_oper() -- search for a unary right operator (postfix operator) * Given operator name and type of arg, return oper struct. * * IMPORTANT: the returned operator (if any) is only promised to be * coercion-compatible with the input datatype. Do not use this if * you need an exact- or binary-compatible match. * * If no matching operator found, return NULL if noError is true, * raise an error if it is false. pstate and location are used only to report * the error position; pass NULL/-1 if not available. * * NOTE: on success, the returned object is a syscache entry. The caller * must ReleaseSysCache() the entry when done with it. */ Operator right_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location) { Oid operOid; OprCacheKey key; bool key_ok; FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND; HeapTuple tup = NULL; /* * Try to find the mapping in the lookaside cache. */ key_ok = make_oper_cache_key(pstate, &key, op, arg, InvalidOid, location); if (key_ok) { operOid = find_oper_cache_entry(&key); if (OidIsValid(operOid)) { tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); if (HeapTupleIsValid(tup)) return (Operator) tup; } } /* * First try for an "exact" match. */ operOid = OpernameGetOprid(op, arg, InvalidOid); if (!OidIsValid(operOid)) { /* * Otherwise, search for the most suitable candidate. */ FuncCandidateList clist; /* Get postfix operators of given name */ clist = OpernameGetCandidates(op, 'r', false); /* No operators found? Then fail... */ if (clist != NULL) { /* * We must run oper_select_candidate even if only one candidate, * otherwise we may falsely return a non-type-compatible operator. */ fdresult = oper_select_candidate(1, &arg, clist, &operOid); } } if (OidIsValid(operOid)) tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); if (HeapTupleIsValid(tup)) { if (key_ok) make_oper_cache_entry(&key, operOid); } else if (!noError) op_error(pstate, op, 'r', arg, InvalidOid, fdresult, location); return (Operator) tup; }
/* * 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; bool isnull; 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) || heap_attisnull(procTuple, Anum_pg_proc_proargnames)) result = NULL; else { /* Get the data out of the tuple */ proargmodes = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_proargmodes, &isnull); Assert(!isnull); proargnames = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_proargnames, &isnull); Assert(!isnull); /* * 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"); 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"); deconstruct_array(arr, TEXTOID, -1, false, 'i', &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; }
/* * 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; 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); switch (result) { case TYPEFUNC_COMPOSITE: if (resultTupleDesc) *resultTupleDesc = lookup_rowtype_tupdesc_copy(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; }
/* * Validator for an SP-GiST opclass. * * Some of the checks done here cover the whole opfamily, and therefore are * redundant when checking each opclass in a family. But they don't run long * enough to be much of a problem, so we accept the duplication rather than * complicate the amvalidate API. */ bool spgvalidate(Oid opclassoid) { bool result = true; HeapTuple classtup; Form_pg_opclass classform; Oid opfamilyoid; Oid opcintype; char *opclassname; HeapTuple familytup; Form_pg_opfamily familyform; char *opfamilyname; CatCList *proclist, *oprlist; List *grouplist; OpFamilyOpFuncGroup *opclassgroup; int i; ListCell *lc; /* Fetch opclass information */ classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid)); if (!HeapTupleIsValid(classtup)) elog(ERROR, "cache lookup failed for operator class %u", opclassoid); classform = (Form_pg_opclass) GETSTRUCT(classtup); opfamilyoid = classform->opcfamily; opcintype = classform->opcintype; opclassname = NameStr(classform->opcname); /* Fetch opfamily information */ familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfamilyoid)); if (!HeapTupleIsValid(familytup)) elog(ERROR, "cache lookup failed for operator family %u", opfamilyoid); familyform = (Form_pg_opfamily) GETSTRUCT(familytup); opfamilyname = NameStr(familyform->opfname); /* Fetch all operators and support functions of the opfamily */ oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid)); proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid)); /* Check individual support functions */ for (i = 0; i < proclist->n_members; i++) { HeapTuple proctup = &proclist->members[i]->tuple; Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup); bool ok; /* * All SP-GiST support functions should be registered with matching * left/right types */ if (procform->amproclefttype != procform->amprocrighttype) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("spgist opfamily %s contains support procedure %s with cross-type registration", opfamilyname, format_procedure(procform->amproc)))); result = false; } /* Check procedure numbers and function signatures */ switch (procform->amprocnum) { case SPGIST_CONFIG_PROC: case SPGIST_CHOOSE_PROC: case SPGIST_PICKSPLIT_PROC: case SPGIST_INNER_CONSISTENT_PROC: ok = check_amproc_signature(procform->amproc, VOIDOID, true, 2, 2, INTERNALOID, INTERNALOID); break; case SPGIST_LEAF_CONSISTENT_PROC: ok = check_amproc_signature(procform->amproc, BOOLOID, true, 2, 2, INTERNALOID, INTERNALOID); break; default: ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("spgist opfamily %s contains function %s with invalid support number %d", opfamilyname, format_procedure(procform->amproc), procform->amprocnum))); result = false; continue; /* don't want additional message */ } if (!ok) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("spgist opfamily %s contains function %s with wrong signature for support number %d", opfamilyname, format_procedure(procform->amproc), procform->amprocnum))); result = false; } } /* Check individual operators */ for (i = 0; i < oprlist->n_members; i++) { HeapTuple oprtup = &oprlist->members[i]->tuple; Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup); /* TODO: Check that only allowed strategy numbers exist */ if (oprform->amopstrategy < 1 || oprform->amopstrategy > 63) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("spgist opfamily %s contains operator %s with invalid strategy number %d", opfamilyname, format_operator(oprform->amopopr), oprform->amopstrategy))); result = false; } /* spgist doesn't support ORDER BY operators */ if (oprform->amoppurpose != AMOP_SEARCH || OidIsValid(oprform->amopsortfamily)) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("spgist opfamily %s contains invalid ORDER BY specification for operator %s", opfamilyname, format_operator(oprform->amopopr)))); result = false; } /* Check operator signature --- same for all spgist strategies */ if (!check_amop_signature(oprform->amopopr, BOOLOID, oprform->amoplefttype, oprform->amoprighttype)) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("spgist opfamily %s contains operator %s with wrong signature", opfamilyname, format_operator(oprform->amopopr)))); result = false; } } /* Now check for inconsistent groups of operators/functions */ grouplist = identify_opfamily_groups(oprlist, proclist); opclassgroup = NULL; foreach(lc, grouplist) { OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc); /* Remember the group exactly matching the test opclass */ if (thisgroup->lefttype == opcintype && thisgroup->righttype == opcintype) opclassgroup = thisgroup; /* * Complain if there are any datatype pairs with functions but no * operators. This is about the best we can do for now to detect * missing operators. */ if (thisgroup->operatorset == 0) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("spgist opfamily %s is missing operator(s) for types %s and %s", opfamilyname, format_type_be(thisgroup->lefttype), format_type_be(thisgroup->righttype)))); result = false; } /* * Complain if we're missing functions for any datatype, remembering * that SP-GiST doesn't use cross-type support functions. */ if (thisgroup->lefttype != thisgroup->righttype) continue; for (i = 1; i <= SPGISTNProc; i++) { if ((thisgroup->functionset & (((uint64) 1) << i)) != 0) continue; /* got it */ ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("spgist opfamily %s is missing support function %d for type %s", opfamilyname, i, format_type_be(thisgroup->lefttype)))); result = false; } }
/* * Validator for a btree opclass. * * Some of the checks done here cover the whole opfamily, and therefore are * redundant when checking each opclass in a family. But they don't run long * enough to be much of a problem, so we accept the duplication rather than * complicate the amvalidate API. */ bool btvalidate(Oid opclassoid) { bool result = true; HeapTuple classtup; Form_pg_opclass classform; Oid opfamilyoid; Oid opcintype; char *opclassname; HeapTuple familytup; Form_pg_opfamily familyform; char *opfamilyname; CatCList *proclist, *oprlist; List *grouplist; OpFamilyOpFuncGroup *opclassgroup; List *familytypes; int i; ListCell *lc; /* Fetch opclass information */ classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid)); if (!HeapTupleIsValid(classtup)) elog(ERROR, "cache lookup failed for operator class %u", opclassoid); classform = (Form_pg_opclass) GETSTRUCT(classtup); opfamilyoid = classform->opcfamily; opcintype = classform->opcintype; opclassname = NameStr(classform->opcname); /* Fetch opfamily information */ familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfamilyoid)); if (!HeapTupleIsValid(familytup)) elog(ERROR, "cache lookup failed for operator family %u", opfamilyoid); familyform = (Form_pg_opfamily) GETSTRUCT(familytup); opfamilyname = NameStr(familyform->opfname); /* Fetch all operators and support functions of the opfamily */ oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid)); proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid)); /* Check individual support functions */ for (i = 0; i < proclist->n_members; i++) { HeapTuple proctup = &proclist->members[i]->tuple; Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup); bool ok; /* Check procedure numbers and function signatures */ switch (procform->amprocnum) { case BTORDER_PROC: ok = check_amproc_signature(procform->amproc, INT4OID, true, 2, 2, procform->amproclefttype, procform->amprocrighttype); break; case BTSORTSUPPORT_PROC: ok = check_amproc_signature(procform->amproc, VOIDOID, true, 1, 1, INTERNALOID); break; default: ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opfamily %s contains function %s with invalid support number %d", opfamilyname, format_procedure(procform->amproc), procform->amprocnum))); result = false; continue; /* don't want additional message */ } if (!ok) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opfamily %s contains function %s with wrong signature for support number %d", opfamilyname, format_procedure(procform->amproc), procform->amprocnum))); result = false; } } /* Check individual operators */ for (i = 0; i < oprlist->n_members; i++) { HeapTuple oprtup = &oprlist->members[i]->tuple; Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup); /* Check that only allowed strategy numbers exist */ if (oprform->amopstrategy < 1 || oprform->amopstrategy > BTMaxStrategyNumber) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opfamily %s contains operator %s with invalid strategy number %d", opfamilyname, format_operator(oprform->amopopr), oprform->amopstrategy))); result = false; } /* btree doesn't support ORDER BY operators */ if (oprform->amoppurpose != AMOP_SEARCH || OidIsValid(oprform->amopsortfamily)) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opfamily %s contains invalid ORDER BY specification for operator %s", opfamilyname, format_operator(oprform->amopopr)))); result = false; } /* Check operator signature --- same for all btree strategies */ if (!check_amop_signature(oprform->amopopr, BOOLOID, oprform->amoplefttype, oprform->amoprighttype)) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opfamily %s contains operator %s with wrong signature", opfamilyname, format_operator(oprform->amopopr)))); result = false; } } /* Now check for inconsistent groups of operators/functions */ grouplist = identify_opfamily_groups(oprlist, proclist); opclassgroup = NULL; familytypes = NIL; foreach(lc, grouplist) { OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc); /* Remember the group exactly matching the test opclass */ if (thisgroup->lefttype == opcintype && thisgroup->righttype == opcintype) opclassgroup = thisgroup; /* * Identify all distinct data types handled in this opfamily. This * implementation is O(N^2), but there aren't likely to be enough * types in the family for it to matter. */ familytypes = list_append_unique_oid(familytypes, thisgroup->lefttype); familytypes = list_append_unique_oid(familytypes, thisgroup->righttype); /* * Complain if there seems to be an incomplete set of either operators * or support functions for this datatype pair. The only thing that * is considered optional is the sortsupport function. */ if (thisgroup->operatorset != ((1 << BTLessStrategyNumber) | (1 << BTLessEqualStrategyNumber) | (1 << BTEqualStrategyNumber) | (1 << BTGreaterEqualStrategyNumber) | (1 << BTGreaterStrategyNumber))) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opfamily %s is missing operator(s) for types %s and %s", opfamilyname, format_type_be(thisgroup->lefttype), format_type_be(thisgroup->righttype)))); result = false; } if ((thisgroup->functionset & (1 << BTORDER_PROC)) == 0) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("btree opfamily %s is missing support function for types %s and %s", opfamilyname, format_type_be(thisgroup->lefttype), format_type_be(thisgroup->righttype)))); result = false; } }
/* * Write a tuple to the outputstream, in the most efficient format possible. */ static void logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple) { TupleDesc desc; Datum values[MaxTupleAttributeNumber]; bool isnull[MaxTupleAttributeNumber]; int i; uint16 nliveatts = 0; desc = RelationGetDescr(rel); for (i = 0; i < desc->natts; i++) { if (TupleDescAttr(desc, i)->attisdropped) continue; nliveatts++; } pq_sendint(out, nliveatts, 2); /* try to allocate enough memory from the get-go */ enlargeStringInfo(out, tuple->t_len + nliveatts * (1 + 4)); heap_deform_tuple(tuple, desc, values, isnull); /* Write the values */ for (i = 0; i < desc->natts; i++) { HeapTuple typtup; Form_pg_type typclass; Form_pg_attribute att = TupleDescAttr(desc, i); char *outputstr; /* skip dropped columns */ if (att->attisdropped) continue; if (isnull[i]) { pq_sendbyte(out, 'n'); /* null column */ continue; } else if (att->attlen == -1 && VARATT_IS_EXTERNAL_ONDISK(values[i])) { pq_sendbyte(out, 'u'); /* unchanged toast column */ continue; } typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(att->atttypid)); if (!HeapTupleIsValid(typtup)) elog(ERROR, "cache lookup failed for type %u", att->atttypid); typclass = (Form_pg_type) GETSTRUCT(typtup); pq_sendbyte(out, 't'); /* 'text' data follows */ outputstr = OidOutputFunctionCall(typclass->typoutput, values[i]); pq_sendcountedtext(out, outputstr, strlen(outputstr), false); pfree(outputstr); ReleaseSysCache(typtup); } }
/* * CheckMyDatabase -- fetch information from the pg_database entry for our DB */ static void CheckMyDatabase(const char *name, bool am_superuser) { HeapTuple tup; Form_pg_database dbform; char *collate; char *ctype; /* 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) 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 && pg_database_aclcheck(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 && CountDBBackends(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_OVERRIDE); /* If we have no other source of client_encoding, use server encoding */ SetConfigOption("client_encoding", GetDatabaseEncodingName(), PGC_BACKEND, PGC_S_DEFAULT); /* assign locale variables */ collate = NameStr(dbform->datcollate); ctype = NameStr(dbform->datctype); 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."))); /* Make the locale settings visible as GUC variables, too */ SetConfigOption("lc_collate", collate, PGC_INTERNAL, PGC_S_OVERRIDE); SetConfigOption("lc_ctype", ctype, PGC_INTERNAL, PGC_S_OVERRIDE); /* Use the right encoding in translated messages */ #ifdef ENABLE_NLS pg_bind_textdomain_codeset(textdomain(NULL)); #endif ReleaseSysCache(tup); }
/* left_oper() -- search for a unary left operator (prefix operator) * Given operator name and type of arg, return oper struct. * * IMPORTANT: the returned operator (if any) is only promised to be * coercion-compatible with the input datatype. Do not use this if * you need an exact- or binary-compatible match. * * If no matching operator found, return NULL if noError is true, * raise an error if it is false. pstate and location are used only to report * the error position; pass NULL/-1 if not available. * * NOTE: on success, the returned object is a syscache entry. The caller * must ReleaseSysCache() the entry when done with it. */ Operator left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location) { Oid operOid; OprCacheKey key; bool key_ok; FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND; HeapTuple tup = NULL; /* * Try to find the mapping in the lookaside cache. */ key_ok = make_oper_cache_key(pstate, &key, op, InvalidOid, arg, location); if (key_ok) { operOid = find_oper_cache_entry(&key); if (OidIsValid(operOid)) { tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); if (HeapTupleIsValid(tup)) return (Operator) tup; } } /* * First try for an "exact" match. */ operOid = OpernameGetOprid(op, InvalidOid, arg); if (!OidIsValid(operOid)) { /* * Otherwise, search for the most suitable candidate. */ FuncCandidateList clist; /* Get prefix operators of given name */ clist = OpernameGetCandidates(op, 'l', false); /* No operators found? Then fail... */ if (clist != NULL) { /* * The returned list has args in the form (0, oprright). Move the * useful data into args[0] to keep oper_select_candidate simple. * XXX we are assuming here that we may scribble on the list! */ FuncCandidateList clisti; for (clisti = clist; clisti != NULL; clisti = clisti->next) { clisti->args[0] = clisti->args[1]; } /* * We must run oper_select_candidate even if only one candidate, * otherwise we may falsely return a non-type-compatible operator. */ fdresult = oper_select_candidate(1, &arg, clist, &operOid); } } if (OidIsValid(operOid)) tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); if (HeapTupleIsValid(tup)) { if (key_ok) make_oper_cache_entry(&key, operOid); } else if (!noError) op_error(pstate, op, 'l', InvalidOid, arg, fdresult, location); return (Operator) tup; }
/* * Guts of language creation. */ static ObjectAddress create_proc_lang(const char *languageName, bool replace, Oid languageOwner, Oid handlerOid, Oid inlineOid, Oid valOid, bool trusted) { Relation rel; TupleDesc tupDesc; Datum values[Natts_pg_language]; bool nulls[Natts_pg_language]; bool replaces[Natts_pg_language]; NameData langname; HeapTuple oldtup; HeapTuple tup; bool is_update; ObjectAddress myself, referenced; rel = heap_open(LanguageRelationId, RowExclusiveLock); tupDesc = RelationGetDescr(rel); /* Prepare data to be inserted */ memset(values, 0, sizeof(values)); memset(nulls, false, sizeof(nulls)); memset(replaces, true, sizeof(replaces)); namestrcpy(&langname, languageName); values[Anum_pg_language_lanname - 1] = NameGetDatum(&langname); values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(languageOwner); values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true); values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(trusted); values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid); values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid); values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid); nulls[Anum_pg_language_lanacl - 1] = true; /* Check for pre-existing definition */ oldtup = SearchSysCache1(LANGNAME, PointerGetDatum(languageName)); if (HeapTupleIsValid(oldtup)) { /* There is one; okay to replace it? */ if (!replace) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("language \"%s\" already exists", languageName))); if (!pg_language_ownercheck(HeapTupleGetOid(oldtup), languageOwner)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_LANGUAGE, languageName); /* * Do not change existing ownership or permissions. Note * dependency-update code below has to agree with this decision. */ replaces[Anum_pg_language_lanowner - 1] = false; replaces[Anum_pg_language_lanacl - 1] = false; /* Okay, do it... */ tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces); simple_heap_update(rel, &tup->t_self, tup); ReleaseSysCache(oldtup); is_update = true; } else { /* Creating a new language */ tup = heap_form_tuple(tupDesc, values, nulls); simple_heap_insert(rel, tup); is_update = false; } /* Need to update indexes for either the insert or update case */ CatalogUpdateIndexes(rel, tup); /* * Create dependencies for the new language. If we are updating an * existing language, first delete any existing pg_depend entries. * (However, since we are not changing ownership or permissions, the * shared dependencies do *not* need to change, and we leave them alone.) */ myself.classId = LanguageRelationId; myself.objectId = HeapTupleGetOid(tup); myself.objectSubId = 0; if (is_update) deleteDependencyRecordsFor(myself.classId, myself.objectId, true); /* dependency on owner of language */ if (!is_update) recordDependencyOnOwner(myself.classId, myself.objectId, languageOwner); /* dependency on extension */ recordDependencyOnCurrentExtension(&myself, is_update); /* dependency on the PL handler function */ referenced.classId = ProcedureRelationId; referenced.objectId = handlerOid; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); /* dependency on the inline handler function, if any */ if (OidIsValid(inlineOid)) { referenced.classId = ProcedureRelationId; referenced.objectId = inlineOid; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } /* dependency on the validator function, if any */ if (OidIsValid(valOid)) { referenced.classId = ProcedureRelationId; referenced.objectId = valOid; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } /* Post creation hook for new procedural language */ InvokeObjectPostCreateHook(LanguageRelationId, myself.objectId, 0); heap_close(rel, RowExclusiveLock); return myself; }
/* * AggregateCreate */ ObjectAddress AggregateCreate(const char *aggName, Oid aggNamespace, char aggKind, int numArgs, int numDirectArgs, oidvector *parameterTypes, Datum allParameterTypes, Datum parameterModes, Datum parameterNames, List *parameterDefaults, Oid variadicArgType, List *aggtransfnName, List *aggfinalfnName, List *aggmtransfnName, List *aggminvtransfnName, List *aggmfinalfnName, bool finalfnExtraArgs, bool mfinalfnExtraArgs, List *aggsortopName, Oid aggTransType, int32 aggTransSpace, Oid aggmTransType, int32 aggmTransSpace, const char *agginitval, const char *aggminitval) { Relation aggdesc; HeapTuple tup; bool nulls[Natts_pg_aggregate]; Datum values[Natts_pg_aggregate]; Form_pg_proc proc; Oid transfn; Oid finalfn = InvalidOid; /* can be omitted */ Oid mtransfn = InvalidOid; /* can be omitted */ Oid minvtransfn = InvalidOid; /* can be omitted */ Oid mfinalfn = InvalidOid; /* can be omitted */ Oid sortop = InvalidOid; /* can be omitted */ Oid *aggArgTypes = parameterTypes->values; bool hasPolyArg; bool hasInternalArg; bool mtransIsStrict = false; Oid rettype; Oid finaltype; Oid fnArgs[FUNC_MAX_ARGS]; int nargs_transfn; int nargs_finalfn; Oid procOid; TupleDesc tupDesc; int i; ObjectAddress myself, referenced; AclResult aclresult; /* sanity checks (caller should have caught these) */ if (!aggName) elog(ERROR, "no aggregate name supplied"); if (!aggtransfnName) elog(ERROR, "aggregate must have a transition function"); if (numDirectArgs < 0 || numDirectArgs > numArgs) elog(ERROR, "incorrect number of direct args for aggregate"); /* * Aggregates can have at most FUNC_MAX_ARGS-1 args, else the transfn * and/or finalfn will be unrepresentable in pg_proc. We must check now * to protect fixed-size arrays here and possibly in called functions. */ if (numArgs < 0 || numArgs > FUNC_MAX_ARGS - 1) ereport(ERROR, (errcode(ERRCODE_TOO_MANY_ARGUMENTS), errmsg_plural("aggregates cannot have more than %d argument", "aggregates cannot have more than %d arguments", FUNC_MAX_ARGS - 1, FUNC_MAX_ARGS - 1))); /* check for polymorphic and INTERNAL arguments */ hasPolyArg = false; hasInternalArg = false; for (i = 0; i < numArgs; i++) { if (IsPolymorphicType(aggArgTypes[i])) hasPolyArg = true; else if (aggArgTypes[i] == INTERNALOID) hasInternalArg = true; } /* * If transtype is polymorphic, must have polymorphic argument also; else * we will have no way to deduce the actual transtype. */ if (IsPolymorphicType(aggTransType) && !hasPolyArg) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot determine transition data type"), errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument."))); /* * Likewise for moving-aggregate transtype, if any */ if (OidIsValid(aggmTransType) && IsPolymorphicType(aggmTransType) && !hasPolyArg) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot determine transition data type"), errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument."))); /* * An ordered-set aggregate that is VARIADIC must be VARIADIC ANY. In * principle we could support regular variadic types, but it would make * things much more complicated because we'd have to assemble the correct * subsets of arguments into array values. Since no standard aggregates * have use for such a case, we aren't bothering for now. */ if (AGGKIND_IS_ORDERED_SET(aggKind) && OidIsValid(variadicArgType) && variadicArgType != ANYOID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("a variadic ordered-set aggregate must use VARIADIC type ANY"))); /* * If it's a hypothetical-set aggregate, there must be at least as many * direct arguments as aggregated ones, and the last N direct arguments * must match the aggregated ones in type. (We have to check this again * when the aggregate is called, in case ANY is involved, but it makes * sense to reject the aggregate definition now if the declared arg types * don't match up.) It's unconditionally OK if numDirectArgs == numArgs, * indicating that the grammar merged identical VARIADIC entries from both * lists. Otherwise, if the agg is VARIADIC, then we had VARIADIC only on * the aggregated side, which is not OK. Otherwise, insist on the last N * parameter types on each side matching exactly. */ if (aggKind == AGGKIND_HYPOTHETICAL && numDirectArgs < numArgs) { int numAggregatedArgs = numArgs - numDirectArgs; if (OidIsValid(variadicArgType) || numDirectArgs < numAggregatedArgs || memcmp(aggArgTypes + (numDirectArgs - numAggregatedArgs), aggArgTypes + numDirectArgs, numAggregatedArgs * sizeof(Oid)) != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("a hypothetical-set aggregate must have direct arguments matching its aggregated arguments"))); } /* * Find the transfn. For ordinary aggs, it takes the transtype plus all * aggregate arguments. For ordered-set aggs, it takes the transtype plus * all aggregated args, but not direct args. However, we have to treat * specially the case where a trailing VARIADIC item is considered to * cover both direct and aggregated args. */ if (AGGKIND_IS_ORDERED_SET(aggKind)) { if (numDirectArgs < numArgs) nargs_transfn = numArgs - numDirectArgs + 1; else { /* special case with VARIADIC last arg */ Assert(variadicArgType != InvalidOid); nargs_transfn = 2; } fnArgs[0] = aggTransType; memcpy(fnArgs + 1, aggArgTypes + (numArgs - (nargs_transfn - 1)), (nargs_transfn - 1) * sizeof(Oid)); } else { nargs_transfn = numArgs + 1; fnArgs[0] = aggTransType; memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid)); } transfn = lookup_agg_function(aggtransfnName, nargs_transfn, fnArgs, variadicArgType, &rettype); /* * Return type of transfn (possibly after refinement by * enforce_generic_type_consistency, if transtype isn't polymorphic) must * exactly match declared transtype. * * In the non-polymorphic-transtype case, it might be okay to allow a * rettype that's binary-coercible to transtype, but I'm not quite * convinced that it's either safe or useful. When transtype is * polymorphic we *must* demand exact equality. */ if (rettype != aggTransType) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("return type of transition function %s is not %s", NameListToString(aggtransfnName), format_type_be(aggTransType)))); tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn)); if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for function %u", transfn); proc = (Form_pg_proc) GETSTRUCT(tup); /* * If the transfn is strict and the initval is NULL, make sure first input * type and transtype are the same (or at least binary-compatible), so * that it's OK to use the first input value as the initial transValue. */ if (proc->proisstrict && agginitval == NULL) { if (numArgs < 1 || !IsBinaryCoercible(aggArgTypes[0], aggTransType)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type"))); } ReleaseSysCache(tup); /* handle moving-aggregate transfn, if supplied */ if (aggmtransfnName) { /* * The arguments are the same as for the regular transfn, except that * the transition data type might be different. So re-use the fnArgs * values set up above, except for that one. */ Assert(OidIsValid(aggmTransType)); fnArgs[0] = aggmTransType; mtransfn = lookup_agg_function(aggmtransfnName, nargs_transfn, fnArgs, variadicArgType, &rettype); /* As above, return type must exactly match declared mtranstype. */ if (rettype != aggmTransType) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("return type of transition function %s is not %s", NameListToString(aggmtransfnName), format_type_be(aggmTransType)))); tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(mtransfn)); if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for function %u", mtransfn); proc = (Form_pg_proc) GETSTRUCT(tup); /* * If the mtransfn is strict and the minitval is NULL, check first * input type and mtranstype are binary-compatible. */ if (proc->proisstrict && aggminitval == NULL) { if (numArgs < 1 || !IsBinaryCoercible(aggArgTypes[0], aggmTransType)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type"))); } /* Remember if mtransfn is strict; we may need this below */ mtransIsStrict = proc->proisstrict; ReleaseSysCache(tup); } /* handle minvtransfn, if supplied */ if (aggminvtransfnName) { /* * This must have the same number of arguments with the same types as * the forward transition function, so just re-use the fnArgs data. */ Assert(aggmtransfnName); minvtransfn = lookup_agg_function(aggminvtransfnName, nargs_transfn, fnArgs, variadicArgType, &rettype); /* As above, return type must exactly match declared mtranstype. */ if (rettype != aggmTransType) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("return type of inverse transition function %s is not %s", NameListToString(aggminvtransfnName), format_type_be(aggmTransType)))); tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(minvtransfn)); if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for function %u", minvtransfn); proc = (Form_pg_proc) GETSTRUCT(tup); /* * We require the strictness settings of the forward and inverse * transition functions to agree. This saves having to handle * assorted special cases at execution time. */ if (proc->proisstrict != mtransIsStrict) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("strictness of aggregate's forward and inverse transition functions must match"))); ReleaseSysCache(tup); } /* handle finalfn, if supplied */ if (aggfinalfnName) { /* * If finalfnExtraArgs is specified, the transfn takes the transtype * plus all args; otherwise, it just takes the transtype plus any * direct args. (Non-direct args are useless at runtime, and are * actually passed as NULLs, but we may need them in the function * signature to allow resolution of a polymorphic agg's result type.) */ Oid ffnVariadicArgType = variadicArgType; fnArgs[0] = aggTransType; memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid)); if (finalfnExtraArgs) nargs_finalfn = numArgs + 1; else { nargs_finalfn = numDirectArgs + 1; if (numDirectArgs < numArgs) { /* variadic argument doesn't affect finalfn */ ffnVariadicArgType = InvalidOid; } } finalfn = lookup_agg_function(aggfinalfnName, nargs_finalfn, fnArgs, ffnVariadicArgType, &finaltype); /* * When finalfnExtraArgs is specified, the finalfn will certainly be * passed at least one null argument, so complain if it's strict. * Nothing bad would happen at runtime (you'd just get a null result), * but it's surely not what the user wants, so let's complain now. */ if (finalfnExtraArgs && func_strict(finalfn)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("final function with extra arguments must not be declared STRICT"))); } else { /* * If no finalfn, aggregate result type is type of the state value */ finaltype = aggTransType; } Assert(OidIsValid(finaltype)); /* * If finaltype (i.e. aggregate return type) is polymorphic, inputs must * be polymorphic also, else parser will fail to deduce result type. * (Note: given the previous test on transtype and inputs, this cannot * happen, unless someone has snuck a finalfn definition into the catalogs * that itself violates the rule against polymorphic result with no * polymorphic input.) */ if (IsPolymorphicType(finaltype) && !hasPolyArg) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot determine result data type"), errdetail("An aggregate returning a polymorphic type " "must have at least one polymorphic argument."))); /* * Also, the return type can't be INTERNAL unless there's at least one * INTERNAL argument. This is the same type-safety restriction we enforce * for regular functions, but at the level of aggregates. We must test * this explicitly because we allow INTERNAL as the transtype. */ if (finaltype == INTERNALOID && !hasInternalArg) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("unsafe use of pseudo-type \"internal\""), errdetail("A function returning \"internal\" must have at least one \"internal\" argument."))); /* * If a moving-aggregate implementation is supplied, look up its finalfn * if any, and check that the implied aggregate result type matches the * plain implementation. */ if (OidIsValid(aggmTransType)) { /* handle finalfn, if supplied */ if (aggmfinalfnName) { /* * The arguments are figured the same way as for the regular * finalfn, but using aggmTransType and mfinalfnExtraArgs. */ Oid ffnVariadicArgType = variadicArgType; fnArgs[0] = aggmTransType; memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid)); if (mfinalfnExtraArgs) nargs_finalfn = numArgs + 1; else { nargs_finalfn = numDirectArgs + 1; if (numDirectArgs < numArgs) { /* variadic argument doesn't affect finalfn */ ffnVariadicArgType = InvalidOid; } } mfinalfn = lookup_agg_function(aggmfinalfnName, nargs_finalfn, fnArgs, ffnVariadicArgType, &rettype); /* As above, check strictness if mfinalfnExtraArgs is given */ if (mfinalfnExtraArgs && func_strict(mfinalfn)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("final function with extra arguments must not be declared STRICT"))); } else { /* * If no finalfn, aggregate result type is type of the state value */ rettype = aggmTransType; } Assert(OidIsValid(rettype)); if (rettype != finaltype) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("moving-aggregate implementation returns type %s, but plain implementation returns type %s", format_type_be(aggmTransType), format_type_be(aggTransType)))); } /* handle sortop, if supplied */ if (aggsortopName) { if (numArgs != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("sort operator can only be specified for single-argument aggregates"))); sortop = LookupOperName(NULL, aggsortopName, aggArgTypes[0], aggArgTypes[0], false, -1); } /* * permission checks on used types */ for (i = 0; i < numArgs; i++) { aclresult = pg_type_aclcheck(aggArgTypes[i], GetUserId(), ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error_type(aclresult, aggArgTypes[i]); } aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error_type(aclresult, aggTransType); if (OidIsValid(aggmTransType)) { aclresult = pg_type_aclcheck(aggmTransType, GetUserId(), ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error_type(aclresult, aggmTransType); } aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error_type(aclresult, finaltype); /* * Everything looks okay. Try to create the pg_proc entry for the * aggregate. (This could fail if there's already a conflicting entry.) */ myself = ProcedureCreate(aggName, aggNamespace, false, /* no replacement */ false, /* doesn't return a set */ finaltype, /* returnType */ GetUserId(), /* proowner */ INTERNALlanguageId, /* languageObjectId */ InvalidOid, /* no validator */ "aggregate_dummy", /* placeholder proc */ NULL, /* probin */ true, /* isAgg */ false, /* isWindowFunc */ false, /* security invoker (currently not * definable for agg) */ false, /* isLeakProof */ false, /* isStrict (not needed for agg) */ PROVOLATILE_IMMUTABLE, /* volatility (not * needed for agg) */ parameterTypes, /* paramTypes */ allParameterTypes, /* allParamTypes */ parameterModes, /* parameterModes */ parameterNames, /* parameterNames */ parameterDefaults, /* parameterDefaults */ PointerGetDatum(NULL), /* proconfig */ 1, /* procost */ 0); /* prorows */ procOid = myself.objectId; /* * Okay to create the pg_aggregate entry. */ /* initialize nulls and values */ for (i = 0; i < Natts_pg_aggregate; i++) { nulls[i] = false; values[i] = (Datum) NULL; } values[Anum_pg_aggregate_aggfnoid - 1] = ObjectIdGetDatum(procOid); values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind); values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs); values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn); values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn); values[Anum_pg_aggregate_aggmtransfn - 1] = ObjectIdGetDatum(mtransfn); values[Anum_pg_aggregate_aggminvtransfn - 1] = ObjectIdGetDatum(minvtransfn); values[Anum_pg_aggregate_aggmfinalfn - 1] = ObjectIdGetDatum(mfinalfn); values[Anum_pg_aggregate_aggfinalextra - 1] = BoolGetDatum(finalfnExtraArgs); values[Anum_pg_aggregate_aggmfinalextra - 1] = BoolGetDatum(mfinalfnExtraArgs); values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop); values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType); values[Anum_pg_aggregate_aggtransspace - 1] = Int32GetDatum(aggTransSpace); values[Anum_pg_aggregate_aggmtranstype - 1] = ObjectIdGetDatum(aggmTransType); values[Anum_pg_aggregate_aggmtransspace - 1] = Int32GetDatum(aggmTransSpace); if (agginitval) values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval); else nulls[Anum_pg_aggregate_agginitval - 1] = true; if (aggminitval) values[Anum_pg_aggregate_aggminitval - 1] = CStringGetTextDatum(aggminitval); else nulls[Anum_pg_aggregate_aggminitval - 1] = true; aggdesc = heap_open(AggregateRelationId, RowExclusiveLock); tupDesc = aggdesc->rd_att; tup = heap_form_tuple(tupDesc, values, nulls); simple_heap_insert(aggdesc, tup); CatalogUpdateIndexes(aggdesc, tup); heap_close(aggdesc, RowExclusiveLock); /* * Create dependencies for the aggregate (above and beyond those already * made by ProcedureCreate). Note: we don't need an explicit dependency * on aggTransType since we depend on it indirectly through transfn. * Likewise for aggmTransType if any. */ /* Depends on transition function */ referenced.classId = ProcedureRelationId; referenced.objectId = transfn; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); /* Depends on final function, if any */ if (OidIsValid(finalfn)) { referenced.classId = ProcedureRelationId; referenced.objectId = finalfn; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } /* Depends on forward transition function, if any */ if (OidIsValid(mtransfn)) { referenced.classId = ProcedureRelationId; referenced.objectId = mtransfn; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } /* Depends on inverse transition function, if any */ if (OidIsValid(minvtransfn)) { referenced.classId = ProcedureRelationId; referenced.objectId = minvtransfn; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } /* Depends on final function, if any */ if (OidIsValid(mfinalfn)) { referenced.classId = ProcedureRelationId; referenced.objectId = mfinalfn; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } /* Depends on sort operator, if any */ if (OidIsValid(sortop)) { referenced.classId = OperatorRelationId; referenced.objectId = sortop; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } return myself; }
/* * Fetch the subscription from the syscache. */ Subscription * GetSubscription(Oid subid, bool missing_ok) { HeapTuple tup; Subscription *sub; Form_pg_subscription subform; Datum datum; bool isnull; tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid)); if (!HeapTupleIsValid(tup)) { if (missing_ok) return NULL; elog(ERROR, "cache lookup failed for subscription %u", subid); } subform = (Form_pg_subscription) GETSTRUCT(tup); sub = (Subscription *) palloc(sizeof(Subscription)); sub->oid = subid; sub->dbid = subform->subdbid; sub->name = pstrdup(NameStr(subform->subname)); sub->owner = subform->subowner; sub->enabled = subform->subenabled; /* Get conninfo */ datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subconninfo, &isnull); Assert(!isnull); sub->conninfo = TextDatumGetCString(datum); /* Get slotname */ datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subslotname, &isnull); if (!isnull) sub->slotname = pstrdup(NameStr(*DatumGetName(datum))); else sub->slotname = NULL; /* Get synccommit */ datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subsynccommit, &isnull); Assert(!isnull); sub->synccommit = TextDatumGetCString(datum); /* Get publications */ datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subpublications, &isnull); Assert(!isnull); sub->publications = textarray_to_stringlist(DatumGetArrayTypeP(datum)); ReleaseSysCache(tup); return sub; }
/* * Copied from src/backend/commands/indexcmds.c, not exported. * Resolve possibly-defaulted operator class specification */ Oid GetIndexOpClass(List *opclass, Oid attrType, char *accessMethodName, Oid accessMethodId) { char *schemaname; char *opcname; HeapTuple tuple; Oid opClassId, opInputType; /* * Release 7.0 removed network_ops, timespan_ops, and datetime_ops, so we * ignore those opclass names so the default *_ops is used. This can be * removed in some later release. bjm 2000/02/07 * * Release 7.1 removes lztext_ops, so suppress that too for a while. tgl * 2000/07/30 * * Release 7.2 renames timestamp_ops to timestamptz_ops, so suppress that * too for awhile. I'm starting to think we need a better approach. tgl * 2000/10/01 * * Release 8.0 removes bigbox_ops (which was dead code for a long while * anyway). tgl 2003/11/11 */ if (list_length(opclass) == 1) { char *claname = strVal(linitial(opclass)); if (strcmp(claname, "network_ops") == 0 || strcmp(claname, "timespan_ops") == 0 || strcmp(claname, "datetime_ops") == 0 || strcmp(claname, "lztext_ops") == 0 || strcmp(claname, "timestamp_ops") == 0 || strcmp(claname, "bigbox_ops") == 0) opclass = NIL; } if (opclass == NIL) { /* no operator class specified, so find the default */ opClassId = GetDefaultOpClass(attrType, accessMethodId); if (!OidIsValid(opClassId)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("data type %s has no default operator class for access method \"%s\"", format_type_be(attrType), accessMethodName), errhint("You must specify an operator class for the index or define a default operator class for the data type."))); return opClassId; } /* * Specific opclass name given, so look up the opclass. */ /* deconstruct the name list */ DeconstructQualifiedName(opclass, &schemaname, &opcname); if (schemaname) { /* Look in specific schema only */ Oid namespaceId; #if PG_VERSION_NUM >= 90300 namespaceId = LookupExplicitNamespace(schemaname, false); #else namespaceId = LookupExplicitNamespace(schemaname); #endif tuple = SearchSysCache3(CLAAMNAMENSP, ObjectIdGetDatum(accessMethodId), PointerGetDatum(opcname), ObjectIdGetDatum(namespaceId)); } else { /* Unqualified opclass name, so search the search path */ opClassId = OpclassnameGetOpcid(accessMethodId, opcname); if (!OidIsValid(opClassId)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("operator class \"%s\" does not exist for access method \"%s\"", opcname, accessMethodName))); tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opClassId)); } if (!HeapTupleIsValid(tuple)) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("operator class \"%s\" does not exist for access method \"%s\"", NameListToString(opclass), accessMethodName))); } /* * Verify that the index operator class accepts this datatype. Note we * will accept binary compatibility. */ opClassId = HeapTupleGetOid(tuple); opInputType = ((Form_pg_opclass) GETSTRUCT(tuple))->opcintype; if (!IsBinaryCoercible(attrType, opInputType)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("operator class \"%s\" does not accept data type %s", NameListToString(opclass), format_type_be(attrType)))); ReleaseSysCache(tuple); return opClassId; }
/* * Validator for a bloom opclass. */ bool blvalidate(Oid opclassoid) { bool result = true; HeapTuple classtup; Form_pg_opclass classform; Oid opfamilyoid; Oid opcintype; Oid opckeytype; char *opclassname; HeapTuple familytup; Form_pg_opfamily familyform; char *opfamilyname; CatCList *proclist, *oprlist; List *grouplist; OpFamilyOpFuncGroup *opclassgroup; int i; ListCell *lc; /* Fetch opclass information */ classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid)); if (!HeapTupleIsValid(classtup)) elog(ERROR, "cache lookup failed for operator class %u", opclassoid); classform = (Form_pg_opclass) GETSTRUCT(classtup); opfamilyoid = classform->opcfamily; opcintype = classform->opcintype; opckeytype = classform->opckeytype; if (!OidIsValid(opckeytype)) opckeytype = opcintype; opclassname = NameStr(classform->opcname); /* Fetch opfamily information */ familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfamilyoid)); if (!HeapTupleIsValid(familytup)) elog(ERROR, "cache lookup failed for operator family %u", opfamilyoid); familyform = (Form_pg_opfamily) GETSTRUCT(familytup); opfamilyname = NameStr(familyform->opfname); /* Fetch all operators and support functions of the opfamily */ oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid)); proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid)); /* Check individual support functions */ for (i = 0; i < proclist->n_members; i++) { HeapTuple proctup = &proclist->members[i]->tuple; Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup); bool ok; /* * All bloom support functions should be registered with matching * left/right types */ if (procform->amproclefttype != procform->amprocrighttype) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("bloom opfamily %s contains support procedure %s with cross-type registration", opfamilyname, format_procedure(procform->amproc)))); result = false; } /* * We can't check signatures except within the specific opclass, since * we need to know the associated opckeytype in many cases. */ if (procform->amproclefttype != opcintype) continue; /* Check procedure numbers and function signatures */ switch (procform->amprocnum) { case BLOOM_HASH_PROC: ok = check_amproc_signature(procform->amproc, INT4OID, false, 1, 1, opckeytype); break; default: ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("bloom opfamily %s contains function %s with invalid support number %d", opfamilyname, format_procedure(procform->amproc), procform->amprocnum))); result = false; continue; /* don't want additional message */ } if (!ok) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("gist opfamily %s contains function %s with wrong signature for support number %d", opfamilyname, format_procedure(procform->amproc), procform->amprocnum))); result = false; } } /* Check individual operators */ for (i = 0; i < oprlist->n_members; i++) { HeapTuple oprtup = &oprlist->members[i]->tuple; Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup); /* Check it's allowed strategy for bloom */ if (oprform->amopstrategy < 1 || oprform->amopstrategy > BLOOM_NSTRATEGIES) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("bloom opfamily %s contains operator %s with invalid strategy number %d", opfamilyname, format_operator(oprform->amopopr), oprform->amopstrategy))); result = false; } /* bloom doesn't support ORDER BY operators */ if (oprform->amoppurpose != AMOP_SEARCH || OidIsValid(oprform->amopsortfamily)) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("bloom opfamily %s contains invalid ORDER BY specification for operator %s", opfamilyname, format_operator(oprform->amopopr)))); result = false; } /* Check operator signature --- same for all bloom strategies */ if (!check_amop_signature(oprform->amopopr, BOOLOID, oprform->amoplefttype, oprform->amoprighttype)) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("bloom opfamily %s contains operator %s with wrong signature", opfamilyname, format_operator(oprform->amopopr)))); result = false; } } /* Now check for inconsistent groups of operators/functions */ grouplist = identify_opfamily_groups(oprlist, proclist); opclassgroup = NULL; foreach(lc, grouplist) { OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc); /* Remember the group exactly matching the test opclass */ if (thisgroup->lefttype == opcintype && thisgroup->righttype == opcintype) opclassgroup = thisgroup; /* * There is not a lot we can do to check the operator sets, since each * bloom opclass is more or less a law unto itself, and some contain * only operators that are binary-compatible with the opclass datatype * (meaning that empty operator sets can be OK). That case also means * that we shouldn't insist on nonempty function sets except for the * opclass's own group. */ }
/* * 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, (void *) &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, (void *) &spcid, HASH_ENTER, NULL); spc->opts = opts; return spc; }
/* * sepgsql_relation_setattr * * It checks privileges to set attribute of the supplied relation */ void sepgsql_relation_setattr(Oid relOid) { Relation rel; ScanKeyData skey; SysScanDesc sscan; HeapTuple oldtup; HeapTuple newtup; Form_pg_class oldform; Form_pg_class newform; ObjectAddress object; char *audit_name; uint16_t tclass; switch (get_rel_relkind(relOid)) { case RELKIND_RELATION: tclass = SEPG_CLASS_DB_TABLE; break; case RELKIND_SEQUENCE: tclass = SEPG_CLASS_DB_SEQUENCE; break; case RELKIND_VIEW: tclass = SEPG_CLASS_DB_VIEW; break; case RELKIND_INDEX: /* deal with indexes specially */ sepgsql_index_modify(relOid); return; default: /* other relkinds don't need additional work */ return; } /* * Fetch newer catalog */ rel = heap_open(RelationRelationId, AccessShareLock); ScanKeyInit(&skey, ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relOid)); sscan = systable_beginscan(rel, ClassOidIndexId, true, SnapshotSelf, 1, &skey); newtup = systable_getnext(sscan); if (!HeapTupleIsValid(newtup)) elog(ERROR, "catalog lookup failed for relation %u", relOid); newform = (Form_pg_class) GETSTRUCT(newtup); /* * Fetch older catalog */ oldtup = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid)); if (!HeapTupleIsValid(oldtup)) elog(ERROR, "cache lookup failed for relation %u", relOid); oldform = (Form_pg_class) GETSTRUCT(oldtup); /* * Does this ALTER command takes operation to namespace? */ if (newform->relnamespace != oldform->relnamespace) { sepgsql_schema_remove_name(oldform->relnamespace); sepgsql_schema_add_name(newform->relnamespace); } if (strcmp(NameStr(newform->relname), NameStr(oldform->relname)) != 0) sepgsql_schema_rename(oldform->relnamespace); /* * XXX - In the future version, db_tuple:{use} of system catalog entry * shall be checked, if tablespace configuration is changed. */ /* * check db_xxx:{setattr} permission */ object.classId = RelationRelationId; object.objectId = relOid; object.objectSubId = 0; audit_name = getObjectIdentity(&object); sepgsql_avc_check_perms(&object, tclass, SEPG_DB_TABLE__SETATTR, audit_name, true); pfree(audit_name); ReleaseSysCache(oldtup); systable_endscan(sscan); heap_close(rel, AccessShareLock); }
Datum plpgsql_validator(PG_FUNCTION_ARGS) { Oid funcoid = PG_GETARG_OID(0); HeapTuple tuple; Form_pg_proc proc; char functyptype; int numargs; Oid *argtypes; char **argnames; char *argmodes; bool is_dml_trigger = false; bool is_event_trigger = false; int i; if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) PG_RETURN_VOID(); /* Get the new function's pg_proc entry */ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for function %u", funcoid); proc = (Form_pg_proc) GETSTRUCT(tuple); functyptype = get_typtype(proc->prorettype); /* Disallow pseudotype result */ /* except for TRIGGER, RECORD, VOID, or polymorphic */ if (functyptype == TYPTYPE_PSEUDO) { /* we assume OPAQUE with no arguments means a trigger */ if (proc->prorettype == TRIGGEROID || (proc->prorettype == OPAQUEOID && proc->pronargs == 0)) is_dml_trigger = true; else if (proc->prorettype == EVTTRIGGEROID) is_event_trigger = true; else if (proc->prorettype != RECORDOID && proc->prorettype != VOIDOID && !IsPolymorphicType(proc->prorettype)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/pgSQL functions cannot return type %s", format_type_be(proc->prorettype)))); } /* Disallow pseudotypes in arguments (either IN or OUT) */ /* except for polymorphic */ numargs = get_func_arg_info(tuple, &argtypes, &argnames, &argmodes); for (i = 0; i < numargs; i++) { if (get_typtype(argtypes[i]) == TYPTYPE_PSEUDO) { if (!IsPolymorphicType(argtypes[i])) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/pgSQL functions cannot accept type %s", format_type_be(argtypes[i])))); } } /* Postpone body checks if !check_function_bodies */ if (check_function_bodies) { FunctionCallInfoData fake_fcinfo; FmgrInfo flinfo; int rc; TriggerData trigdata; EventTriggerData etrigdata; /* * Connect to SPI manager (is this needed for compilation?) */ if ((rc = SPI_connect()) != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc)); /* * Set up a fake fcinfo with just enough info to satisfy * plpgsql_compile(). */ MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo)); MemSet(&flinfo, 0, sizeof(flinfo)); fake_fcinfo.flinfo = &flinfo; flinfo.fn_oid = funcoid; flinfo.fn_mcxt = CurrentMemoryContext; if (is_dml_trigger) { MemSet(&trigdata, 0, sizeof(trigdata)); trigdata.type = T_TriggerData; fake_fcinfo.context = (Node *) &trigdata; } else if (is_event_trigger) { MemSet(&etrigdata, 0, sizeof(etrigdata)); etrigdata.type = T_EventTriggerData; fake_fcinfo.context = (Node *) &etrigdata; } /* Test-compile the function */ plpgsql_compile(&fake_fcinfo, true); /* * Disconnect from SPI manager */ if ((rc = SPI_finish()) != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc)); } ReleaseSysCache(tuple); PG_RETURN_VOID(); }
/* * 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). Note that * if *not* checking as another role, the caller should pass InvalidOid rather * than GetUserId(). Otherwise the check for row_security = OFF is skipped, and * so we may falsely report that RLS is active when the user has bypassed it. * * 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) { HeapTuple tuple; Form_pg_class classform; bool relrowsecurity; Oid user_id = checkAsUser ? checkAsUser : GetUserId(); /* Nothing to do for built-in relations */ if (relid < FirstNormalObjectId) return RLS_NONE; /* * Check if we have been told to explicitly skip RLS (perhaps because this * is a foreign key check) */ if (InRowLevelSecurityDisabled()) return RLS_NONE; tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); if (!HeapTupleIsValid(tuple)) return RLS_NONE; classform = (Form_pg_class) GETSTRUCT(tuple); relrowsecurity = classform->relrowsecurity; ReleaseSysCache(tuple); /* Nothing to do if the relation does not have RLS */ if (!relrowsecurity) return RLS_NONE; /* * Check permissions * * If the relation has row level security enabled and the row_security GUC * is off, then check if the user has rights to bypass RLS for this * relation. Table owners can always bypass, as can any role with the * BYPASSRLS capability. * * If the role is the table owner, then we bypass RLS unless row_security * is set to 'force'. Note that superuser is always considered an owner. * * Return RLS_NONE_ENV to indicate that this decision depends on the * environment (in this case, what the current values of user_id and * row_security are). */ if (row_security != ROW_SECURITY_FORCE && (pg_class_ownercheck(relid, user_id))) return RLS_NONE_ENV; /* * If the row_security GUC is 'off' then check if the user has permission * to bypass it. Note that we have already handled the case where the * user is the table owner above. * * Note that row_security is always considered 'on' when querying through * a view or other cases where checkAsUser is true, so skip this if * checkAsUser is in use. */ if (!checkAsUser && row_security == ROW_SECURITY_OFF) { if (has_bypassrls_privilege(user_id)) /* OK to bypass */ return RLS_NONE_ENV; else if (noError) return RLS_ENABLED; else ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("insufficient privilege to bypass row security."))); } /* RLS should be fully enabled for this relation. */ return RLS_ENABLED; }
/* * Check given password for given user, and return STATUS_OK or STATUS_ERROR. * In the error case, optionally store a palloc'd string at *logdetail * that will be sent to the postmaster log (but not the client). */ int md5_crypt_verify(const Port *port, const char *role, char *client_pass, char **logdetail) { int retval = STATUS_ERROR; char *shadow_pass, *crypt_pwd; TimestampTz vuntil = 0; char *crypt_client_pass = client_pass; HeapTuple roleTup; Datum datum; bool isnull; /* Get role info from pg_authid */ roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role)); if (!HeapTupleIsValid(roleTup)) { *logdetail = psprintf(_("Role \"%s\" does not exist."), role); return STATUS_ERROR; /* no such user */ } datum = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_rolpassword, &isnull); if (isnull) { ReleaseSysCache(roleTup); *logdetail = psprintf(_("User \"%s\" has no password assigned."), role); return STATUS_ERROR; /* user has no password */ } shadow_pass = TextDatumGetCString(datum); datum = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_rolvaliduntil, &isnull); if (!isnull) vuntil = DatumGetTimestampTz(datum); ReleaseSysCache(roleTup); if (*shadow_pass == '\0') { *logdetail = psprintf(_("User \"%s\" has an empty password."), role); return STATUS_ERROR; /* empty password */ } /* * Compare with the encrypted or plain password depending on the * authentication method being used for this connection. (We do not * bother setting logdetail for pg_md5_encrypt failure: the only possible * error is out-of-memory, which is unlikely, and if it did happen adding * a psprintf call would only make things worse.) */ switch (port->hba->auth_method) { case uaMD5: crypt_pwd = palloc(MD5_PASSWD_LEN + 1); if (isMD5(shadow_pass)) { /* stored password already encrypted, only do salt */ if (!pg_md5_encrypt(shadow_pass + strlen("md5"), port->md5Salt, sizeof(port->md5Salt), crypt_pwd)) { pfree(crypt_pwd); return STATUS_ERROR; } } else { /* stored password is plain, double-encrypt */ char *crypt_pwd2 = palloc(MD5_PASSWD_LEN + 1); if (!pg_md5_encrypt(shadow_pass, port->user_name, strlen(port->user_name), crypt_pwd2)) { pfree(crypt_pwd); pfree(crypt_pwd2); return STATUS_ERROR; } if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"), port->md5Salt, sizeof(port->md5Salt), crypt_pwd)) { pfree(crypt_pwd); pfree(crypt_pwd2); return STATUS_ERROR; } pfree(crypt_pwd2); } break; default: if (isMD5(shadow_pass)) { /* Encrypt user-supplied password to match stored MD5 */ crypt_client_pass = palloc(MD5_PASSWD_LEN + 1); if (!pg_md5_encrypt(client_pass, port->user_name, strlen(port->user_name), crypt_client_pass)) { pfree(crypt_client_pass); return STATUS_ERROR; } } crypt_pwd = shadow_pass; break; } if (strcmp(crypt_client_pass, crypt_pwd) == 0) { /* * Password OK, now check to be sure we are not past rolvaliduntil */ if (isnull) retval = STATUS_OK; else if (vuntil < GetCurrentTimestamp()) { *logdetail = psprintf(_("User \"%s\" has an expired password."), role); retval = STATUS_ERROR; } else retval = STATUS_OK; } else *logdetail = psprintf(_("Password does not match for user \"%s\"."), role); if (port->hba->auth_method == uaMD5) pfree(crypt_pwd); if (crypt_client_pass != client_pass) pfree(crypt_client_pass); return retval; }
static char * format_type_internal(Oid type_oid, int32 typemod, bool typemod_given, bool allow_invalid) { bool with_typemod = typemod_given && (typemod >= 0); HeapTuple tuple; Form_pg_type typeform; Oid array_base_type; bool is_array; char *buf; if (type_oid == InvalidOid && allow_invalid) return pstrdup("-"); tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid)); if (!HeapTupleIsValid(tuple)) { if (allow_invalid) return pstrdup("???"); else elog(ERROR, "cache lookup failed for type %u", type_oid); } typeform = (Form_pg_type) GETSTRUCT(tuple); /* * Check if it's an array (and not a domain --- we don't want to show the * substructure of a domain type). Fixed-length array types such as * "name" shouldn't get deconstructed either. As of Postgres 8.1, rather * than checking typlen we 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 (array_base_type != InvalidOid && typeform->typstorage != 'p' && typeform->typtype != TYPTYPE_DOMAIN) { /* Switch our attention to the array element type */ ReleaseSysCache(tuple); tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type)); if (!HeapTupleIsValid(tuple)) { if (allow_invalid) 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; /* * 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 (typemod_given) { /* * 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 (typemod_given) { /* * 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, and we must double-quote it if it's not a standard identifier * or if it matches any keyword. */ char *nspname; char *typname; if (TypeIsVisible(type_oid)) nspname = NULL; else nspname = get_namespace_name(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 = psnprintf(strlen(buf) + 3, "%s[]", buf); ReleaseSysCache(tuple); return buf; }
/* * Create a new PLyProcedure structure */ static PLyProcedure * PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) { char procName[NAMEDATALEN + 256]; Form_pg_proc procStruct; PLyProcedure *volatile proc; MemoryContext cxt; MemoryContext oldcxt; int rv; char *ptr; procStruct = (Form_pg_proc) GETSTRUCT(procTup); rv = snprintf(procName, sizeof(procName), "__plpython_procedure_%s_%u", NameStr(procStruct->proname), fn_oid); if (rv >= sizeof(procName) || rv < 0) elog(ERROR, "procedure name would overrun buffer"); /* Replace any not-legal-in-Python-names characters with '_' */ for (ptr = procName; *ptr; ptr++) { if (!((*ptr >= 'A' && *ptr <= 'Z') || (*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9'))) *ptr = '_'; } /* Create long-lived context that all procedure info will live in */ cxt = AllocSetContextCreateExtended(TopMemoryContext, procName, MEMCONTEXT_COPY_NAME, ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(cxt); proc = (PLyProcedure *) palloc0(sizeof(PLyProcedure)); proc->mcxt = cxt; PG_TRY(); { Datum protrftypes_datum; Datum prosrcdatum; bool isnull; char *procSource; int i; proc->proname = pstrdup(NameStr(procStruct->proname)); proc->pyname = pstrdup(procName); proc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data); proc->fn_tid = procTup->t_self; proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE); proc->is_setof = procStruct->proretset; proc->is_procedure = (procStruct->prorettype == InvalidOid); proc->src = NULL; proc->argnames = NULL; proc->args = NULL; proc->nargs = 0; proc->langid = procStruct->prolang; protrftypes_datum = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_protrftypes, &isnull); proc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum); proc->code = NULL; proc->statics = NULL; proc->globals = NULL; proc->calldepth = 0; proc->argstack = NULL; /* * get information required for output conversion of the return value, * but only if this isn't a trigger or procedure. */ if (!is_trigger && procStruct->prorettype) { Oid rettype = procStruct->prorettype; HeapTuple rvTypeTup; Form_pg_type rvTypeStruct; rvTypeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype)); if (!HeapTupleIsValid(rvTypeTup)) elog(ERROR, "cache lookup failed for type %u", rettype); rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup); /* Disallow pseudotype result, except for void or record */ if (rvTypeStruct->typtype == TYPTYPE_PSEUDO) { if (rettype == VOIDOID || rettype == RECORDOID) /* okay */ ; else if (rettype == TRIGGEROID || rettype == EVTTRIGGEROID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("trigger functions can only be called as triggers"))); else ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Python functions cannot return type %s", format_type_be(rettype)))); } /* set up output function for procedure result */ PLy_output_setup_func(&proc->result, proc->mcxt, rettype, -1, proc); ReleaseSysCache(rvTypeTup); } else { /* * In a trigger function, we use proc->result and proc->result_in * for converting tuples, but we don't yet have enough info to set * them up. PLy_exec_trigger will deal with it. */ proc->result.typoid = InvalidOid; proc->result_in.typoid = InvalidOid; } /* * Now get information required for input conversion of the * procedure's arguments. Note that we ignore output arguments here. * If the function returns record, those I/O functions will be set up * when the function is first called. */ if (procStruct->pronargs) { Oid *types; char **names, *modes; int pos, total; /* extract argument type info from the pg_proc tuple */ total = get_func_arg_info(procTup, &types, &names, &modes); /* count number of in+inout args into proc->nargs */ if (modes == NULL) proc->nargs = total; else { /* proc->nargs was initialized to 0 above */ for (i = 0; i < total; i++) { if (modes[i] != PROARGMODE_OUT && modes[i] != PROARGMODE_TABLE) (proc->nargs)++; } } /* Allocate arrays for per-input-argument data */ proc->argnames = (char **) palloc0(sizeof(char *) * proc->nargs); proc->args = (PLyDatumToOb *) palloc0(sizeof(PLyDatumToOb) * proc->nargs); for (i = pos = 0; i < total; i++) { HeapTuple argTypeTup; Form_pg_type argTypeStruct; if (modes && (modes[i] == PROARGMODE_OUT || modes[i] == PROARGMODE_TABLE)) continue; /* skip OUT arguments */ Assert(types[i] == procStruct->proargtypes.values[pos]); argTypeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(types[i])); if (!HeapTupleIsValid(argTypeTup)) elog(ERROR, "cache lookup failed for type %u", types[i]); argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup); /* disallow pseudotype arguments */ if (argTypeStruct->typtype == TYPTYPE_PSEUDO) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Python functions cannot accept type %s", format_type_be(types[i])))); /* set up I/O function info */ PLy_input_setup_func(&proc->args[pos], proc->mcxt, types[i], -1, /* typmod not known */ proc); /* get argument name */ proc->argnames[pos] = names ? pstrdup(names[i]) : NULL; ReleaseSysCache(argTypeTup); pos++; } } /* * get the text of the function. */ prosrcdatum = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "null prosrc"); procSource = TextDatumGetCString(prosrcdatum); PLy_procedure_compile(proc, procSource); pfree(procSource); } PG_CATCH(); { MemoryContextSwitchTo(oldcxt); PLy_procedure_delete(proc); PG_RE_THROW(); } PG_END_TRY(); MemoryContextSwitchTo(oldcxt); return proc; }
/* * 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; MemSet(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(Oid); ctl.entrysize = sizeof(TSParserCacheEntry); ctl.hash = oid_hash; TSParserCacheHash = hash_create("Tsearch parser cache", 4, &ctl, HASH_ELEM | HASH_FUNCTION); /* 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, (void *) &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, (void *) &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 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, (void *) &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, (void *) &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 = heap_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); heap_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; }