Reputation: 795
What happens to an index built on a function when I change the underlying function?
Say, I have a function clean_name()
defined as:
CREATE OR REPLACE FUNCTION clean_name(n text)
RETURNS TEXT AS
$BODY$
DECLARE
rec TEXT;
BEGIN
EXECUTE
'SELECT Regexp_replace(' || quote_literal(n) || ', ''[a-z]'', '''', ''g'');'
INTO rec;
RETURN rec;
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE
;
Then create an index:
CREATE INDEX my_table_upper_name_btree
ON schema.my_table USING GIST (my_text_field);
But then I decide to redefine the function to remove upper case letters instead. What happens to the index I created? Does it change on its own? Do I DROP
and CREATE
again? VACUUM [ANALYZE] [FULL]
?
(The function in question is similar, using instead a rather lengthy series of replacements that are still being tweaked but expected to be stable.)
Upvotes: 9
Views: 3516
Reputation: 1677
I was surprised to find out that Postgres relies so much on good behavior from it's administrators to prevent alterations from happening to a function that is ("was") used by an index. I actually think this is extremely problematic. There is nothing stopping wrong-doing and I consider that to be a design issue rather than a user-error.
So I decided to make a handy little system to do this sort of enforcement using event triggers (and a tiny bit of regex hacking). I think it holds water pretty well, but I have yet to test it in any high pressure way.
Using these:
CREATE OR REPLACE FUNCTION pg_index_monitor()
RETURNS event_trigger LANGUAGE plpgsql AS $$
DECLARE
obj record;
BEGIN
FOR obj IN (
WITH
index_functions AS
(
select
unnest(regexp_matches(indexprs, '(?<=(funcid(\s)))(\d+)', 'g'))::oid as objid
, indexrelid
FROM pg_index
WHERE
indexprs IS NOT NULL
)
SELECT
*
FROM pg_event_trigger_ddl_commands()
JOIN index_functions USING (objid)
JOIN pg_class ON indexrelid = oid
where
object_type = 'function'
)
LOOP
RAISE EXCEPTION 'This function cannot be modified as it is being used by index "%".', obj.relname;
END LOOP;
END
$$
;
DROP EVENT TRIGGER IF EXISTS pg_index_monitor_trigger;
CREATE EVENT TRIGGER pg_index_monitor_trigger
ON ddl_command_end
EXECUTE FUNCTION pg_index_monitor();
Then if I have an index (old or new) that has any reliance on a function being updated, it will throw an error.
CREATE INDEX some_index ON some_table (test(some_column));
followed by any attempt at editing that linked function:
CREATE OR REPLACE FUNCTION test(text) RETURNS text AS $$
select 'abc'::text;
$$
LANGUAGE sql
IMMUTABLE
;
[P0001] ERROR: This function cannot be modified as it is being used by index "some_index".
Upvotes: 0
Reputation: 125444
If you change the function you will have to rebuild the index.
create table t (i integer);
insert into t (i)
select generate_series(1, 100000);
analyze t;
A simple function to return the opposite integer:
create or replace function f(i integer)
returns integer as $$
select i * -1;
$$ immutable language sql;
And the index on it:
create index t_i_index on t(f(i));
The index is used:
explain select * from t order by f(i);
QUERY PLAN
---------------------------------------------------------------------------
Index Scan using t_i_index on t (cost=0.00..3300.26 rows=100000 width=4)
Now the function is changed to return the integer itself:
create or replace function f(i integer)
returns integer as $$
select i;
$$ immutable language sql;
And the index is not used anymore:
explain select * from t order by f(i);
QUERY PLAN
---------------------------------------------------------------
Sort (cost=11116.32..11366.32 rows=100000 width=4)
Sort Key: i
-> Seq Scan on t (cost=0.00..1443.00 rows=100000 width=4)
If the index is rebuilt
reindex index t_i_index;
It is used again:
explain select * from t order by f(i);
QUERY PLAN
---------------------------------------------------------------------------
Index Scan using t_i_index on t (cost=0.00..4376.26 rows=100000 width=4)
Upvotes: 4
Reputation: 324841
IMMUTABLE
means "does not change" or "unchangeable". What you must do to strictly avoid violating that rule is drop the function and everything that depends on it then re-create it and the indexes that use it.
If you replace the function in-place you're taking responsibility for the consequences. Personally I think PostgreSQL should disallow OR REPLACE
for IMMUTABLE
functions for this reason, forcing you to jump through an extra hoop like setting ignore_immutable_checks_even_though_it_might_cause_incorrect_queries
configuration option.
If you change an immutable function's behaviour then indexes based on the function are invalid. The server can't tell if the function's behaviour has changed or not; you might just have replaced it with an optimized version that has identical behaviour in every respect. So it won't invalidate the indexes for you, though perhaps it should, since if your function's behaviour does differ you can get incorrect query results for queries based on the function.
Upvotes: 6