Managing UDFs
Create, query, modify, and delete User-Defined Functions in Tacnode.
Creating Functions
Use CREATE FUNCTION to define a new UDF:
CREATE OR REPLACE FUNCTION add_numbers(a INTEGER, b INTEGER)
RETURNS INTEGER
AS $$
return a + b
$$ LANGUAGE plpython3u;
Key options:
OR REPLACE— Update the function if it already existsLANGUAGE— Chooseplpython3u,plpythonvec3u, orplpgsql
See the PL/Python guide or PL/pgSQL guide for language-specific details.
Listing Functions
Quick List with psql
\df -- List all functions
\df+ my_function -- Show function details including source code
Query All Custom Functions
SELECT
proname AS name,
pg_get_function_arguments(oid) AS arguments,
pg_get_function_result(oid) AS returns,
lanname AS language
FROM pg_proc p
JOIN pg_language l ON p.prolang = l.oid
WHERE pronamespace = 'public'::regnamespace
ORDER BY proname;
View Function Source Code
SELECT pg_get_functiondef(oid)
FROM pg_proc
WHERE proname = 'add_numbers';
Find All PL/Python Functions
SELECT
proname AS name,
pg_get_function_arguments(oid) AS arguments,
prosrc AS source
FROM pg_proc p
JOIN pg_language l ON p.prolang = l.oid
WHERE l.lanname = 'plpython3u'
ORDER BY proname;
Find Functions Using a Library
-- Find functions that import numpy
SELECT proname
FROM pg_proc
WHERE prosrc LIKE '%import numpy%'
OR prosrc LIKE '%from numpy%';
Dropping Functions
-- Drop a function (must specify parameter types)
DROP FUNCTION add_numbers(INTEGER, INTEGER);
-- Drop if exists (no error if function doesn't exist)
DROP FUNCTION IF EXISTS add_numbers(INTEGER, INTEGER);
When functions are overloaded (same name, different parameters), you must specify which one to drop:
-- Two functions with the same name
CREATE FUNCTION format_value(val TEXT) RETURNS TEXT ...
CREATE FUNCTION format_value(val INTEGER) RETURNS TEXT ...
-- Drop the TEXT version
DROP FUNCTION format_value(TEXT);
-- Drop the INTEGER version
DROP FUNCTION format_value(INTEGER);
Bulk Delete by Language
-- Delete all PL/Python functions in public schema
DO $$
DECLARE
func RECORD;
BEGIN
FOR func IN
SELECT proname, pg_get_function_identity_arguments(p.oid) AS args
FROM pg_proc p
JOIN pg_language l ON p.prolang = l.oid
WHERE l.lanname = 'plpython3u'
AND pronamespace = 'public'::regnamespace
LOOP
EXECUTE format('DROP FUNCTION %I(%s)', func.proname, func.args);
END LOOP;
END $$;
Modifying Functions
Update Function Body
Use CREATE OR REPLACE to update an existing function:
CREATE OR REPLACE FUNCTION add_numbers(a INTEGER, b INTEGER)
RETURNS INTEGER
AS $$
# Updated implementation
result = a + b
return result
$$ LANGUAGE plpython3u;
You can’t change a function’s return type or parameter types with CREATE OR REPLACE. Drop and recreate the function instead.
Change Owner
ALTER FUNCTION add_numbers(INTEGER, INTEGER) OWNER TO new_owner;
Add a Comment
COMMENT ON FUNCTION add_numbers(INTEGER, INTEGER)
IS 'Adds two integers and returns the sum';
Permissions
View Function Permissions
SELECT proname, proacl
FROM pg_proc
WHERE proname = 'add_numbers';
Grant Execute Permission
-- Grant to a user
GRANT EXECUTE ON FUNCTION add_numbers(INTEGER, INTEGER) TO alice;
-- Grant to a role
GRANT EXECUTE ON FUNCTION add_numbers(INTEGER, INTEGER) TO analysts;
-- Grant to everyone
GRANT EXECUTE ON FUNCTION add_numbers(INTEGER, INTEGER) TO PUBLIC;
Revoke Permission
REVOKE EXECUTE ON FUNCTION add_numbers(INTEGER, INTEGER) FROM alice;
Function Attributes
Volatility
Tell the query planner how your function behaves:
-- Can return different results each time (default)
CREATE FUNCTION get_random() RETURNS INTEGER
AS $$ import random; return random.randint(1, 100) $$
LANGUAGE plpython3u VOLATILE;
-- Same result within a single query
CREATE FUNCTION get_config(key TEXT) RETURNS TEXT
AS $$ ... $$
LANGUAGE plpython3u STABLE;
-- Always same result for same inputs
CREATE FUNCTION calculate_tax(amount NUMERIC) RETURNS NUMERIC
AS $$ return float(amount) * 0.08 $$
LANGUAGE plpython3u IMMUTABLE;
STRICT (Returns NULL on NULL Input)
Skip null-checking boilerplate:
CREATE FUNCTION double_it(n INTEGER)
RETURNS INTEGER
AS $$ return n * 2 $$
LANGUAGE plpython3u STRICT;
SELECT double_it(NULL); -- Returns NULL, function never executes
SECURITY DEFINER
Run with the permissions of the function owner instead of the caller:
CREATE FUNCTION admin_action()
RETURNS TEXT
AS $$
BEGIN
-- This runs with owner's permissions
RETURN 'done';
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Use SECURITY DEFINER carefully. It can inadvertently grant elevated privileges if not properly secured.