/*------------------------------------------------------------------------- * * catalog.c * * routines for working with Postgres's catalog and caches * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "access/genam.h" #include "access/htup_details.h" #if PG_VERSION_NUM >= 120000 #include "access/table.h" #endif #include "catalog/pg_extension.h" #include "catalog/indexing.h" #include "catalog/pg_language.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/extension.h" #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #if PG_VERSION_NUM >= 100000 #include "utils/regproc.h" #endif #if PG_VERSION_NUM >= 110000 #include "catalog/pg_proc.h" #endif #if PG_VERSION_NUM < 120000 #include "access/sysattr.h" #endif #include "utils/syscache.h" /* * Fix - change of typename in Postgres 14 */ bool plpgsql_check_is_eventtriggeroid(Oid typoid) { #if PG_VERSION_NUM >= 140000 return typoid == EVENT_TRIGGEROID; #else return typoid == EVTTRIGGEROID; #endif } /* * Prepare metadata necessary for plpgsql_check */ void plpgsql_check_get_function_info(HeapTuple procTuple, Oid *rettype, char *volatility, PLpgSQL_trigtype *trigtype, bool *is_procedure) { Form_pg_proc proc; char functyptype; proc = (Form_pg_proc) GETSTRUCT(procTuple); functyptype = get_typtype(proc->prorettype); *trigtype = PLPGSQL_NOT_TRIGGER; #if PG_VERSION_NUM >= 110000 *is_procedure = proc->prokind == PROKIND_PROCEDURE; #else *is_procedure = false; #endif /* * 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 #if PG_VERSION_NUM < 130000 || (proc->prorettype == OPAQUEOID && proc->pronargs == 0) #endif ) *trigtype = PLPGSQL_DML_TRIGGER; else if (plpgsql_check_is_eventtriggeroid(proc->prorettype)) *trigtype = PLPGSQL_EVENT_TRIGGER; 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)))); } *volatility = ((Form_pg_proc) GETSTRUCT(procTuple))->provolatile; *rettype = ((Form_pg_proc) GETSTRUCT(procTuple))->prorettype; } char * plpgsql_check_get_src(HeapTuple procTuple) { Datum prosrcdatum; bool isnull; prosrcdatum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "null prosrc"); return TextDatumGetCString(prosrcdatum); } /* * Process necessary checking before code checking * a) disallow other than plpgsql check function, * b) when function is trigger function, then reloid must be defined */ void plpgsql_check_precheck_conditions(plpgsql_check_info *cinfo) { Form_pg_proc proc; Form_pg_language languageStruct; HeapTuple languageTuple; char *funcname; proc = (Form_pg_proc) GETSTRUCT(cinfo->proctuple); funcname = format_procedure(cinfo->fn_oid); /* used language must be plpgsql */ languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang)); Assert(HeapTupleIsValid(languageTuple)); languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); if (strcmp(NameStr(languageStruct->lanname), "plpgsql") != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("%s is not a plpgsql function", funcname))); ReleaseSysCache(languageTuple); /* profiler doesn't require trigger data check */ if (!cinfo->show_profile) { /* dml trigger needs valid relid, others not */ if (cinfo->trigtype == PLPGSQL_DML_TRIGGER) { if (!OidIsValid(cinfo->relid)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("missing trigger relation"), errhint("Trigger relation oid must be valid"))); } else { if (OidIsValid(cinfo->relid)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("function is not trigger"), errhint("Trigger relation oid must not be valid for non dml trigger function."))); } } pfree(funcname); } /* * plpgsql_check_get_extension_schema - given an extension OID, fetch its extnamespace * * Returns InvalidOid if no such extension. */ static Oid get_extension_schema(Oid ext_oid) { Oid result; Relation rel; SysScanDesc scandesc; HeapTuple tuple; ScanKeyData entry[1]; #if PG_VERSION_NUM >= 120000 rel = table_open(ExtensionRelationId, AccessShareLock); ScanKeyInit(&entry[0], Anum_pg_extension_oid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(ext_oid)); #else rel = heap_open(ExtensionRelationId, AccessShareLock); ScanKeyInit(&entry[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(ext_oid)); #endif scandesc = systable_beginscan(rel, ExtensionOidIndexId, true, NULL, 1, entry); tuple = systable_getnext(scandesc); /* We assume that there can be at most one matching tuple */ if (HeapTupleIsValid(tuple)) result = ((Form_pg_extension) GETSTRUCT(tuple))->extnamespace; else result = InvalidOid; systable_endscan(scandesc); #if PG_VERSION_NUM >= 120000 table_close(rel, AccessShareLock); #else heap_close(rel, AccessShareLock); #endif return result; } /* * Returns oid of pragma function. It is used for elimination * pragma function from volatility tests. */ Oid plpgsql_check_pragma_func_oid(void) { Oid result = InvalidOid; Oid extoid; extoid = get_extension_oid("plpgsql_check", true); if (OidIsValid(extoid)) { CatCList *catlist; Oid schemaoid; int i; schemaoid = get_extension_schema(extoid); /* Search syscache by name only */ catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum("plpgsql_check_pragma")); for (i = 0; i < catlist->n_members; i++) { HeapTuple proctup = &catlist->members[i]->tuple; Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); /* Consider only procs in specified namespace */ if (procform->pronamespace != schemaoid) continue; #if PG_VERSION_NUM >= 120000 result = procform->oid; #else result = HeapTupleGetOid(proctup); #endif break; } ReleaseSysCacheList(catlist); } return result; }