/* * generate_relation_name generates a schema qualified relation name for the * given relationId. Note: This function is adapted from generate_relation_name * from postgres ruleutils.c. */ static char * generate_relation_name(Oid relationId) { HeapTuple tp; Form_pg_class reltup; char *relname; char *nspname; char *result; tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relationId)); if (!HeapTupleIsValid(tp)) { ereport(ERROR, (errmsg("cache lookup failed for relation %u", relationId))); } reltup = (Form_pg_class) GETSTRUCT(tp); relname = NameStr(reltup->relname); nspname = get_namespace_name(reltup->relnamespace); result = quote_qualified_identifier(nspname, relname); ReleaseSysCache(tp); return result; }
char * get_relation_name(Oid relid) { return quote_qualified_identifier( get_namespace_name(get_rel_namespace(relid)), get_rel_name(relid)); }
/* * regprocout - converts proc OID to "pro_name" */ Datum regprocout(PG_FUNCTION_ARGS) { RegProcedure proid = PG_GETARG_OID(0); char *result; HeapTuple proctup; if (proid == InvalidOid) { result = pstrdup("-"); PG_RETURN_CSTRING(result); } proctup = SearchSysCache(PROCOID, ObjectIdGetDatum(proid), 0, 0, 0); if (HeapTupleIsValid(proctup)) { Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); char *proname = NameStr(procform->proname); /* * In bootstrap mode, skip the fancy namespace stuff and just return * the proc name. (This path is only needed for debugging output * anyway.) */ if (IsBootstrapProcessingMode()) result = pstrdup(proname); else { char *nspname; FuncCandidateList clist; /* * Would this proc be found (uniquely!) by regprocin? If not, * qualify it. */ clist = FuncnameGetCandidates(list_make1(makeString(proname)), -1); if (clist != NULL && clist->next == NULL && clist->oid == proid) nspname = NULL; else nspname = get_namespace_name(procform->pronamespace); result = quote_qualified_identifier(nspname, proname); } ReleaseSysCache(proctup); } else { /* If OID doesn't match any pg_proc entry, return it numerically */ result = (char *) palloc(NAMEDATALEN); snprintf(result, NAMEDATALEN, "%u", proid); } PG_RETURN_CSTRING(result); }
/* * Routine to produce regprocedure names; see format_procedure above. * * force_qualify says whether to schema-qualify; if true, the name is always * qualified regardless of search_path visibility. Otherwise the name is only * qualified if the function is not in path. */ static char * format_procedure_internal(Oid procedure_oid, bool force_qualify) { char *result; HeapTuple proctup; proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid)); if (HeapTupleIsValid(proctup)) { Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); char *proname = NameStr(procform->proname); int nargs = procform->pronargs; int i; char *nspname; StringInfoData buf; /* XXX no support here for bootstrap mode */ initStringInfo(&buf); /* * Would this proc be found (given the right args) by regprocedurein? * If not, or if caller requests it, we need to qualify it. */ if (!force_qualify && FunctionIsVisible(procedure_oid)) nspname = NULL; else nspname = get_namespace_name(procform->pronamespace); appendStringInfo(&buf, "%s(", quote_qualified_identifier(nspname, proname)); for (i = 0; i < nargs; i++) { Oid thisargtype = procform->proargtypes.values[i]; if (i > 0) appendStringInfoChar(&buf, ','); appendStringInfoString(&buf, force_qualify ? format_type_be_qualified(thisargtype) : format_type_be(thisargtype)); } appendStringInfoChar(&buf, ')'); result = buf.data; ReleaseSysCache(proctup); } else { /* If OID doesn't match any pg_proc entry, return it numerically */ result = (char *) palloc(NAMEDATALEN); snprintf(result, NAMEDATALEN, "%u", procedure_oid); } return result; }
/* GUC check_hook for default_text_search_config */ bool check_TSCurrentConfig(char **newval, void **extra, GucSource source) { /* * If we aren't inside a transaction, we cannot do database access so * cannot verify the config name. Must accept it on faith. */ if (IsTransactionState()) { Oid cfgId; HeapTuple tuple; Form_pg_ts_config cfg; char *buf; cfgId = get_ts_config_oid(stringToQualifiedNameList(*newval), true); /* * When source == PGC_S_TEST, don't throw a hard error for a * nonexistent configuration, only a NOTICE. See comments in guc.h. */ if (!OidIsValid(cfgId)) { if (source == PGC_S_TEST) { ereport(NOTICE, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("text search configuration \"%s\" does not exist", *newval))); return true; } else return false; } /* * Modify the actually stored value to be fully qualified, to ensure * later changes of search_path don't affect it. */ tuple = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for text search configuration %u", cfgId); cfg = (Form_pg_ts_config) GETSTRUCT(tuple); buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace), NameStr(cfg->cfgname)); ReleaseSysCache(tuple); /* GUC wants it malloc'd not palloc'd */ free(*newval); *newval = strdup(buf); pfree(buf); if (!*newval) return false; } return true; }
/* * regclassout - converts class OID to "class_name" */ Datum regclassout(PG_FUNCTION_ARGS) { Oid classid = PG_GETARG_OID(0); char *result; HeapTuple classtup; if (classid == InvalidOid) { result = pstrdup("-"); PG_RETURN_CSTRING(result); } classtup = SearchSysCache(RELOID, ObjectIdGetDatum(classid), 0, 0, 0); if (HeapTupleIsValid(classtup)) { Form_pg_class classform = (Form_pg_class) GETSTRUCT(classtup); char *classname = NameStr(classform->relname); /* * In bootstrap mode, skip the fancy namespace stuff and just * return the class name. (This path is only needed for debugging * output anyway.) */ if (IsBootstrapProcessingMode()) result = pstrdup(classname); else { char *nspname; /* * Would this class be found by regclassin? If not, qualify * it. */ if (RelationIsVisible(classid)) nspname = NULL; else nspname = get_namespace_name(classform->relnamespace); result = quote_qualified_identifier(nspname, classname); } ReleaseSysCache(classtup); } else { /* If OID doesn't match any pg_class entry, return it numerically */ result = (char *) palloc(NAMEDATALEN); snprintf(result, NAMEDATALEN, "%u", classid); } PG_RETURN_CSTRING(result); }
/* * Print a relation name into the StringInfo provided by caller. */ static void print_relname(StringInfo s, Relation rel) { Form_pg_class class_form = RelationGetForm(rel); appendStringInfoString(s, quote_qualified_identifier( get_namespace_name( get_rel_namespace(RelationGetRelid(rel))), NameStr(class_form->relname))); }
static char * get_relation_name(Oid relid) { Oid nsp = get_rel_namespace(relid); char *nspname; /* Qualify the name if not visible in search path */ if (RelationIsVisible(relid)) nspname = NULL; else nspname = get_namespace_name(nsp); return quote_qualified_identifier(nspname, get_rel_name(relid)); }
static char * get_relation_name(Oid relid) { Oid nsp = get_rel_namespace(relid); char *nspname; char *strver; int ver; /* Get the version of the running server (PG_VERSION_NUM would return * the version we compiled the extension with) */ strver = GetConfigOptionByName("server_version_num", NULL #if PG_VERSION_NUM >= 90600 , false /* missing_ok */ #endif ); ver = atoi(strver); pfree(strver); /* * Relation names given by PostgreSQL core are always * qualified since some minor releases. Note that this change * wasn't introduced in PostgreSQL 9.2 and 9.1 releases. */ if ((ver >= 100000 && ver < 100003) || (ver >= 90600 && ver < 90608) || (ver >= 90500 && ver < 90512) || (ver >= 90400 && ver < 90417) || (ver >= 90300 && ver < 90322) || (ver >= 90200 && ver < 90300) || (ver >= 90100 && ver < 90200)) { /* Qualify the name if not visible in search path */ if (RelationIsVisible(relid)) nspname = NULL; else nspname = get_namespace_name(nsp); } else { /* Always qualify the name */ if (OidIsValid(nsp)) nspname = get_namespace_name(nsp); else nspname = NULL; } return quote_qualified_identifier(nspname, get_rel_name(relid)); }
/* * regdictionaryout - converts tsdictionary OID to "tsdictionaryname" */ Datum regdictionaryout(PG_FUNCTION_ARGS) { Oid dictid = PG_GETARG_OID(0); char *result; HeapTuple dicttup; if (dictid == InvalidOid) { result = pstrdup("-"); PG_RETURN_CSTRING(result); } dicttup = SearchSysCache(TSDICTOID, ObjectIdGetDatum(dictid), 0, 0, 0); if (HeapTupleIsValid(dicttup)) { Form_pg_ts_dict dictform = (Form_pg_ts_dict) GETSTRUCT(dicttup); char *dictname = NameStr(dictform->dictname); char *nspname; /* * Would this dictionary be found by regdictionaryin? If not, qualify * it. */ if (TSDictionaryIsVisible(dictid)) nspname = NULL; else nspname = get_namespace_name(dictform->dictnamespace); result = quote_qualified_identifier(nspname, dictname); ReleaseSysCache(dicttup); } else { /* If OID doesn't match any pg_ts_dict row, return it numerically */ result = (char *) palloc(NAMEDATALEN); snprintf(result, NAMEDATALEN, "%u", dictid); } PG_RETURN_CSTRING(result); }
/* * regconfigout - converts tsconfig OID to "tsconfigname" */ Datum regconfigout(PG_FUNCTION_ARGS) { Oid cfgid = PG_GETARG_OID(0); char *result; HeapTuple cfgtup; if (cfgid == InvalidOid) { result = pstrdup("-"); PG_RETURN_CSTRING(result); } cfgtup = SearchSysCache(TSCONFIGOID, ObjectIdGetDatum(cfgid), 0, 0, 0); if (HeapTupleIsValid(cfgtup)) { Form_pg_ts_config cfgform = (Form_pg_ts_config) GETSTRUCT(cfgtup); char *cfgname = NameStr(cfgform->cfgname); char *nspname; /* * Would this config be found by regconfigin? If not, qualify it. */ if (TSConfigIsVisible(cfgid)) nspname = NULL; else nspname = get_namespace_name(cfgform->cfgnamespace); result = quote_qualified_identifier(nspname, cfgname); ReleaseSysCache(cfgtup); } else { /* If OID doesn't match any pg_ts_config row, return it numerically */ result = (char *) palloc(NAMEDATALEN); snprintf(result, NAMEDATALEN, "%u", cfgid); } PG_RETURN_CSTRING(result); }
/* * regclassout - converts class OID to "class_name" */ Datum regclassout(PG_FUNCTION_ARGS) { Oid classid = PG_GETARG_OID(0); char *result; HeapTuple classtup; cqContext *pcqCtx; if (classid == InvalidOid) { result = pstrdup("-"); PG_RETURN_CSTRING(result); } pcqCtx = caql_beginscan( NULL, cql("SELECT * FROM pg_class " " WHERE oid = :1 ", ObjectIdGetDatum(classid))); classtup = caql_getnext(pcqCtx); /* XXX XXX select relname, relnamespace from pg_class */ if (HeapTupleIsValid(classtup)) { Form_pg_class classform = (Form_pg_class) GETSTRUCT(classtup); char *classname = NameStr(classform->relname); /* * In bootstrap mode, skip the fancy namespace stuff and just return * the class name. (This path is only needed for debugging output * anyway.) */ if (IsBootstrapProcessingMode()) result = pstrdup(classname); else { char *nspname; /* * Would this class be found by regclassin? If not, qualify it. */ if (RelationIsVisible(classid)) nspname = NULL; else nspname = get_namespace_name(classform->relnamespace); result = quote_qualified_identifier(nspname, classname); } } else { /* If OID doesn't match any pg_class entry, return it numerically */ result = (char *) palloc(NAMEDATALEN); snprintf(result, NAMEDATALEN, "%u", classid); } caql_endscan(pcqCtx); PG_RETURN_CSTRING(result); }
/* * refresh_by_match_merge * * Refresh a materialized view with transactional semantics, while allowing * concurrent reads. * * This is called after a new version of the data has been created in a * temporary table. It performs a full outer join against the old version of * the data, producing "diff" results. This join cannot work if there are any * duplicated rows in either the old or new versions, in the sense that every * column would compare as equal between the two rows. It does work correctly * in the face of rows which have at least one NULL value, with all non-NULL * columns equal. The behavior of NULLs on equality tests and on UNIQUE * indexes turns out to be quite convenient here; the tests we need to make * are consistent with default behavior. If there is at least one UNIQUE * index on the materialized view, we have exactly the guarantee we need. * * The temporary table used to hold the diff results contains just the TID of * the old record (if matched) and the ROW from the new table as a single * column of complex record type (if matched). * * Once we have the diff table, we perform set-based DELETE and INSERT * operations against the materialized view, and discard both temporary * tables. * * Everything from the generation of the new data to applying the differences * takes place under cover of an ExclusiveLock, since it seems as though we * would want to prohibit not only concurrent REFRESH operations, but also * incremental maintenance. It also doesn't seem reasonable or safe to allow * SELECT FOR UPDATE or SELECT FOR SHARE on rows being updated or deleted by * this command. */ static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner, int save_sec_context) { StringInfoData querybuf; Relation matviewRel; Relation tempRel; char *matviewname; char *tempname; char *diffname; TupleDesc tupdesc; bool foundUniqueIndex; List *indexoidlist; ListCell *indexoidscan; int16 relnatts; bool *usedForQual; initStringInfo(&querybuf); matviewRel = heap_open(matviewOid, NoLock); matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)), RelationGetRelationName(matviewRel)); tempRel = heap_open(tempOid, NoLock); tempname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel)), RelationGetRelationName(tempRel)); diffname = make_temptable_name_n(tempname, 2); relnatts = matviewRel->rd_rel->relnatts; usedForQual = (bool *) palloc0(sizeof(bool) * relnatts); /* Open SPI context. */ if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); /* Analyze the temp table with the new contents. */ appendStringInfo(&querybuf, "ANALYZE %s", tempname); if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY) elog(ERROR, "SPI_exec failed: %s", querybuf.data); /* * We need to ensure that there are not duplicate rows without NULLs in * the new data set before we can count on the "diff" results. Check for * that in a way that allows showing the first duplicated row found. Even * after we pass this test, a unique index on the materialized view may * find a duplicate key problem. */ resetStringInfo(&querybuf); appendStringInfo(&querybuf, "SELECT newdata FROM %s newdata " "WHERE newdata IS NOT NULL AND EXISTS " "(SELECT * FROM %s newdata2 WHERE newdata2 IS NOT NULL " "AND newdata2 OPERATOR(pg_catalog.*=) newdata " "AND newdata2.ctid OPERATOR(pg_catalog.<>) " "newdata.ctid) LIMIT 1", tempname, tempname); if (SPI_execute(querybuf.data, false, 1) != SPI_OK_SELECT) elog(ERROR, "SPI_exec failed: %s", querybuf.data); if (SPI_processed > 0) { ereport(ERROR, (errcode(ERRCODE_CARDINALITY_VIOLATION), errmsg("new data for \"%s\" contains duplicate rows without any null columns", RelationGetRelationName(matviewRel)), errdetail("Row: %s", SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1)))); } SetUserIdAndSecContext(relowner, save_sec_context | SECURITY_LOCAL_USERID_CHANGE); /* Start building the query for creating the diff table. */ resetStringInfo(&querybuf); appendStringInfo(&querybuf, "CREATE TEMP TABLE %s AS " "SELECT mv.ctid AS tid, newdata " "FROM %s mv FULL JOIN %s newdata ON (", diffname, matviewname, tempname); /* * Get the list of index OIDs for the table from the relcache, and look up * each one in the pg_index syscache. We will test for equality on all * columns present in all unique indexes which only reference columns and * include all rows. */ tupdesc = matviewRel->rd_att; foundUniqueIndex = false; indexoidlist = RelationGetIndexList(matviewRel); foreach(indexoidscan, indexoidlist) { Oid indexoid = lfirst_oid(indexoidscan); Relation indexRel; Form_pg_index indexStruct; indexRel = index_open(indexoid, RowExclusiveLock); indexStruct = indexRel->rd_index; /* * We're only interested if it is unique, valid, contains no * expressions, and is not partial. */ if (indexStruct->indisunique && IndexIsValid(indexStruct) && RelationGetIndexExpressions(indexRel) == NIL && RelationGetIndexPredicate(indexRel) == NIL) { int numatts = indexStruct->indnatts; int i; /* Add quals for all columns from this index. */ for (i = 0; i < numatts; i++) { int attnum = indexStruct->indkey.values[i]; Oid type; Oid op; const char *colname; /* * Only include the column once regardless of how many times * it shows up in how many indexes. */ if (usedForQual[attnum - 1]) continue; usedForQual[attnum - 1] = true; /* * Actually add the qual, ANDed with any others. */ if (foundUniqueIndex) appendStringInfoString(&querybuf, " AND "); colname = quote_identifier(NameStr((tupdesc->attrs[attnum - 1])->attname)); appendStringInfo(&querybuf, "newdata.%s ", colname); type = attnumTypeId(matviewRel, attnum); op = lookup_type_cache(type, TYPECACHE_EQ_OPR)->eq_opr; mv_GenerateOper(&querybuf, op); appendStringInfo(&querybuf, " mv.%s", colname); foundUniqueIndex = true; } } /* Keep the locks, since we're about to run DML which needs them. */ index_close(indexRel, NoLock); }
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 = SearchSysCache(TYPEOID, ObjectIdGetDatum(type_oid), 0, 0, 0); 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 = SearchSysCache(TYPEOID, ObjectIdGetDatum(array_base_type), 0, 0, 0); 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; }
/* * format_procedure - converts proc OID to "pro_name(args)" * * This exports the useful functionality of regprocedureout for use * in other backend modules. The result is a palloc'd string. */ char * format_procedure(Oid procedure_oid) { char *result; HeapTuple proctup; cqContext *pcqCtx; pcqCtx = caql_beginscan( NULL, cql("SELECT * FROM pg_proc " " WHERE oid = :1 ", ObjectIdGetDatum(procedure_oid))); proctup = caql_getnext(pcqCtx); /* XXX XXX select proname, pronamespace from pg_proc */ if (HeapTupleIsValid(proctup)) { Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); char *proname = NameStr(procform->proname); int nargs = procform->pronargs; int i; char *nspname; StringInfoData buf; /* XXX no support here for bootstrap mode */ initStringInfo(&buf); /* * Would this proc be found (given the right args) by regprocedurein? * If not, we need to qualify it. */ if (FunctionIsVisible(procedure_oid)) nspname = NULL; else nspname = get_namespace_name(procform->pronamespace); appendStringInfo(&buf, "%s(", quote_qualified_identifier(nspname, proname)); for (i = 0; i < nargs; i++) { Oid thisargtype = procform->proargtypes.values[i]; if (i > 0) appendStringInfoChar(&buf, ','); appendStringInfoString(&buf, format_type_be(thisargtype)); } appendStringInfoChar(&buf, ')'); result = buf.data; } else { /* If OID doesn't match any pg_proc entry, return it numerically */ result = (char *) palloc(NAMEDATALEN); snprintf(result, NAMEDATALEN, "%u", procedure_oid); } caql_endscan(pcqCtx); return result; }
/* * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command * * This refreshes the materialized view by creating a new table and swapping * the relfilenodes of the new table and the old materialized view, so the OID * of the original materialized view is preserved. Thus we do not lose GRANT * nor references to this materialized view. * * If WITH NO DATA was specified, this is effectively like a TRUNCATE; * otherwise it is like a TRUNCATE followed by an INSERT using the SELECT * statement associated with the materialized view. The statement node's * skipData field shows whether the clause was used. * * Indexes are rebuilt too, via REINDEX. Since we are effectively bulk-loading * the new heap, it's better to create the indexes afterwards than to fill them * incrementally while we load. * * The matview's "populated" state is changed based on whether the contents * reflect the result set of the materialized view's query. */ ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ParamListInfo params, char *completionTag) { Oid matviewOid; Relation matviewRel; RewriteRule *rule; List *actions; Query *dataQuery; Oid tableSpace; Oid relowner; Oid OIDNewHeap; DestReceiver *dest; uint64 processed = 0; bool concurrent; LOCKMODE lockmode; char relpersistence; Oid save_userid; int save_sec_context; int save_nestlevel; ObjectAddress address; /* Determine strength of lock needed. */ concurrent = stmt->concurrent; lockmode = concurrent ? ExclusiveLock : AccessExclusiveLock; /* * Get a lock until end of transaction. */ matviewOid = RangeVarGetRelidExtended(stmt->relation, lockmode, 0, RangeVarCallbackOwnsTable, NULL); matviewRel = table_open(matviewOid, NoLock); /* Make sure it is a materialized view. */ if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("\"%s\" is not a materialized view", RelationGetRelationName(matviewRel)))); /* Check that CONCURRENTLY is not specified if not populated. */ if (concurrent && !RelationIsPopulated(matviewRel)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("CONCURRENTLY cannot be used when the materialized view is not populated"))); /* Check that conflicting options have not been specified. */ if (concurrent && stmt->skipData) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("CONCURRENTLY and WITH NO DATA options cannot be used together"))); /* * Check that everything is correct for a refresh. Problems at this point * are internal errors, so elog is sufficient. */ if (matviewRel->rd_rel->relhasrules == false || matviewRel->rd_rules->numLocks < 1) elog(ERROR, "materialized view \"%s\" is missing rewrite information", RelationGetRelationName(matviewRel)); if (matviewRel->rd_rules->numLocks > 1) elog(ERROR, "materialized view \"%s\" has too many rules", RelationGetRelationName(matviewRel)); rule = matviewRel->rd_rules->rules[0]; if (rule->event != CMD_SELECT || !(rule->isInstead)) elog(ERROR, "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule", RelationGetRelationName(matviewRel)); actions = rule->actions; if (list_length(actions) != 1) elog(ERROR, "the rule for materialized view \"%s\" is not a single action", RelationGetRelationName(matviewRel)); /* * Check that there is a unique index with no WHERE clause on one or more * columns of the materialized view if CONCURRENTLY is specified. */ if (concurrent) { List *indexoidlist = RelationGetIndexList(matviewRel); ListCell *indexoidscan; bool hasUniqueIndex = false; foreach(indexoidscan, indexoidlist) { Oid indexoid = lfirst_oid(indexoidscan); Relation indexRel; indexRel = index_open(indexoid, AccessShareLock); hasUniqueIndex = is_usable_unique_index(indexRel); index_close(indexRel, AccessShareLock); if (hasUniqueIndex) break; } list_free(indexoidlist); if (!hasUniqueIndex) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("cannot refresh materialized view \"%s\" concurrently", quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)), RelationGetRelationName(matviewRel))), errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view."))); }
/* * master_append_table_to_shard appends the given table's contents to the given * shard, and updates shard metadata on the master node. If the function fails * to append table data to all shard placements, it doesn't update any metadata * and errors out. Else if the function fails to append table data to some of * the shard placements, it marks those placements as invalid. These invalid * placements will get cleaned up during shard rebalancing. */ Datum master_append_table_to_shard(PG_FUNCTION_ARGS) { uint64 shardId = PG_GETARG_INT64(0); text *sourceTableNameText = PG_GETARG_TEXT_P(1); text *sourceNodeNameText = PG_GETARG_TEXT_P(2); uint32 sourceNodePort = PG_GETARG_UINT32(3); char *sourceTableName = text_to_cstring(sourceTableNameText); char *sourceNodeName = text_to_cstring(sourceNodeNameText); Oid shardSchemaOid = 0; char *shardSchemaName = NULL; char *shardTableName = NULL; char *shardQualifiedName = NULL; List *shardPlacementList = NIL; List *succeededPlacementList = NIL; List *failedPlacementList = NIL; ListCell *shardPlacementCell = NULL; ListCell *failedPlacementCell = NULL; uint64 newShardSize = 0; uint64 shardMaxSizeInBytes = 0; float4 shardFillLevel = 0.0; char partitionMethod = 0; ShardInterval *shardInterval = LoadShardInterval(shardId); Oid relationId = shardInterval->relationId; bool cstoreTable = CStoreTable(relationId); char storageType = shardInterval->storageType; EnsureTablePermissions(relationId, ACL_INSERT); if (storageType != SHARD_STORAGE_TABLE && !cstoreTable) { ereport(ERROR, (errmsg("cannot append to shardId " UINT64_FORMAT, shardId), errdetail("The underlying shard is not a regular table"))); } partitionMethod = PartitionMethod(relationId); if (partitionMethod == DISTRIBUTE_BY_HASH) { ereport(ERROR, (errmsg("cannot append to shardId " UINT64_FORMAT, shardId), errdetail("We currently don't support appending to shards " "in hash-partitioned tables"))); } /* * We lock on the shardId, but do not unlock. When the function returns, and * the transaction for this function commits, this lock will automatically * be released. This ensures appends to a shard happen in a serial manner. */ LockShardResource(shardId, AccessExclusiveLock); /* get schame name of the target shard */ shardSchemaOid = get_rel_namespace(relationId); shardSchemaName = get_namespace_name(shardSchemaOid); /* Build shard table name. */ shardTableName = get_rel_name(relationId); AppendShardIdToName(&shardTableName, shardId); shardQualifiedName = quote_qualified_identifier(shardSchemaName, shardTableName); shardPlacementList = FinalizedShardPlacementList(shardId); if (shardPlacementList == NIL) { ereport(ERROR, (errmsg("could not find any shard placements for shardId " UINT64_FORMAT, shardId), errhint("Try running master_create_empty_shard() first"))); } /* issue command to append table to each shard placement */ foreach(shardPlacementCell, shardPlacementList) { ShardPlacement *shardPlacement = (ShardPlacement *) lfirst(shardPlacementCell); char *workerName = shardPlacement->nodeName; uint32 workerPort = shardPlacement->nodePort; List *queryResultList = NIL; StringInfo workerAppendQuery = makeStringInfo(); appendStringInfo(workerAppendQuery, WORKER_APPEND_TABLE_TO_SHARD, quote_literal_cstr(shardQualifiedName), quote_literal_cstr(sourceTableName), quote_literal_cstr(sourceNodeName), sourceNodePort); /* inserting data should be performed by the current user */ queryResultList = ExecuteRemoteQuery(workerName, workerPort, NULL, workerAppendQuery); if (queryResultList != NIL) { succeededPlacementList = lappend(succeededPlacementList, shardPlacement); } else { failedPlacementList = lappend(failedPlacementList, shardPlacement); } }
/* * regprocout - converts proc OID to "pro_name" */ Datum regprocout(PG_FUNCTION_ARGS) { RegProcedure proid = PG_GETARG_OID(0); char *result; HeapTuple proctup; cqContext *pcqCtx; if (proid == InvalidOid) { result = pstrdup("-"); PG_RETURN_CSTRING(result); } pcqCtx = caql_beginscan( NULL, cql("SELECT * FROM pg_proc " " WHERE oid = :1 ", ObjectIdGetDatum(proid))); proctup = caql_getnext(pcqCtx); /* XXX XXX select proname, pronamespace from pg_proc */ if (HeapTupleIsValid(proctup)) { Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); char *proname = NameStr(procform->proname); /* * In bootstrap mode, skip the fancy namespace stuff and just return * the proc name. (This path is only needed for debugging output * anyway.) */ if (IsBootstrapProcessingMode()) result = pstrdup(proname); else { char *nspname; FuncCandidateList clist; /* * Would this proc be found (uniquely!) by regprocin? If not, * qualify it. */ clist = FuncnameGetCandidates(list_make1(makeString(proname)), -1, false, false); if (clist != NULL && clist->next == NULL && clist->oid == proid) nspname = NULL; else nspname = get_namespace_name(procform->pronamespace); result = quote_qualified_identifier(nspname, proname); } } else { /* If OID doesn't match any pg_proc entry, return it numerically */ result = (char *) palloc(NAMEDATALEN); snprintf(result, NAMEDATALEN, "%u", proid); } caql_endscan(pcqCtx); PG_RETURN_CSTRING(result); }
static char *format_type_internal(oid_t type_oid, int32 typemod, bool typemod_given, bool allow_invalid) { bool with_typemod = typemod_given && (typemod >= 0); struct heap_tuple * tuple; Form_pg_type typeform; oid_t array_base_type; bool is_array; char *buf; if (type_oid == INVALID_OID && allow_invalid) return pstrdup("-"); tuple = search_syscache1(TYPEOID, OID_TO_D(type_oid)); if (!HT_VALID(tuple)) { if (allow_invalid) return pstrdup("???"); else elog(ERROR, "cache lookup failed for type %u", type_oid); } typeform = (Form_pg_type) GET_STRUCT(tuple); /* * Check if it's a regular (variable length) array type. Fixed-length * array types such as "name" shouldn't get deconstructed. 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 oid_vector_s as oid[]. */ array_base_type = typeform->typelem; if (array_base_type != INVALID_OID && typeform->typstorage != 'p') { /* Switch our attention to the array element type */ release_syscache(tuple); tuple = search_syscache1(TYPEOID, OID_TO_D(array_base_type)); if (!HT_VALID(tuple)) { if (allow_invalid) return pstrdup("???[]"); else elog(ERROR, "cache lookup failed for type %u", type_oid); } typeform = (Form_pg_type) GET_STRUCT(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 (type_is_visible(type_oid)) nspname = NULL; else nspname = get_ns_name(typeform->typnamespace); typname = NAME_TO_STR(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); release_syscache(tuple); return buf; }
/* * DropShards drops all given shards in a relation. The id, name and schema * for the relation are explicitly provided, since this function may be * called when the table is already dropped. * * We mark shard placements that we couldn't drop as to be deleted later, but * we do delete the shard metadadata. */ static int DropShards(Oid relationId, char *schemaName, char *relationName, List *deletableShardIntervalList) { ListCell *shardIntervalCell = NULL; int droppedShardCount = 0; BeginOrContinueCoordinatedTransaction(); /* At this point we intentionally decided to not use 2PC for reference tables */ if (MultiShardCommitProtocol == COMMIT_PROTOCOL_2PC) { CoordinatedTransactionUse2PC(); } foreach(shardIntervalCell, deletableShardIntervalList) { List *shardPlacementList = NIL; ListCell *shardPlacementCell = NULL; ShardInterval *shardInterval = (ShardInterval *) lfirst(shardIntervalCell); uint64 shardId = shardInterval->shardId; char *quotedShardName = NULL; char *shardRelationName = pstrdup(relationName); Assert(shardInterval->relationId == relationId); /* Build shard relation name. */ AppendShardIdToName(&shardRelationName, shardId); quotedShardName = quote_qualified_identifier(schemaName, shardRelationName); shardPlacementList = ShardPlacementList(shardId); foreach(shardPlacementCell, shardPlacementList) { ShardPlacement *shardPlacement = (ShardPlacement *) lfirst(shardPlacementCell); char *workerName = shardPlacement->nodeName; uint32 workerPort = shardPlacement->nodePort; StringInfo workerDropQuery = makeStringInfo(); MultiConnection *connection = NULL; uint32 connectionFlags = FOR_DDL; char storageType = shardInterval->storageType; if (storageType == SHARD_STORAGE_TABLE) { appendStringInfo(workerDropQuery, DROP_REGULAR_TABLE_COMMAND, quotedShardName); } else if (storageType == SHARD_STORAGE_COLUMNAR || storageType == SHARD_STORAGE_FOREIGN) { appendStringInfo(workerDropQuery, DROP_FOREIGN_TABLE_COMMAND, quotedShardName); } connection = GetPlacementConnection(connectionFlags, shardPlacement, NULL); RemoteTransactionBeginIfNecessary(connection); if (PQstatus(connection->pgConn) != CONNECTION_OK) { uint64 placementId = shardPlacement->placementId; ereport(WARNING, (errmsg("could not connect to shard \"%s\" on node " "\"%s:%u\"", shardRelationName, workerName, workerPort), errdetail("Marking this shard placement for " "deletion"))); UpdateShardPlacementState(placementId, FILE_TO_DELETE); continue; } MarkRemoteTransactionCritical(connection); ExecuteCriticalRemoteCommand(connection, workerDropQuery->data); DeleteShardPlacementRow(shardPlacement->placementId); }
/* * refresh_by_match_merge * * Refresh a materialized view with transactional semantics, while allowing * concurrent reads. * * This is called after a new version of the data has been created in a * temporary table. It performs a full outer join against the old version of * the data, producing "diff" results. This join cannot work if there are any * duplicated rows in either the old or new versions, in the sense that every * column would compare as equal between the two rows. It does work correctly * in the face of rows which have at least one NULL value, with all non-NULL * columns equal. The behavior of NULLs on equality tests and on UNIQUE * indexes turns out to be quite convenient here; the tests we need to make * are consistent with default behavior. If there is at least one UNIQUE * index on the materialized view, we have exactly the guarantee we need. By * joining based on equality on all columns which are part of any unique * index, we identify the rows on which we can use UPDATE without any problem. * If any column is NULL in either the old or new version of a row (or both), * we must use DELETE and INSERT, since there could be multiple rows which are * NOT DISTINCT FROM each other, and we could otherwise end up with the wrong * number of occurrences in the updated relation. The temporary table used to * hold the diff results contains just the TID of the old record (if matched) * and the ROW from the new table as a single column of complex record type * (if matched). * * Once we have the diff table, we perform set-based DELETE, UPDATE, and * INSERT operations against the materialized view, and discard both temporary * tables. * * Everything from the generation of the new data to applying the differences * takes place under cover of an ExclusiveLock, since it seems as though we * would want to prohibit not only concurrent REFRESH operations, but also * incremental maintenance. It also doesn't seem reasonable or safe to allow * SELECT FOR UPDATE or SELECT FOR SHARE on rows being updated or deleted by * this command. */ static void refresh_by_match_merge(Oid matviewOid, Oid tempOid) { StringInfoData querybuf; Relation matviewRel; Relation tempRel; char *matviewname; char *tempname; char *diffname; TupleDesc tupdesc; bool foundUniqueIndex; List *indexoidlist; ListCell *indexoidscan; int16 relnatts; bool *usedForQual; Oid save_userid; int save_sec_context; int save_nestlevel; initStringInfo(&querybuf); matviewRel = heap_open(matviewOid, NoLock); matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)), RelationGetRelationName(matviewRel)); tempRel = heap_open(tempOid, NoLock); tempname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(tempRel)), RelationGetRelationName(tempRel)); diffname = make_temptable_name_n(tempname, 2); relnatts = matviewRel->rd_rel->relnatts; usedForQual = (bool *) palloc0(sizeof(bool) * relnatts); /* Open SPI context. */ if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); /* Analyze the temp table with the new contents. */ appendStringInfo(&querybuf, "ANALYZE %s", tempname); if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY) elog(ERROR, "SPI_exec failed: %s", querybuf.data); /* * We need to ensure that there are not duplicate rows without NULLs in * the new data set before we can count on the "diff" results. Check for * that in a way that allows showing the first duplicated row found. Even * after we pass this test, a unique index on the materialized view may * find a duplicate key problem. */ resetStringInfo(&querybuf); appendStringInfo(&querybuf, "SELECT x FROM %s x WHERE x IS NOT NULL AND EXISTS " "(SELECT * FROM %s y WHERE y IS NOT NULL " "AND (y.*) = (x.*) AND y.ctid <> x.ctid) LIMIT 1", tempname, tempname); if (SPI_execute(querybuf.data, false, 1) != SPI_OK_SELECT) elog(ERROR, "SPI_exec failed: %s", querybuf.data); if (SPI_processed > 0) { ereport(ERROR, (errcode(ERRCODE_CARDINALITY_VIOLATION), errmsg("new data for \"%s\" contains duplicate rows without any NULL columns", RelationGetRelationName(matviewRel)), errdetail("Row: %s", SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1)))); } /* Start building the query for creating the diff table. */ resetStringInfo(&querybuf); appendStringInfo(&querybuf, "CREATE TEMP TABLE %s AS " "SELECT x.ctid AS tid, y FROM %s x FULL JOIN %s y ON (", diffname, matviewname, tempname); /* * Get the list of index OIDs for the table from the relcache, and look up * each one in the pg_index syscache. We will test for equality on all * columns present in all unique indexes which only reference columns and * include all rows. */ tupdesc = matviewRel->rd_att; foundUniqueIndex = false; indexoidlist = RelationGetIndexList(matviewRel); foreach(indexoidscan, indexoidlist) { Oid indexoid = lfirst_oid(indexoidscan); HeapTuple indexTuple; Form_pg_index index; indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid)); if (!HeapTupleIsValid(indexTuple)) /* should not happen */ elog(ERROR, "cache lookup failed for index %u", indexoid); index = (Form_pg_index) GETSTRUCT(indexTuple); /* We're only interested if it is unique and valid. */ if (index->indisunique && IndexIsValid(index)) { int numatts = index->indnatts; int i; bool expr = false; Relation indexRel; /* Skip any index on an expression. */ for (i = 0; i < numatts; i++) { if (index->indkey.values[i] == 0) { expr = true; break; } } if (expr) { ReleaseSysCache(indexTuple); continue; } /* Skip partial indexes. */ indexRel = index_open(index->indexrelid, RowExclusiveLock); if (RelationGetIndexPredicate(indexRel) != NIL) { index_close(indexRel, NoLock); ReleaseSysCache(indexTuple); continue; } /* Hold the locks, since we're about to run DML which needs them. */ index_close(indexRel, NoLock); /* Add quals for all columns from this index. */ for (i = 0; i < numatts; i++) { int attnum = index->indkey.values[i]; Oid type; Oid op; const char *colname; /* * Only include the column once regardless of how many times * it shows up in how many indexes. * * This is also useful later to omit columns which can not * have changed from the SET clause of the UPDATE statement. */ if (usedForQual[attnum - 1]) continue; usedForQual[attnum - 1] = true; /* * Actually add the qual, ANDed with any others. */ if (foundUniqueIndex) appendStringInfoString(&querybuf, " AND "); colname = quote_identifier(NameStr((tupdesc->attrs[attnum - 1])->attname)); appendStringInfo(&querybuf, "y.%s ", colname); type = attnumTypeId(matviewRel, attnum); op = lookup_type_cache(type, TYPECACHE_EQ_OPR)->eq_opr; mv_GenerateOper(&querybuf, op); appendStringInfo(&querybuf, " x.%s", colname); foundUniqueIndex = true; } } ReleaseSysCache(indexTuple); }
/* * sepgsql_proc_post_create * * This routine assigns a default security label on a newly defined * procedure. */ void sepgsql_proc_post_create(Oid functionId) { Relation rel; ScanKeyData skey; SysScanDesc sscan; HeapTuple tuple; char *nsp_name; char *scontext; char *tcontext; char *ncontext; uint32 required; int i; StringInfoData audit_name; ObjectAddress object; Form_pg_proc proForm; /* * Fetch namespace of the new procedure. Because pg_proc entry is not * visible right now, we need to scan the catalog using SnapshotSelf. */ rel = heap_open(ProcedureRelationId, AccessShareLock); ScanKeyInit(&skey, ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(functionId)); sscan = systable_beginscan(rel, ProcedureOidIndexId, true, SnapshotSelf, 1, &skey); tuple = systable_getnext(sscan); if (!HeapTupleIsValid(tuple)) elog(ERROR, "catalog lookup failed for proc %u", functionId); proForm = (Form_pg_proc) GETSTRUCT(tuple); /* * check db_schema:{add_name} permission of the namespace */ object.classId = NamespaceRelationId; object.objectId = proForm->pronamespace; object.objectSubId = 0; sepgsql_avc_check_perms(&object, SEPG_CLASS_DB_SCHEMA, SEPG_DB_SCHEMA__ADD_NAME, getObjectIdentity(&object), true); /* * XXX - db_language:{implement} also should be checked here */ /* * Compute a default security label when we create a new procedure object * under the specified namespace. */ scontext = sepgsql_get_client_label(); tcontext = sepgsql_get_label(NamespaceRelationId, proForm->pronamespace, 0); ncontext = sepgsql_compute_create(scontext, tcontext, SEPG_CLASS_DB_PROCEDURE, NameStr(proForm->proname)); /* * check db_procedure:{create (install)} permission */ initStringInfo(&audit_name); nsp_name = get_namespace_name(proForm->pronamespace); appendStringInfo(&audit_name, "%s(", quote_qualified_identifier(nsp_name, NameStr(proForm->proname))); for (i = 0; i < proForm->pronargs; i++) { if (i > 0) appendStringInfoChar(&audit_name, ','); object.classId = TypeRelationId; object.objectId = proForm->proargtypes.values[i]; object.objectSubId = 0; appendStringInfoString(&audit_name, getObjectIdentity(&object)); } appendStringInfoChar(&audit_name, ')'); required = SEPG_DB_PROCEDURE__CREATE; if (proForm->proleakproof) required |= SEPG_DB_PROCEDURE__INSTALL; sepgsql_avc_check_perms_label(ncontext, SEPG_CLASS_DB_PROCEDURE, required, audit_name.data, true); /* * Assign the default security label on a new procedure */ object.classId = ProcedureRelationId; object.objectId = functionId; object.objectSubId = 0; SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); /* * Cleanup */ systable_endscan(sscan); heap_close(rel, AccessShareLock); pfree(audit_name.data); pfree(tcontext); pfree(ncontext); }