/* * intorel_startup --- executor startup */ static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) { DR_intorel *myState = (DR_intorel *) self; IntoClause *into = myState->into; bool is_matview; char relkind; CreateStmt *create; Oid intoRelationId; Relation intoRelationDesc; RangeTblEntry *rte; Datum toast_options; ListCell *lc; int attnum; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Assert(into != NULL); /* else somebody forgot to set it */ /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); relkind = is_matview ? RELKIND_MATVIEW : RELKIND_RELATION; /* * Create the target relation by faking up a CREATE TABLE parsetree and * passing it to DefineRelation. */ create = makeNode(CreateStmt); create->relation = into->rel; create->tableElts = NIL; /* will fill below */ create->inhRelations = NIL; create->ofTypename = NULL; create->constraints = NIL; create->options = into->options; create->oncommit = into->onCommit; create->tablespacename = into->tableSpaceName; create->if_not_exists = false; /* * Build column definitions using "pre-cooked" type and collation info. If * a column name list was specified in CREATE TABLE AS, override the * column names derived from the query. (Too few column names are OK, too * many are not.) */ lc = list_head(into->colNames); for (attnum = 0; attnum < typeinfo->natts; attnum++) { Form_pg_attribute attribute = typeinfo->attrs[attnum]; ColumnDef *col = makeNode(ColumnDef); TypeName *coltype = makeNode(TypeName); if (lc) { col->colname = strVal(lfirst(lc)); lc = lnext(lc); } else col->colname = NameStr(attribute->attname); col->typeName = coltype; col->inhcount = 0; col->is_local = true; col->is_not_null = false; col->is_from_type = false; col->storage = 0; col->raw_default = NULL; col->cooked_default = NULL; col->collClause = NULL; col->collOid = attribute->attcollation; col->constraints = NIL; col->fdwoptions = NIL; coltype->names = NIL; coltype->typeOid = attribute->atttypid; coltype->setof = false; coltype->pct_type = false; coltype->typmods = NIL; coltype->typemod = attribute->atttypmod; coltype->arrayBounds = NIL; coltype->location = -1; /* * It's possible that the column is of a collatable type but the * collation could not be resolved, so double-check. (We must check * this here because DefineRelation would adopt the type's default * collation rather than complaining.) */ if (!OidIsValid(col->collOid) && type_is_collatable(coltype->typeOid)) ereport(ERROR, (errcode(ERRCODE_INDETERMINATE_COLLATION), errmsg("no collation was derived for column \"%s\" with collatable type %s", col->colname, format_type_be(coltype->typeOid)), errhint("Use the COLLATE clause to set the collation explicitly."))); create->tableElts = lappend(create->tableElts, col); } if (lc != NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("too many column names were specified"))); /* * Actually create the target table */ intoRelationId = DefineRelation(create, relkind, InvalidOid); /* * If necessary, create a TOAST table for the target table. Note that * AlterTableCreateToastTable ends with CommandCounterIncrement(), so that * the TOAST table will be visible for insertion. */ CommandCounterIncrement(); /* parse and validate reloptions for the toast table */ toast_options = transformRelOptions((Datum) 0, create->options, "toast", validnsps, true, false); (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); AlterTableCreateToastTable(intoRelationId, toast_options); /* Create the "view" part of a materialized view. */ if (is_matview) { /* StoreViewQuery scribbles on tree, so make a copy */ Query *query = (Query *) copyObject(into->viewQuery); StoreViewQuery(intoRelationId, query, false); CommandCounterIncrement(); } /* * Finally we can open the target table */ intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock); /* * Check INSERT permission on the constructed table. * * XXX: It would arguably make sense to skip this check if into->skipData * is true. */ rte = makeNode(RangeTblEntry); rte->rtekind = RTE_RELATION; rte->relid = intoRelationId; rte->relkind = relkind; rte->requiredPerms = ACL_INSERT; for (attnum = 1; attnum <= intoRelationDesc->rd_att->natts; attnum++) rte->modifiedCols = bms_add_member(rte->modifiedCols, attnum - FirstLowInvalidHeapAttributeNumber); ExecCheckRTPerms(list_make1(rte), true); /* * Tentatively mark the target as populated, if it's a matview and we're * going to fill it; otherwise, no change needed. */ if (is_matview && !into->skipData) SetMatViewPopulatedState(intoRelationDesc, true); /* * Fill private fields of myState for use by later routines */ myState->rel = intoRelationDesc; myState->output_cid = GetCurrentCommandId(true); /* * We can skip WAL-logging the insertions, unless PITR or streaming * replication is in use. We can skip the FSM in any case. */ myState->hi_options = HEAP_INSERT_SKIP_FSM | (XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL); myState->bistate = GetBulkInsertState(); /* Not using WAL requires smgr_targblock be initially invalid */ Assert(RelationGetTargetBlock(intoRelationDesc) == InvalidBlockNumber); }
/* * Given the result tuple descriptor for a function with OUT parameters, * replace any polymorphic columns (ANYELEMENT etc) with correct data types * deduced from the input arguments. Returns TRUE if able to deduce all types, * FALSE if not. */ static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, Node *call_expr) { int natts = tupdesc->natts; int nargs = declared_args->dim1; bool have_anyelement_result = false; bool have_anyarray_result = false; bool have_anynonarray = false; bool have_anyenum = false; Oid anyelement_type = InvalidOid; Oid anyarray_type = InvalidOid; int i; /* See if there are any polymorphic outputs; quick out if not */ for (i = 0; i < natts; i++) { switch (tupdesc->attrs[i]->atttypid) { case ANYELEMENTOID: have_anyelement_result = true; break; case ANYARRAYOID: have_anyarray_result = true; break; case ANYNONARRAYOID: have_anyelement_result = true; have_anynonarray = true; break; case ANYENUMOID: have_anyelement_result = true; have_anyenum = true; break; default: break; } } if (!have_anyelement_result && !have_anyarray_result) return true; /* * Otherwise, extract actual datatype(s) from input arguments. (We assume * the parser already validated consistency of the arguments.) */ if (!call_expr) return false; /* no hope */ for (i = 0; i < nargs; i++) { switch (declared_args->values[i]) { case ANYELEMENTOID: case ANYNONARRAYOID: case ANYENUMOID: if (!OidIsValid(anyelement_type)) anyelement_type = get_call_expr_argtype(call_expr, i); break; case ANYARRAYOID: if (!OidIsValid(anyarray_type)) anyarray_type = get_call_expr_argtype(call_expr, i); break; default: break; } } /* If nothing found, parser messed up */ if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type)) return false; /* If needed, deduce one polymorphic type from the other */ if (have_anyelement_result && !OidIsValid(anyelement_type)) anyelement_type = resolve_generic_type(ANYELEMENTOID, anyarray_type, ANYARRAYOID); if (have_anyarray_result && !OidIsValid(anyarray_type)) anyarray_type = resolve_generic_type(ANYARRAYOID, anyelement_type, ANYELEMENTOID); /* Enforce ANYNONARRAY if needed */ if (have_anynonarray && type_is_array(anyelement_type)) return false; /* Enforce ANYENUM if needed */ if (have_anyenum && !type_is_enum(anyelement_type)) return false; /* And finally replace the tuple column types as needed */ for (i = 0; i < natts; i++) { switch (tupdesc->attrs[i]->atttypid) { case ANYELEMENTOID: case ANYNONARRAYOID: case ANYENUMOID: TupleDescInitEntry(tupdesc, i + 1, NameStr(tupdesc->attrs[i]->attname), anyelement_type, -1, 0); break; case ANYARRAYOID: TupleDescInitEntry(tupdesc, i + 1, NameStr(tupdesc->attrs[i]->attname), anyarray_type, -1, 0); break; default: break; } } return true; }
/* -------------------------------- * InitPostgres * Initialize POSTGRES. * * The database can be specified by name, using the in_dbname parameter, or by * OID, using the dboid parameter. In the latter case, the actual database * name can be returned to the caller in out_dbname. If out_dbname isn't * NULL, it must point to a buffer of size NAMEDATALEN. * * In bootstrap mode no parameters are used. * * The return value indicates whether the userID is a superuser. (That * can only be tested inside a transaction, so we want to do it during * the startup transaction rather than doing a separate one in postgres.c.) * * As of PostgreSQL 8.2, we expect InitProcess() was already called, so we * already have a PGPROC struct ... but it's not filled in yet. * * Note: * Be very careful with the order of calls in the InitPostgres function. * -------------------------------- */ void InitPostgres(const char *in_dbname, Oid dboid, const char *username, char *out_dbname) { bool bootstrap = IsBootstrapProcessingMode(); bool autovacuum = IsAutoVacuumWorkerProcess(); bool am_superuser; char *fullpath; char dbname[NAMEDATALEN]; /* * Add my PGPROC struct to the ProcArray. * * Once I have done this, I am visible to other backends! */ InitProcessPhase2(); /* Initialize SessionState entry */ SessionState_Init(); /* Initialize memory protection */ GPMemoryProtect_Init(); #ifdef USE_ORCA /* Initialize GPOPT */ InitGPOPT(); #endif /* * Initialize my entry in the shared-invalidation manager's array of * per-backend data. * * Sets up MyBackendId, a unique backend identifier. */ MyBackendId = InvalidBackendId; SharedInvalBackendInit(false); if (MyBackendId > MaxBackends || MyBackendId <= 0) elog(FATAL, "bad backend id: %d", MyBackendId); /* Now that we have a BackendId, we can participate in ProcSignal */ ProcSignalInit(MyBackendId); /* * bufmgr needs another initialization call too */ InitBufferPoolBackend(); /* * Initialize local process's access to XLOG. In bootstrap case we may * skip this since StartupXLOG() was run instead. */ if (!bootstrap) InitXLOGAccess(); /* * Initialize the relation cache and the system catalog caches. Note that * no catalog access happens here; we only set up the hashtable structure. * We must do this before starting a transaction because transaction abort * would try to touch these hashtables. */ RelationCacheInitialize(); InitCatalogCache(); InitPlanCache(); /* Initialize portal manager */ EnablePortalManager(); /* Initialize stats collection --- must happen before first xact */ if (!bootstrap) pgstat_initialize(); /* * Load relcache entries for the shared system catalogs. This must create * at least entries for pg_database and catalogs used for authentication. */ RelationCacheInitializePhase2(); /* * Set up process-exit callback to do pre-shutdown cleanup. This has to * be after we've initialized all the low-level modules like the buffer * manager, because during shutdown this has to run before the low-level * modules start to close down. On the other hand, we want it in place * before we begin our first transaction --- if we fail during the * initialization transaction, as is entirely possible, we need the * AbortTransaction call to clean up. */ on_shmem_exit(ShutdownPostgres, 0); /* TODO: autovacuum launcher should be done here? */ /* * Start a new transaction here before first access to db, and get a * snapshot. We don't have a use for the snapshot itself, but we're * interested in the secondary effect that it sets RecentGlobalXmin. */ if (!bootstrap) { StartTransactionCommand(); (void) GetTransactionSnapshot(); } /* * Figure out our postgres user id, and see if we are a superuser. * * In standalone mode and in the autovacuum process, we use a fixed id, * otherwise we figure it out from the authenticated user name. */ if (bootstrap || autovacuum) { InitializeSessionUserIdStandalone(); am_superuser = true; } else if (!IsUnderPostmaster) { InitializeSessionUserIdStandalone(); am_superuser = true; if (!ThereIsAtLeastOneRole()) ereport(WARNING, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("no roles are defined in this database system"), errhint("You should immediately run CREATE USER \"%s\" CREATEUSER;.", username))); } else { /* normal multiuser case */ Assert(MyProcPort != NULL); PerformAuthentication(MyProcPort); InitializeSessionUserId(username); am_superuser = superuser(); } /* * Check a normal user hasn't connected to a superuser reserved slot. */ if (!am_superuser && ReservedBackends > 0 && !HaveNFreeProcs(ReservedBackends)) ereport(FATAL, (errcode(ERRCODE_TOO_MANY_CONNECTIONS), errmsg("connection limit exceeded for non-superusers"), errSendAlert(true))); /* * If walsender, we don't want to connect to any particular database. Just * finish the backend startup by processing any options from the startup * packet, and we're done. */ if (am_walsender) { Assert(!bootstrap); /* * We don't have replication role, which existed in postgres. */ if (!superuser()) ereport(FATAL, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser role to start walsender"))); /* process any options passed in the startup packet */ if (MyProcPort != NULL) process_startup_options(MyProcPort, am_superuser); /* Apply PostAuthDelay as soon as we've read all options */ if (PostAuthDelay > 0) pg_usleep(PostAuthDelay * 1000000L); /* initialize client encoding */ InitializeClientEncoding(); /* report this backend in the PgBackendStatus array */ pgstat_bestart(); /* close the transaction we started above */ CommitTransactionCommand(); return; } /* * Set up the global variables holding database id and path. But note we * won't actually try to touch the database just yet. * * We take a shortcut in the bootstrap case, otherwise we have to look up * the db name in pg_database. */ if (bootstrap) { MyDatabaseId = TemplateDbOid; MyDatabaseTableSpace = DEFAULTTABLESPACE_OID; } else if (in_dbname != NULL) { HeapTuple tuple; Form_pg_database dbform; tuple = GetDatabaseTuple(in_dbname); if (!HeapTupleIsValid(tuple)) ereport(FATAL, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" does not exist", in_dbname))); dbform = (Form_pg_database) GETSTRUCT(tuple); MyDatabaseId = HeapTupleGetOid(tuple); MyDatabaseTableSpace = dbform->dattablespace; /* take database name from the caller, just for paranoia */ strlcpy(dbname, in_dbname, sizeof(dbname)); pfree(tuple); } else { /* caller specified database by OID */ HeapTuple tuple; Form_pg_database dbform; tuple = GetDatabaseTupleByOid(dboid); if (!HeapTupleIsValid(tuple)) ereport(FATAL, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database %u does not exist", dboid))); dbform = (Form_pg_database) GETSTRUCT(tuple); MyDatabaseId = HeapTupleGetOid(tuple); MyDatabaseTableSpace = dbform->dattablespace; Assert(MyDatabaseId == dboid); strlcpy(dbname, NameStr(dbform->datname), sizeof(dbname)); /* pass the database name back to the caller */ if (out_dbname) strcpy(out_dbname, dbname); pfree(tuple); } /* Now we can mark our PGPROC entry with the database ID */ /* (We assume this is an atomic store so no lock is needed) */ MyProc->databaseId = MyDatabaseId; /* * Now, take a writer's lock on the database we are trying to connect to. * If there is a concurrently running DROP DATABASE on that database, this * will block us until it finishes (and has committed its update of * pg_database). * * Note that the lock is not held long, only until the end of this startup * transaction. This is OK since we are already advertising our use of * the database in the PGPROC array; anyone trying a DROP DATABASE after * this point will see us there. * * Note: use of RowExclusiveLock here is reasonable because we envision * our session as being a concurrent writer of the database. If we had a * way of declaring a session as being guaranteed-read-only, we could use * AccessShareLock for such sessions and thereby not conflict against * CREATE DATABASE. */ if (!bootstrap) LockSharedObject(DatabaseRelationId, MyDatabaseId, 0, RowExclusiveLock); /* * Recheck pg_database to make sure the target database hasn't gone away. * If there was a concurrent DROP DATABASE, this ensures we will die * cleanly without creating a mess. */ if (!bootstrap) { HeapTuple tuple; tuple = GetDatabaseTuple(dbname); if (!HeapTupleIsValid(tuple) || MyDatabaseId != HeapTupleGetOid(tuple) || MyDatabaseTableSpace != ((Form_pg_database) GETSTRUCT(tuple))->dattablespace) ereport(FATAL, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" does not exist", dbname), errdetail("It seems to have just been dropped or renamed."))); } fullpath = GetDatabasePath(MyDatabaseId, MyDatabaseTableSpace); if (!bootstrap) { if (access(fullpath, F_OK) == -1) { if (errno == ENOENT) ereport(FATAL, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" does not exist", dbname), errdetail("The database subdirectory \"%s\" is missing.", fullpath))); else ereport(FATAL, (errcode_for_file_access(), errmsg("could not access directory \"%s\": %m", fullpath))); } ValidatePgVersion(fullpath); } SetDatabasePath(fullpath); /* * It's now possible to do real access to the system catalogs. * * Load relcache entries for the system catalogs. This must create at * least the minimum set of "nailed-in" cache entries. */ RelationCacheInitializePhase3(); /* * Now we have full access to catalog including toast tables, * we can process pg_authid.rolconfig. This ought to come before * processing startup options so that it can override the settings. */ if (!bootstrap) ProcessRoleGUC(); /* set up ACL framework (so CheckMyDatabase can check permissions) */ initialize_acl(); /* * Re-read the pg_database row for our database, check permissions and set * up database-specific GUC settings. We can't do this until all the * database-access infrastructure is up. (Also, it wants to know if the * user is a superuser, so the above stuff has to happen first.) */ if (!bootstrap) CheckMyDatabase(dbname, am_superuser); /* * Now process any command-line switches and any additional GUC variable * settings passed in the startup packet. We couldn't do this before * because we didn't know if client is a superuser. */ if (MyProcPort != NULL) process_startup_options(MyProcPort, am_superuser); /* * Maintenance Mode: allow superuser to connect when * gp_maintenance_conn GUC is set. We cannot check it until * process_startup_options parses the GUC. */ if (gp_maintenance_mode && Gp_role == GP_ROLE_DISPATCH && !(superuser() && gp_maintenance_conn)) ereport(FATAL, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("maintenance mode: connected by superuser only"), errSendAlert(false))); /* * MPP: If we were started in utility mode then we only want to allow * incoming sessions that specify gp_session_role=utility as well. This * lets the bash scripts start the QD in utility mode and connect in but * protect ourselves from normal clients who might be trying to connect to * the system while we startup. */ if ((Gp_role == GP_ROLE_UTILITY) && (Gp_session_role != GP_ROLE_UTILITY)) { ereport(FATAL, (errcode(ERRCODE_CANNOT_CONNECT_NOW), errmsg("System was started in master-only utility mode - only utility mode connections are allowed"))); } /* Apply PostAuthDelay as soon as we've read all options */ if (PostAuthDelay > 0) pg_usleep(PostAuthDelay * 1000000L); /* set default namespace search path */ InitializeSearchPath(); /* initialize client encoding */ InitializeClientEncoding(); /* report this backend in the PgBackendStatus array */ if (!bootstrap) pgstat_bestart(); /* * MPP package setup * * Primary function is to establish connctions to the qExecs. * This is SKIPPED when the database is in bootstrap mode or * Is not UnderPostmaster. */ if (!bootstrap && IsUnderPostmaster) { cdb_setup(); on_proc_exit( cdb_cleanup, 0 ); } /* * MPP SharedSnapshot Setup */ if (Gp_role == GP_ROLE_DISPATCH) { addSharedSnapshot("Query Dispatcher", gp_session_id); } else if (Gp_role == GP_ROLE_DISPATCHAGENT) { SharedLocalSnapshotSlot = NULL; } else if (Gp_segment == -1 && Gp_role == GP_ROLE_EXECUTE && !Gp_is_writer) { /* * Entry db singleton QE is a user of the shared snapshot -- not a creator. * The lookup will occur once the distributed snapshot has been received. */ lookupSharedSnapshot("Entry DB Singleton", "Query Dispatcher", gp_session_id); } else if (Gp_role == GP_ROLE_EXECUTE) { if (Gp_is_writer) { addSharedSnapshot("Writer qExec", gp_session_id); } else { /* * NOTE: This assumes that the Slot has already been * allocated by the writer. Need to make sure we * always allocate the writer qExec first. */ lookupSharedSnapshot("Reader qExec", "Writer qExec", gp_session_id); } } /* close the transaction we started above */ if (!bootstrap) CommitTransactionCommand(); return; }
/* * 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 = 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; }
/* * format_operator - converts operator OID to "opr_name(args)" * * This exports the useful functionality of regoperatorout for use * in other backend modules. The result is a palloc'd string. */ char * format_operator(Oid operator_oid) { char *result; HeapTuple opertup; opertup = SearchSysCache(OPEROID, ObjectIdGetDatum(operator_oid), 0, 0, 0); if (HeapTupleIsValid(opertup)) { Form_pg_operator operform = (Form_pg_operator) GETSTRUCT(opertup); char *oprname = NameStr(operform->oprname); char *nspname; StringInfoData buf; /* XXX no support here for bootstrap mode */ initStringInfo(&buf); /* * Would this oper be found (given the right args) by regoperatorin? * If not, we need to qualify it. */ if (!OperatorIsVisible(operator_oid)) { nspname = get_namespace_name(operform->oprnamespace); appendStringInfo(&buf, "%s.", quote_identifier(nspname)); } appendStringInfo(&buf, "%s(", oprname); if (operform->oprleft) appendStringInfo(&buf, "%s,", format_type_be(operform->oprleft)); else appendStringInfo(&buf, "NONE,"); if (operform->oprright) appendStringInfo(&buf, "%s)", format_type_be(operform->oprright)); else appendStringInfo(&buf, "NONE)"); result = buf.data; ReleaseSysCache(opertup); } else { /* * If OID doesn't match any pg_operator entry, return it numerically */ result = (char *) palloc(NAMEDATALEN); snprintf(result, NAMEDATALEN, "%u", operator_oid); } return result; }
/* * Compare two TupleDesc structures for logical equality * * Note: we deliberately do not check the attrelid and tdtypmod fields. * This allows typcache.c to use this routine to see if a cached record type * matches a requested type, and is harmless for relcache.c's uses. * We don't compare tdrefcount, either. */ bool equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2, bool strict) { int i, j, n; if (tupdesc1->natts != tupdesc2->natts) return false; if (strict && tupdesc1->tdtypeid != tupdesc2->tdtypeid) return false; if (tupdesc1->tdhasoid != tupdesc2->tdhasoid) return false; for (i = 0; i < tupdesc1->natts; i++) { Form_pg_attribute attr1 = tupdesc1->attrs[i]; Form_pg_attribute attr2 = tupdesc2->attrs[i]; /* * We do not need to check every single field here: we can disregard * attrelid and attnum (which were used to place the row in the attrs * array in the first place). It might look like we could dispense * with checking attlen/attbyval/attalign, since these are derived * from atttypid; but in the case of dropped columns we must check * them (since atttypid will be zero for all dropped columns) and in * general it seems safer to check them always. * * attcacheoff must NOT be checked since it's possibly not set in both * copies. */ if (strcmp(NameStr(attr1->attname), NameStr(attr2->attname)) != 0) return false; if (attr1->atttypid != attr2->atttypid) return false; if (attr1->attstattarget != attr2->attstattarget) return false; if (attr1->attlen != attr2->attlen) return false; if (attr1->attndims != attr2->attndims) return false; if (attr1->atttypmod != attr2->atttypmod) return false; if (attr1->attbyval != attr2->attbyval) return false; if (attr1->attstorage != attr2->attstorage) return false; if (attr1->attalign != attr2->attalign) return false; if (strict) { if (attr1->attnotnull != attr2->attnotnull) return false; if (attr1->atthasdef != attr2->atthasdef) return false; if (attr1->attisdropped != attr2->attisdropped) return false; if (attr1->attislocal != attr2->attislocal) return false; if (attr1->attinhcount != attr2->attinhcount) return false; } } if (!strict) return true; if (tupdesc1->constr != NULL) { TupleConstr *constr1 = tupdesc1->constr; TupleConstr *constr2 = tupdesc2->constr; if (constr2 == NULL) return false; if (constr1->has_not_null != constr2->has_not_null) return false; n = constr1->num_defval; if (n != (int) constr2->num_defval) return false; for (i = 0; i < n; i++) { AttrDefault *defval1 = constr1->defval + i; AttrDefault *defval2 = constr2->defval; /* * We can't assume that the items are always read from the system * catalogs in the same order; so use the adnum field to identify * the matching item to compare. */ for (j = 0; j < n; defval2++, j++) { if (defval1->adnum == defval2->adnum) break; } if (j >= n) return false; if (strcmp(defval1->adbin, defval2->adbin) != 0) return false; } n = constr1->num_check; if (n != (int) constr2->num_check) return false; for (i = 0; i < n; i++) { ConstrCheck *check1 = constr1->check + i; ConstrCheck *check2 = constr2->check; /* * Similarly, don't assume that the checks are always read in the * same order; match them up by name and contents. (The name * *should* be unique, but...) */ for (j = 0; j < n; check2++, j++) { if (strcmp(check1->ccname, check2->ccname) == 0 && strcmp(check1->ccbin, check2->ccbin) == 0) break; } if (j >= n) return false; } } else if (tupdesc2->constr != NULL) return false; return true; }
/* * sepgsql_attribute_post_create * * This routine assigns a default security label on a newly defined * column, using ALTER TABLE ... ADD COLUMN. * Note that this routine is not invoked in the case of CREATE TABLE, * although it also defines columns in addition to table. */ void sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum) { Relation rel; ScanKeyData skey[2]; SysScanDesc sscan; HeapTuple tuple; char *scontext; char *tcontext; char *ncontext; ObjectAddress object; Form_pg_attribute attForm; StringInfoData audit_name; /* * Only attributes within regular relation have individual security * labels. */ if (get_rel_relkind(relOid) != RELKIND_RELATION) return; /* * Compute a default security label of the new column underlying the * specified relation, and check permission to create it. */ rel = heap_open(AttributeRelationId, AccessShareLock); ScanKeyInit(&skey[0], Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relOid)); ScanKeyInit(&skey[1], Anum_pg_attribute_attnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(attnum)); sscan = systable_beginscan(rel, AttributeRelidNumIndexId, true, SnapshotSelf, 2, &skey[0]); tuple = systable_getnext(sscan); if (!HeapTupleIsValid(tuple)) elog(ERROR, "catalog lookup failed for column %d of relation %u", attnum, relOid); attForm = (Form_pg_attribute) GETSTRUCT(tuple); scontext = sepgsql_get_client_label(); tcontext = sepgsql_get_label(RelationRelationId, relOid, 0); ncontext = sepgsql_compute_create(scontext, tcontext, SEPG_CLASS_DB_COLUMN, NameStr(attForm->attname)); /* * check db_column:{create} permission */ object.classId = RelationRelationId; object.objectId = relOid; object.objectSubId = 0; initStringInfo(&audit_name); appendStringInfo(&audit_name, "%s.%s", getObjectIdentity(&object), quote_identifier(NameStr(attForm->attname))); sepgsql_avc_check_perms_label(ncontext, SEPG_CLASS_DB_COLUMN, SEPG_DB_COLUMN__CREATE, audit_name.data, true); /* * Assign the default security label on a new procedure */ object.classId = RelationRelationId; object.objectId = relOid; object.objectSubId = attnum; SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); systable_endscan(sscan); heap_close(rel, AccessShareLock); pfree(tcontext); pfree(ncontext); }
/* * 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); }
/* * 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; }
/* * sepgsql_relation_post_create * * The post creation hook of relation/attribute */ void sepgsql_relation_post_create(Oid relOid) { Relation rel; ScanKeyData skey; SysScanDesc sscan; HeapTuple tuple; Form_pg_class classForm; ObjectAddress object; uint16 tclass; char *scontext; /* subject */ char *tcontext; /* schema */ char *rcontext; /* relation */ char *ccontext; /* column */ char *nsp_name; StringInfoData audit_name; /* * Fetch catalog record of the new relation. Because pg_class entry is not * visible right now, we need to scan the catalog using SnapshotSelf. */ rel = heap_open(RelationRelationId, AccessShareLock); ScanKeyInit(&skey, ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relOid)); sscan = systable_beginscan(rel, ClassOidIndexId, true, SnapshotSelf, 1, &skey); tuple = systable_getnext(sscan); if (!HeapTupleIsValid(tuple)) elog(ERROR, "catalog lookup failed for relation %u", relOid); classForm = (Form_pg_class) GETSTRUCT(tuple); /* ignore indexes on toast tables */ if (classForm->relkind == RELKIND_INDEX && classForm->relnamespace == PG_TOAST_NAMESPACE) goto out; /* * check db_schema:{add_name} permission of the namespace */ object.classId = NamespaceRelationId; object.objectId = classForm->relnamespace; object.objectSubId = 0; sepgsql_avc_check_perms(&object, SEPG_CLASS_DB_SCHEMA, SEPG_DB_SCHEMA__ADD_NAME, getObjectIdentity(&object), true); switch (classForm->relkind) { 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; no need for tclass */ sepgsql_index_modify(relOid); goto out; default: /* ignore other relkinds */ goto out; } /* * Compute a default security label when we create a new relation object * under the specified namespace. */ scontext = sepgsql_get_client_label(); tcontext = sepgsql_get_label(NamespaceRelationId, classForm->relnamespace, 0); rcontext = sepgsql_compute_create(scontext, tcontext, tclass, NameStr(classForm->relname)); /* * check db_xxx:{create} permission */ nsp_name = get_namespace_name(classForm->relnamespace); initStringInfo(&audit_name); appendStringInfo(&audit_name, "%s.%s", quote_identifier(nsp_name), quote_identifier(NameStr(classForm->relname))); sepgsql_avc_check_perms_label(rcontext, tclass, SEPG_DB_DATABASE__CREATE, audit_name.data, true); /* * Assign the default security label on the new relation */ object.classId = RelationRelationId; object.objectId = relOid; object.objectSubId = 0; SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, rcontext); /* * We also assigns a default security label on columns of the new regular * tables. */ if (classForm->relkind == RELKIND_RELATION) { Relation arel; ScanKeyData akey; SysScanDesc ascan; HeapTuple atup; Form_pg_attribute attForm; arel = heap_open(AttributeRelationId, AccessShareLock); ScanKeyInit(&akey, Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relOid)); ascan = systable_beginscan(arel, AttributeRelidNumIndexId, true, SnapshotSelf, 1, &akey); while (HeapTupleIsValid(atup = systable_getnext(ascan))) { attForm = (Form_pg_attribute) GETSTRUCT(atup); resetStringInfo(&audit_name); appendStringInfo(&audit_name, "%s.%s.%s", quote_identifier(nsp_name), quote_identifier(NameStr(classForm->relname)), quote_identifier(NameStr(attForm->attname))); ccontext = sepgsql_compute_create(scontext, rcontext, SEPG_CLASS_DB_COLUMN, NameStr(attForm->attname)); /* * check db_column:{create} permission */ sepgsql_avc_check_perms_label(ccontext, SEPG_CLASS_DB_COLUMN, SEPG_DB_COLUMN__CREATE, audit_name.data, true); object.classId = RelationRelationId; object.objectId = relOid; object.objectSubId = attForm->attnum; SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ccontext); pfree(ccontext); } systable_endscan(ascan); heap_close(arel, AccessShareLock); } pfree(rcontext); out: systable_endscan(sscan); heap_close(rel, AccessShareLock); }
/* * Internal handler function */ Datum handler_internal(Oid function_oid, FunctionCallInfo fcinfo, bool execute) { HeapTuple proctuple; Form_pg_proc pg_proc_entry; const char * sourcecode; const char * rest; char *tempfile; int i; int argc; char * arguments[FUNC_MAX_ARGS + 2]; char * ret; HeapTuple returntuple = NULL; Datum prosrcdatum; bool isnull; proctuple = SearchSysCache(PROCOID, ObjectIdGetDatum(function_oid), 0, 0, 0); if (!HeapTupleIsValid(proctuple)) elog(ERROR, "cache lookup failed for function %u", function_oid); prosrcdatum = SysCacheGetAttr(PROCOID, proctuple, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "null prosrc"); sourcecode = DatumGetCString(DirectFunctionCall1(textout, prosrcdatum)); parse_shell_and_arguments(sourcecode, &argc, arguments, &rest); /* validation stops here */ if (!execute) { ReleaseSysCache(proctuple); PG_RETURN_VOID(); } tempfile = write_to_tempfile(rest); arguments[argc++] = tempfile; /* evaluate arguments */ pg_proc_entry = (Form_pg_proc) GETSTRUCT(proctuple); if (CALLED_AS_TRIGGER(fcinfo)) { TriggerData *trigdata = (TriggerData *) fcinfo->context; Trigger *trigger = trigdata->tg_trigger; TupleDesc tupdesc = trigdata->tg_relation->rd_att; HeapTuple oldtuple = trigdata->tg_trigtuple; /* first the CREATE TRIGGER fixed arguments */ for (i = 0; i < trigger->tgnargs; i++) { arguments[argc++] = trigger->tgargs[i]; } if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) for (i = 0; i < tupdesc->natts; i++) { char * s; bool isnull; Datum attr; attr = heap_getattr(oldtuple, i + 1, tupdesc, &isnull); if (isnull) s = ""; else s = type_to_cstring(attr, tupdesc->attrs[i]->atttypid); elog(DEBUG2, "arg %d is \"%s\" (type %u)", i, s, tupdesc->attrs[i]->atttypid); arguments[argc++] = s; } /* since we can't alter the tuple anyway, set up a return tuple right now */ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) returntuple = trigdata->tg_trigtuple; else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) returntuple = trigdata->tg_trigtuple; else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) returntuple = trigdata->tg_newtuple; #ifdef TRIGGER_FIRED_BY_TRUNCATE else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event)) returntuple = trigdata->tg_trigtuple; #endif else elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE"); } else if (CALLED_AS_EVENT_TRIGGER(fcinfo)) { /* nothing */ } else /* not trigger */ { for (i = 0; i < pg_proc_entry->pronargs; i++) { char * s; if (PG_ARGISNULL(i)) s = ""; else s = type_to_cstring(PG_GETARG_DATUM(i), pg_proc_entry->proargtypes.values[i]); elog(DEBUG2, "arg %d is \"%s\"", i, s); arguments[argc++] = s; } } /* terminate list */ arguments[argc] = NULL; ret = handler_internal2(tempfile, arguments, NameStr(pg_proc_entry->proname), CALLED_AS_TRIGGER(fcinfo) ? (TriggerData *) fcinfo->context : NULL, CALLED_AS_EVENT_TRIGGER(fcinfo) ? (EventTriggerData *) fcinfo->context : NULL); ReleaseSysCache(proctuple); if (CALLED_AS_TRIGGER(fcinfo)) { PG_RETURN_DATUM(PointerGetDatum(returntuple)); } else if (CALLED_AS_EVENT_TRIGGER(fcinfo)) { PG_RETURN_NULL(); } else { if (ret) PG_RETURN_DATUM(cstring_to_type(ret, pg_proc_entry->prorettype)); else PG_RETURN_NULL(); } }
/* * Add an attribute to the hash calculation. * **IMPORTANT: any new hard coded support for a data type in here * must be added to isGreenplumDbHashable() below! * * Note that the caller should provide the base type if the datum is * of a domain type. It is quite expensive to call get_typtype() and * getBaseType() here since this function gets called a lot for the * same set of Datums. * * @param hashFn called to update the hash value. * @param clientData passed to hashFn. */ void hashDatum(Datum datum, Oid type, datumHashFunction hashFn, void *clientData) { void *buf = NULL; /* pointer to the data */ size_t len = 0; /* length for the data buffer */ int64 intbuf; /* an 8 byte buffer for all integer sizes */ float4 buf_f4; float8 buf_f8; Timestamp tsbuf; /* timestamp data dype is either a double or * int8 (determined in compile time) */ TimestampTz tstzbuf; DateADT datebuf; TimeADT timebuf; TimeTzADT *timetzptr; Interval *intervalptr; AbsoluteTime abstime_buf; RelativeTime reltime_buf; TimeInterval tinterval; AbsoluteTime tinterval_len; Numeric num; bool bool_buf; char char_buf; Name namebuf; ArrayType *arrbuf; inet *inetptr; /* inet/cidr */ unsigned char inet_hkey[sizeof(inet_struct)]; macaddr *macptr; /* MAC address */ VarBit *vbitptr; int2vector *i2vec_buf; oidvector *oidvec_buf; Cash cash_buf; AclItem *aclitem_ptr; uint32 aclitem_buf; /* * special case buffers */ uint32 nanbuf; uint32 invalidbuf; void *tofree = NULL; /* * Select the hash to be performed according to the field type we are adding to the * hash. */ switch (type) { /* * ======= NUMERIC TYPES ======== */ case INT2OID: /* -32 thousand to 32 thousand, 2-byte storage */ intbuf = (int64) DatumGetInt16(datum); /* cast to 8 byte before * hashing */ buf = &intbuf; len = sizeof(intbuf); break; case INT4OID: /* -2 billion to 2 billion integer, 4-byte * storage */ intbuf = (int64) DatumGetInt32(datum); /* cast to 8 byte before * hashing */ buf = &intbuf; len = sizeof(intbuf); break; case INT8OID: /* ~18 digit integer, 8-byte storage */ intbuf = DatumGetInt64(datum); /* cast to 8 byte before * hashing */ buf = &intbuf; len = sizeof(intbuf); break; case FLOAT4OID: /* single-precision floating point number, * 4-byte storage */ buf_f4 = DatumGetFloat4(datum); /* * On IEEE-float machines, minus zero and zero have different bit * patterns but should compare as equal. We must ensure that they * have the same hash value, which is most easily done this way: */ if (buf_f4 == (float4) 0) buf_f4 = 0.0; buf = &buf_f4; len = sizeof(buf_f4); break; case FLOAT8OID: /* double-precision floating point number, * 8-byte storage */ buf_f8 = DatumGetFloat8(datum); /* * On IEEE-float machines, minus zero and zero have different bit * patterns but should compare as equal. We must ensure that they * have the same hash value, which is most easily done this way: */ if (buf_f8 == (float8) 0) buf_f8 = 0.0; buf = &buf_f8; len = sizeof(buf_f8); break; case NUMERICOID: num = DatumGetNumeric(datum); if (NUMERIC_IS_NAN(num)) { nanbuf = NAN_VAL; buf = &nanbuf; len = sizeof(nanbuf); } else /* not a nan */ { buf = num->n_data; len = (VARSIZE(num) - NUMERIC_HDRSZ); } /* * If we did a pg_detoast_datum, we need to remember to pfree, * or we will leak memory. Because of the 1-byte varlena header stuff. */ if (num != DatumGetPointer(datum)) tofree = num; break; /* * ====== CHARACTER TYPES ======= */ case CHAROID: /* char(1), single character */ char_buf = DatumGetChar(datum); buf = &char_buf; len = 1; break; case BPCHAROID: /* char(n), blank-padded string, fixed storage */ case TEXTOID: /* text */ case VARCHAROID: /* varchar */ case BYTEAOID: /* bytea */ { int tmplen; varattrib_untoast_ptr_len(datum, (char **) &buf, &tmplen, &tofree); /* adjust length to not include trailing blanks */ if (type != BYTEAOID && tmplen > 1) tmplen = ignoreblanks((char *) buf, tmplen); len = tmplen; break; } case NAMEOID: namebuf = DatumGetName(datum); len = NAMEDATALEN; buf = NameStr(*namebuf); /* adjust length to not include trailing blanks */ if (len > 1) len = ignoreblanks((char *) buf, len); break; /* * ====== OBJECT IDENTIFIER TYPES ====== */ case OIDOID: /* object identifier(oid), maximum 4 billion */ case REGPROCOID: /* function name */ case REGPROCEDUREOID: /* function name with argument types */ case REGOPEROID: /* operator name */ case REGOPERATOROID: /* operator with argument types */ case REGCLASSOID: /* relation name */ case REGTYPEOID: /* data type name */ intbuf = (int64) DatumGetUInt32(datum); /* cast to 8 byte before hashing */ buf = &intbuf; len = sizeof(intbuf); break; case TIDOID: /* tuple id (6 bytes) */ buf = DatumGetPointer(datum); len = SizeOfIptrData; break; /* * ====== DATE/TIME TYPES ====== */ case TIMESTAMPOID: /* date and time */ tsbuf = DatumGetTimestamp(datum); buf = &tsbuf; len = sizeof(tsbuf); break; case TIMESTAMPTZOID: /* date and time with time zone */ tstzbuf = DatumGetTimestampTz(datum); buf = &tstzbuf; len = sizeof(tstzbuf); break; case DATEOID: /* ANSI SQL date */ datebuf = DatumGetDateADT(datum); buf = &datebuf; len = sizeof(datebuf); break; case TIMEOID: /* hh:mm:ss, ANSI SQL time */ timebuf = DatumGetTimeADT(datum); buf = &timebuf; len = sizeof(timebuf); break; case TIMETZOID: /* time with time zone */ /* * will not compare to TIMEOID on equal values. * Postgres never attempts to compare the two as well. */ timetzptr = DatumGetTimeTzADTP(datum); buf = (unsigned char *) timetzptr; /* * Specify hash length as sizeof(double) + sizeof(int4), not as * sizeof(TimeTzADT), so that any garbage pad bytes in the structure * won't be included in the hash! */ len = sizeof(timetzptr->time) + sizeof(timetzptr->zone); break; case INTERVALOID: /* @ <number> <units>, time interval */ intervalptr = DatumGetIntervalP(datum); buf = (unsigned char *) intervalptr; /* * Specify hash length as sizeof(double) + sizeof(int4), not as * sizeof(Interval), so that any garbage pad bytes in the structure * won't be included in the hash! */ len = sizeof(intervalptr->time) + sizeof(intervalptr->month); break; case ABSTIMEOID: abstime_buf = DatumGetAbsoluteTime(datum); if (abstime_buf == INVALID_ABSTIME) { /* hash to a constant value */ invalidbuf = INVALID_VAL; len = sizeof(invalidbuf); buf = &invalidbuf; } else { len = sizeof(abstime_buf); buf = &abstime_buf; } break; case RELTIMEOID: reltime_buf = DatumGetRelativeTime(datum); if (reltime_buf == INVALID_RELTIME) { /* hash to a constant value */ invalidbuf = INVALID_VAL; len = sizeof(invalidbuf); buf = &invalidbuf; } else { len = sizeof(reltime_buf); buf = &reltime_buf; } break; case TINTERVALOID: tinterval = DatumGetTimeInterval(datum); /* * check if a valid interval. the '0' status code * stands for T_INTERVAL_INVAL which is defined in * nabstime.c. We use the actual value instead * of defining it again here. */ if(tinterval->status == 0 || tinterval->data[0] == INVALID_ABSTIME || tinterval->data[1] == INVALID_ABSTIME) { /* hash to a constant value */ invalidbuf = INVALID_VAL; len = sizeof(invalidbuf); buf = &invalidbuf; } else { /* normalize on length of the time interval */ tinterval_len = tinterval->data[1] - tinterval->data[0]; len = sizeof(tinterval_len); buf = &tinterval_len; } break; /* * ======= NETWORK TYPES ======== */ case INETOID: case CIDROID: inetptr = DatumGetInetP(datum); len = inet_getkey(inetptr, inet_hkey, sizeof(inet_hkey)); /* fill-in inet_key & get len */ buf = inet_hkey; break; case MACADDROID: macptr = DatumGetMacaddrP(datum); len = sizeof(macaddr); buf = (unsigned char *) macptr; break; /* * ======== BIT STRINGS ======== */ case BITOID: case VARBITOID: /* * Note that these are essentially strings. * we don't need to worry about '10' and '010' * to compare, b/c they will not, by design. * (see SQL standard, and varbit.c) */ vbitptr = DatumGetVarBitP(datum); len = VARBITBYTES(vbitptr); buf = (char *) VARBITS(vbitptr); break; /* * ======= other types ======= */ case BOOLOID: /* boolean, 'true'/'false' */ bool_buf = DatumGetBool(datum); buf = &bool_buf; len = sizeof(bool_buf); break; /* * We prepare the hash key for aclitems just like postgresql does. * (see code and comment in acl.c: hash_aclitem() ). */ case ACLITEMOID: aclitem_ptr = DatumGetAclItemP(datum); aclitem_buf = (uint32) (aclitem_ptr->ai_privs + aclitem_ptr->ai_grantee + aclitem_ptr->ai_grantor); buf = &aclitem_buf; len = sizeof(aclitem_buf); break; /* * ANYARRAY is a pseudo-type. We use it to include * any of the array types (OIDs 1007-1033 in pg_type.h). * caller needs to be sure the type is ANYARRAYOID * before calling cdbhash on an array (INSERT and COPY do so). */ case ANYARRAYOID: arrbuf = DatumGetArrayTypeP(datum); len = VARSIZE(arrbuf) - VARHDRSZ; buf = VARDATA(arrbuf); break; case INT2VECTOROID: i2vec_buf = (int2vector *) DatumGetPointer(datum); len = i2vec_buf->dim1 * sizeof(int2); buf = (void *)i2vec_buf->values; break; case OIDVECTOROID: oidvec_buf = (oidvector *) DatumGetPointer(datum); len = oidvec_buf->dim1 * sizeof(Oid); buf = oidvec_buf->values; break; case CASHOID: /* cash is stored in int32 internally */ cash_buf = (* (Cash *)DatumGetPointer(datum)); len = sizeof(Cash); buf = &cash_buf; break; default: ereport(ERROR, (errcode(ERRCODE_CDB_FEATURE_NOT_YET), errmsg("Type %u is not hashable.", type))); } /* switch(type) */ /* do the hash using the selected algorithm */ hashFn(clientData, buf, len); if(tofree) pfree(tofree); }
/* * 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. */ }
int namecmp(Name n1, Name n2) { return strncmp(NameStr(*n1), NameStr(*n2), NAMEDATALEN); }
/* * 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 = SearchSysCache(OPEROID, ObjectIdGetDatum(oprid), 0, 0, 0); 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); }
/* * Build a tuple from the given args and kw using the TupleDesc to * create an appropriately ordered PyTupleObject of arguments and * keywords. * * The output of this can then be casted as needed. */ PyObj PyTuple_FromTupleDescAndParameters(TupleDesc td, PyObj args, PyObj kw) { PyObj rob; Py_ssize_t args_len, kw_len; Assert(td != NULL); Assert(args != NULL); /* * Python allows NULL kw parameters to be given, * so compensate below. * * Note that abstract interfaces are being used: * args can be any sequence and kw can be any mapping. */ args_len = PyObject_Length(args); kw_len = kw != NULL ? PyObject_Length(kw) : 0; if (args_len == -1 || kw_len == -1) return(NULL); if ((args_len + kw_len) != td->natts) { PyErr_Format(PyExc_TypeError, "requires exactly %d arguments, given %d", td->natts, (args_len + kw_len)); return(NULL); } rob = PyTuple_New(td->natts); /* * There are a few ways this could be implemented, * but it seems the most reasonable is to first set * any available keywords and fill in the gaps with * the positional args. */ if (kw_len > 0) { PyObj ob_key, kw_iter; kw_iter = PyObject_GetIter(kw); if (kw_iter == NULL) { Py_DECREF(rob); return(NULL); } while ((ob_key = PyIter_Next(kw_iter)) != NULL) { PyObj ob, ob_str; char *obstr; int i; ob_str = ob_key; Py_INCREF(ob_str); PyObject_StrBytes(&ob_str); if (ob_str == NULL) { Py_DECREF(kw_iter); Py_DECREF(rob); return(NULL); } obstr = PyBytes_AS_STRING(ob_str); /* * Scan TupleDesc for attribute number. O(NM) :( */ for (i = 0; i < td->natts; ++i) { if (!strcmp(NameStr(td->attrs[i]->attname), obstr)) break; } Py_DECREF(ob_str); /* * No such attribute. */ if (i == td->natts) { PyObj invalid_kw_param; Py_DECREF(rob); Py_DECREF(kw_iter); invalid_kw_param = PyUnicode_FromFormat( "invalid keyword parameter %R", ob_key); if (invalid_kw_param != NULL) { PyErr_SetObject(PyExc_TypeError, invalid_kw_param); Py_DECREF(invalid_kw_param); } Py_DECREF(ob_key); return(NULL); } ob = PyObject_GetItem(kw, ob_key); Py_DECREF(ob_key); PyTuple_SET_ITEM(rob, i, ob); } } if (args_len > 0) { int i, ai; for (i = 0, ai = 0; i < td->natts && ai < args_len; ++i) { PyObj ob; if (PyTuple_GET_ITEM(rob, i) != NULL) continue; ob = PySequence_GetItem(args, ai); if (ob == NULL) { Py_DECREF(rob); return(NULL); } PyTuple_SET_ITEM(rob, i, ob); ++ai; } } return(rob); }
/* * SetDefine - converts query string defining set to an oid * * We create an SQL function having the given querystring as its body. * The name of the function is then changed to use the OID of its tuple * in pg_proc. */ Oid SetDefine(char *querystr, Oid elemType) { Oid setoid; char *procname = GENERICSETNAME; char *fileName = "-"; char realprocname[NAMEDATALEN]; HeapTuple tup, newtup = NULL; Form_pg_proc proc; Relation procrel; int i; Datum replValue[Natts_pg_proc]; char replNull[Natts_pg_proc]; char repl[Natts_pg_proc]; setoid = ProcedureCreate(procname, /* changed below, after oid known */ PG_CATALOG_NAMESPACE, /* XXX wrong */ false, /* don't replace */ true, /* returnsSet */ elemType, /* returnType */ SQLlanguageId, /* language */ F_FMGR_SQL_VALIDATOR, querystr, /* prosrc */ fileName, /* probin */ false, /* not aggregate */ false, /* security invoker */ false, /* isStrict (irrelevant, no args) */ PROVOLATILE_VOLATILE, /* assume unsafe */ 0, /* parameterCount */ NULL); /* parameterTypes */ /* * Since we're still inside this command of the transaction, we can't * see the results of the procedure definition unless we pretend we've * started the next command. (Postgres's solution to the Halloween * problem is to not allow you to see the results of your command * until you start the next command.) */ CommandCounterIncrement(); procrel = heap_openr(ProcedureRelationName, RowExclusiveLock); tup = SearchSysCache(PROCOID, ObjectIdGetDatum(setoid), 0, 0, 0); if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for function %u", setoid); /* * We can tell whether the set was already defined by checking the * name. If it's GENERICSETNAME, the set is new. If it's "set<some * oid>" it's already defined. */ proc = (Form_pg_proc) GETSTRUCT(tup); if (strcmp(procname, NameStr(proc->proname)) == 0) { /* make the real proc name */ snprintf(realprocname, sizeof(realprocname), "set%u", setoid); /* set up the attributes to be modified or kept the same */ repl[0] = 'r'; for (i = 1; i < Natts_pg_proc; i++) repl[i] = ' '; replValue[0] = (Datum) realprocname; for (i = 1; i < Natts_pg_proc; i++) replValue[i] = (Datum) 0; for (i = 0; i < Natts_pg_proc; i++) replNull[i] = ' '; /* change the pg_proc tuple */ newtup = heap_modifytuple(tup, procrel, replValue, replNull, repl); simple_heap_update(procrel, &newtup->t_self, newtup); setoid = HeapTupleGetOid(newtup); CatalogUpdateIndexes(procrel, newtup); heap_freetuple(newtup); } ReleaseSysCache(tup); heap_close(procrel, RowExclusiveLock); return setoid; }
/* * 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); 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); }
/* * 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. */ 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; cqContext *pcqCtx; /* * 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; pcqCtx = caql_beginscan( NULL, cql("SELECT * FROM pg_type " " WHERE oid = :1 ", ObjectIdGetDatum(oidtypeid))); tuple = caql_getnext(pcqCtx); 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; caql_endscan(pcqCtx); }
/* * 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; }
/* * sepgsql_schema_post_create * * This routine assigns a default security label on a newly defined * schema. */ void sepgsql_schema_post_create(Oid namespaceId) { Relation rel; ScanKeyData skey; SysScanDesc sscan; HeapTuple tuple; char *tcontext; char *ncontext; const char *nsp_name; ObjectAddress object; Form_pg_namespace nspForm; StringInfoData audit_name; /* * Compute a default security label when we create a new schema object * under the working database. * * XXX - uncoming version of libselinux supports to take object name to * handle special treatment on default security label; such as special * label on "pg_temp" schema. */ rel = heap_open(NamespaceRelationId, AccessShareLock); ScanKeyInit(&skey, ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(namespaceId)); sscan = systable_beginscan(rel, NamespaceOidIndexId, true, SnapshotSelf, 1, &skey); tuple = systable_getnext(sscan); if (!HeapTupleIsValid(tuple)) elog(ERROR, "catalog lookup failed for namespace %u", namespaceId); nspForm = (Form_pg_namespace) GETSTRUCT(tuple); nsp_name = NameStr(nspForm->nspname); if (strncmp(nsp_name, "pg_temp_", 8) == 0) nsp_name = "pg_temp"; else if (strncmp(nsp_name, "pg_toast_temp_", 14) == 0) nsp_name = "pg_toast_temp"; tcontext = sepgsql_get_label(DatabaseRelationId, MyDatabaseId, 0); ncontext = sepgsql_compute_create(sepgsql_get_client_label(), tcontext, SEPG_CLASS_DB_SCHEMA, nsp_name); /* * check db_schema:{create} */ initStringInfo(&audit_name); appendStringInfo(&audit_name, "%s", quote_identifier(nsp_name)); sepgsql_avc_check_perms_label(ncontext, SEPG_CLASS_DB_SCHEMA, SEPG_DB_SCHEMA__CREATE, audit_name.data, true); systable_endscan(sscan); heap_close(rel, AccessShareLock); /* * Assign the default security label on a new procedure */ object.classId = NamespaceRelationId; object.objectId = namespaceId; object.objectSubId = 0; SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); pfree(ncontext); pfree(tcontext); }
/* * format_operator - converts operator OID to "opr_name(args)" * * This exports the useful functionality of regoperatorout for use * in other backend modules. The result is a palloc'd string. */ char * format_operator(Oid operator_oid) { char *result; HeapTuple opertup; cqContext *pcqCtx; pcqCtx = caql_beginscan( NULL, cql("SELECT * FROM pg_operator " " WHERE oid = :1 ", ObjectIdGetDatum(operator_oid))); opertup = caql_getnext(pcqCtx); /* XXX XXX select oprname, oprnamespace from pg_operator */ if (HeapTupleIsValid(opertup)) { Form_pg_operator operform = (Form_pg_operator) GETSTRUCT(opertup); char *oprname = NameStr(operform->oprname); char *nspname; StringInfoData buf; /* XXX no support here for bootstrap mode */ initStringInfo(&buf); /* * Would this oper be found (given the right args) by regoperatorin? * If not, we need to qualify it. */ if (!OperatorIsVisible(operator_oid)) { nspname = get_namespace_name(operform->oprnamespace); appendStringInfo(&buf, "%s.", quote_identifier(nspname)); } appendStringInfo(&buf, "%s(", oprname); if (operform->oprleft) appendStringInfo(&buf, "%s,", format_type_be(operform->oprleft)); else appendStringInfo(&buf, "NONE,"); if (operform->oprright) appendStringInfo(&buf, "%s)", format_type_be(operform->oprright)); else appendStringInfo(&buf, "NONE)"); result = buf.data; } else { /* * If OID doesn't match any pg_operator entry, return it numerically */ result = (char *) palloc(NAMEDATALEN); snprintf(result, NAMEDATALEN, "%u", operator_oid); } caql_endscan(pcqCtx); 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 = SearchSysCache(PROCOID, ObjectIdGetDatum(funcid), 0, 0, 0); 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 (!OidIsValid(newrettype)) /* 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; }
/* * 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); }
/* * 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; /* Fetch our pg_database row normally, via syscache */ tup = SearchSysCache(DATABASEOID, ObjectIdGetDatum(MyDatabaseId), 0, 0, 0); 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 the 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); /* Use the right encoding in translated messages */ #ifdef ENABLE_NLS pg_bind_textdomain_codeset(textdomain(NULL)); #endif /* * Lastly, set up any database-specific configuration variables. */ if (IsUnderPostmaster) { Datum datum; bool isnull; datum = SysCacheGetAttr(DATABASEOID, tup, Anum_pg_database_datconfig, &isnull); if (!isnull) { ArrayType *a = DatumGetArrayTypeP(datum); /* * We process all the options at SUSET level. We assume that the * right to insert an option into pg_database was checked when it * was inserted. */ ProcessGUCArray(a, PGC_SUSET, PGC_S_DATABASE, GUC_ACTION_SET); } } ReleaseSysCache(tup); }
int namecat(Name n1, Name n2) { return namestrcat(n1, NameStr(*n2)); /* n2 can't be any longer than * n1 */ }
/* * Turn a composite / record into JSON. */ static void composite_to_json(Datum composite, StringInfo result, bool use_line_feeds) { HeapTupleHeader td; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tmptup, *tuple; int i; bool needsep = false; const char *sep; sep = use_line_feeds ? ",\n " : ","; td = DatumGetHeapTupleHeader(composite); /* Extract rowtype info and find a tupdesc */ tupType = HeapTupleHeaderGetTypeId(td); tupTypmod = HeapTupleHeaderGetTypMod(td); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); /* Build a temporary HeapTuple control structure */ tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; tuple = &tmptup; appendStringInfoChar(result, '{'); for (i = 0; i < tupdesc->natts; i++) { Datum val; bool isnull; char *attname; TYPCATEGORY tcategory; Oid typoutput; bool typisvarlena; Oid castfunc = InvalidOid; if (tupdesc->attrs[i]->attisdropped) continue; if (needsep) appendStringInfoString(result, sep); needsep = true; attname = NameStr(tupdesc->attrs[i]->attname); escape_json(result, attname); appendStringInfoChar(result, ':'); val = heap_getattr(tuple, i + 1, tupdesc, &isnull); getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &typoutput, &typisvarlena); if (tupdesc->attrs[i]->atttypid > FirstNormalObjectId) { HeapTuple cast_tuple; Form_pg_cast castForm; cast_tuple = SearchSysCache2(CASTSOURCETARGET, ObjectIdGetDatum(tupdesc->attrs[i]->atttypid), ObjectIdGetDatum(JSONOID)); if (HeapTupleIsValid(cast_tuple)) { castForm = (Form_pg_cast) GETSTRUCT(cast_tuple); if (castForm->castmethod == COERCION_METHOD_FUNCTION) castfunc = typoutput = castForm->castfunc; ReleaseSysCache(cast_tuple); } } if (castfunc != InvalidOid) tcategory = TYPCATEGORY_JSON_CAST; else if (tupdesc->attrs[i]->atttypid == RECORDARRAYOID) tcategory = TYPCATEGORY_ARRAY; else if (tupdesc->attrs[i]->atttypid == RECORDOID) tcategory = TYPCATEGORY_COMPOSITE; else if (tupdesc->attrs[i]->atttypid == JSONOID) tcategory = TYPCATEGORY_JSON; else tcategory = TypeCategory(tupdesc->attrs[i]->atttypid); datum_to_json(val, isnull, result, tcategory, typoutput); } appendStringInfoChar(result, '}'); ReleaseTupleDesc(tupdesc); }
/* * Permanently drop the currently acquired replication slot which will be * released by the point this function returns. */ static void ReplicationSlotDropAcquired(void) { char path[MAXPGPATH]; char tmppath[MAXPGPATH]; ReplicationSlot *slot = MyReplicationSlot; Assert(MyReplicationSlot != NULL); /* slot isn't acquired anymore */ MyReplicationSlot = NULL; /* * If some other backend ran this code concurrently with us, we might try * to delete a slot with a certain name while someone else was trying to * create a slot with the same name. */ LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE); /* Generate pathnames. */ sprintf(path, "pg_replslot/%s", NameStr(slot->data.name)); sprintf(tmppath, "pg_replslot/%s.tmp", NameStr(slot->data.name)); /* * Rename the slot directory on disk, so that we'll no longer recognize * this as a valid slot. Note that if this fails, we've got to mark the * slot inactive before bailing out. If we're dropping a ephemeral slot, * we better never fail hard as the caller won't expect the slot to * survive and this might get called during error handling. */ if (rename(path, tmppath) == 0) { /* * We need to fsync() the directory we just renamed and its parent to * make sure that our changes are on disk in a crash-safe fashion. If * fsync() fails, we can't be sure whether the changes are on disk or * not. For now, we handle that by panicking; * StartupReplicationSlots() will try to straighten it out after * restart. */ START_CRIT_SECTION(); fsync_fname(tmppath, true); fsync_fname("pg_replslot", true); END_CRIT_SECTION(); } else { volatile ReplicationSlot *vslot = slot; bool fail_softly = slot->data.persistency == RS_EPHEMERAL; SpinLockAcquire(&slot->mutex); vslot->active = false; SpinLockRelease(&slot->mutex); ereport(fail_softly ? WARNING : ERROR, (errcode_for_file_access(), errmsg("could not rename file \"%s\" to \"%s\": %m", path, tmppath))); } /* * The slot is definitely gone. Lock out concurrent scans of the array * long enough to kill it. It's OK to clear the active flag here without * grabbing the mutex because nobody else can be scanning the array here, * and nobody can be attached to this slot and thus access it without * scanning the array. */ LWLockAcquire(ReplicationSlotControlLock, LW_EXCLUSIVE); slot->active = false; slot->in_use = false; LWLockRelease(ReplicationSlotControlLock); /* * Slot is dead and doesn't prevent resource removal anymore, recompute * limits. */ ReplicationSlotsComputeRequiredXmin(false); ReplicationSlotsComputeRequiredLSN(); /* * If removing the directory fails, the worst thing that will happen is * that the user won't be able to create a new slot with the same name * until the next server restart. We warn about it, but that's all. */ if (!rmtree(tmppath, true)) ereport(WARNING, (errcode_for_file_access(), errmsg("could not remove directory \"%s\"", tmppath))); /* * We release this at the very end, so that nobody starts trying to create * a slot while we're still cleaning up the detritus of the old one. */ LWLockRelease(ReplicationSlotAllocationLock); }