You need dynamic SQL as has been suggested.
But when passing anything but values (including object names like the function name in your example), you need to defend against SQL injection!
CREATE OR REPLACE PROCEDURE safe_proc(func text)
LANGUAGE plpgsql AS
$proc$
BEGIN
DROP TABLE IF EXISTS pg_temp.foo; -- ② also safer
EXECUTE format( -- ③
'CREATE TEMP TABLE foo AS
SELECT %I(bar.column_a) AS func_column_a FROM bar', $1); -- ① !!
END
$proc$;
① format() with the %I specifier double-quotes identifiers automatically where needed.
Else, a malicious user might do something like this:
CALL unsafe_proc(' 1; DELETE FROM bar; -- ');
Whoops! See what happens:
db<>fiddle here
Further reading:
While being at it, you can also customize the result column name safely:
EXECUTE format('... SELECT %I(column_a) AS %I ...', $1, concat($1, _column_a);
② To be safe, also schema-qualify the temp table when dropping. Since you are not sure it exists, the first plain table of that name in the schema search path would get it. (The temporary schema is first in the search path by default.) See:
③ Another side effect of using format(): It works with passing a NULL value. The result is that bar.column_a gets used as is. Parentheses around a single value ((bar.column_a)) are stripped as noise. You may or may not want to allow this as "no-op" function.
When concatenating with ||, the result of concatenating a NULL is NULL, and EXECUTE will raise an exception:
ERROR: query string argument of EXECUTE is null
Unlike functions, procedures cannot be defined as STRICT.