So after briefly looking into how views are stored, let’s do the same for functions. Hopefully, any references aren’t mutated whenever an ALTER statement is executed.

First, let’s create a function that grabs the same info we were trying to grab in the earlier post:

-- create a function to grab the detailed_message info
CREATE FUNCTION fn_detailed_message() RETURNS TABLE(
    id int,
    content varchar,
    sent timestamp,
    received timestamp,
    recipient_first_name varchar,
    recipient_last_name varchar,
    recipient_phone_number varchar
) AS $$
    SELECT message.id,
           content,
           sent,
           received,
           contact.first_name AS recipient_first_name,
           contact.last_name AS recipient_last_name,
           contact.phone_number AS recipient_phone_number
    FROM contact JOIN message ON message.recipient_id = contact.id;
$$ LANGUAGE SQL;

Arguably, there’s some hand-waving happening here with the RETURNS TABLE, etc. so here are the docs just in case. Otherwise, hopefully the above is pretty self-explanatory.

Now that we have a function set up, let’s check the definition using \df+ fn_detailed_message:

>> \df+ fn_detailed_message
...
Source code         |                                                                          
                    |     SELECT message.id,                                                   
                    |            content,                                                      
                    |            sent,                                                         
                    |            received,                                                     
                    |            contact.first_name AS recipient_first_name,                   
                    |            contact.last_name AS recipient_last_name,                     
                    |            contact.phone_number AS recipient_phone_number                
                    |     FROM contact JOIN message ON message.recipient_id = contact.id; 

Which looks just as we thought it would.

Now let’s try altering the name of the message table and then check the definition again:

>> ALTER TABLE message RENAME TO message_stale
ALTER TABLE

>> \df+ fn_detailed_message
...
Source code         |                                                                                                     
                    |     SELECT message.id,                                                                              
                    |            content,                                                                                 
                    |            sent,                                                                                    
                    |            received,                                                                                
                    |            contact.first_name AS recipient_first_name,                                              
                    |            contact.last_name AS recipient_last_name,                                                
                    |            contact.phone_number AS recipient_phone_number                                           
                    |     FROM contact JOIN message ON message.recipient_id = contact.id;   

And it looks the same! So we’ve found a significant difference here in how materialized/views and functions are stored. Just to make sure, let’s try querying the function to see what postgres tells us:

>> SELECT * FROM fn_detailed_message();
ERROR:  relation "message" does not exist
LINE 9:     FROM contact JOIN message ON message.recipient_id = cont...
                              ^
QUERY:  
    SELECT message.id,
           content,
           sent,
           received,
           contact.first_name AS recipient_first_name,
           contact.last_name AS recipient_last_name,
           contact.phone_number AS recipient_phone_number
    FROM contact JOIN message ON message.recipient_id = contact.id;

CONTEXT:  SQL function "fn_detailed_message" during startup

And expectedly, there is an error! This kind of behavior is what I was expecting when dealing with views, but I can understand the walls guarding views and their dependencies.

To sum things up, it is not possible to swap tables in postgres without altering existing dependencies using a view or materialized view. You can do so with a function though! Just remember to handle the edge case where that relation is missing.