Hi Daniel,

Thanks a lot for the detailed analysis and the suggestions, however I
am not sure marking the proeprty read as immuble is safe - because I
intend to use it as STABLE function - constant during execution of one
statement but might return different values in different
statement-invokations.

What puzzles me is that without ORing both conditions, postgres
behaves as expected.
When just filtering with WHERE current_setting('my.wfsuser', true)=
'admin' it correctly detects this is a static condition, and either
reads all rows or omits reading the rows alltogether.
for filtering just WHERE owner = current_setting('my.wfsuser', true)
it treats the current_setting invokation as stable and does a normal
index lookup just taking a few ms.
but when combining both conditions with an OR everything seems to fall
appart, and instead of an index lookup / seqscan I always get the
(slow) seqscan.

It seems like the check on the stable value of check
current_setting('my.wfsuser', true)= 'admin' will somehow make the
index lookup unusesable - but i have no idea why :/

Best regards, Clemens



Am Mi., 29. Jan. 2025 um 08:43 Uhr schrieb Daniel Blanch Bataller
<daniel.bla...@hoplasoftware.com>:
>
> Better said:
>
> current_setting() is STABLE
>
> If a function is IMMUTABLE it is run only once per statement and it can be 
> run during optimization phase, before it's planned or executed.
> If a function is STABLE it is run only once but after planning, during 
> execution.
> if a function is VOLATILE it is run for every row during executiong time.
>
> Said so i would use a function for what you want, A function using plpgsql 
> that returns all records if user is admin or returns just one record 
> otherwise.
>
> Cheers.
>
>
> El mar, 28 ene 2025 a las 23:26, Daniel Blanch Bataller 
> (<daniel.bla...@hoplasoftware.com>) escribió:
>>
>> I hope this gives you a clue of what it's going on:
>>
>> Functions can be marked as
>> INMUTABLE
>> STABLE
>> VOLATILE
>>
>> IMMUTABLE indicates that the function cannot modify the database and always 
>> returns the same result when given the same argument values; that is, it 
>> does not do database lookups or otherwise use information not directly 
>> present in its argument list. If this option is given, any call of the 
>> function with all-constant arguments can be immediately replaced with the 
>> function value.
>>
>> STABLE indicates that the function cannot modify the database, and that 
>> within a single table scan it will consistently return the same result for 
>> the same argument values, but that its result could change across SQL 
>> statements. This is the appropriate selection for functions whose results 
>> depend on database lookups, parameter variables (such as the current time 
>> zone), etc. (It is inappropriate for AFTER triggers that wish to query rows 
>> modified by the current command.) Also note that the current_timestamp 
>> family of functions qualify as stable, since their values do not change 
>> within a transaction.
>>
>> VOLATILE indicates that the function value can change even within a single 
>> table scan, so no optimizations can be made. Relatively few database 
>> functions are volatile in this sense; some examples are random(), currval(), 
>> timeofday(). But note that any function that has side-effects must be 
>> classified volatile, even if its result is quite predictable, to prevent 
>> calls from being optimized away; an example is setval().
>>
>>
>> random() for example is volatile. That is if you call to random() in a 
>> select, random is evaluated per each row.
>>
>>
>> test=# SELECT random() FROM generate_series(1, 3);
>>        random
>> ---------------------
>>  0.13773282234297923
>>   0.5954521821166239
>>  0.23865666511706607
>> (3 filas)
>>
>> Random is marked as volatile so it is executed for every row.
>>
>> test=# SELECT proname, provolatile
>> FROM pg_proc
>> WHERE proname = 'random';
>>  proname | provolatile
>> ---------+-------------
>>  random  | v
>>
>>
>> To make your function run only once, IMMUTABLE function type seems to be the 
>> type of that does the trick. (
>>
>> You can wrap current_setting in your own function and mark it as IMMUTABLE
>>
>> test=# CREATE OR REPLACE FUNCTION myfunction() RETURNS TEXT IMMUTABLE 
>> LANGUAGE sql AS $$ SELECT current_setting('my.username') $$;
>> CREATE FUNCTION
>>
>> Now if you use myfunction() it behaves as expected;
>>
>>
>> test=# SET my.username = 'admin';
>> SET
>> test=# EXPLAIN SELECT * FROM test WHERE id = 1 OR myfunction() = 'admin'; -- 
>> predicate is always true, all rows are evaluated
>>                        QUERY PLAN
>> ---------------------------------------------------------
>> Seq Scan on test  (cost=0.00..21.00 rows=1000 width=19)
>> (1 fila)
>>
>> test=# EXPLAIN SELECT * FROM test WHERE id = 1 OR myfunction() = 'other'; -- 
>> predicate is true only when id is 1
>>                               QUERY PLAN
>> -----------------------------------------------------------------------
>>  Index Scan using test_pkey on test  (cost=0.28..8.29 rows=1 width=19)
>>    Index Cond: (id = 1)
>> (2 filas)
>> (1 fila)
>>
>>
>> I hope this helps.
>>
>>
>> El mar, 28 ene 2025 a las 15:58, Clemens Eisserer (<linuxhi...@gmail.com>) 
>> escribió:
>>>
>>> Hello,
>>>
>>> Any idea what could cause postgresql (16.0) to fall back to a SeqScan
>>> when ORing a falsy one-time filter to a selection which would
>>> otherwise use an index scan?
>>>
>>> 1.) Without the false one-time condition, the query uses the existing
>>> index on owner to perform the lookup:
>>> select * from mytable where owner = current_setting('my.wfsuser', true);
>>> Bitmap Heap Scan on mytable  (cost=43.92..12523.30 rows=3548
>>> width=2341) (actual time=0.032..0.033 rows=0 loops=1)
>>>   Recheck Cond: ((owner)::text = current_setting('my.wfsuser'::text, true))
>>>   ->  Bitmap Index Scan on mytable_owner_idx  (cost=0.00..43.04
>>> rows=3548 width=0) (actual time=0.029..0.030 rows=0 loops=1)
>>>         Index Cond: ((owner)::text = current_setting('my.wfsuser'::text, 
>>> true))
>>> Planning Time: 0.221 ms
>>> Execution Time: 0.094 ms
>>>
>>> 2.) also a static condition resulting in a false value is correctly 
>>> recognized:
>>> select * from mytable  where current_setting('my.wfsuser'::text, true)
>>> = 'admin'::text;
>>> Result  (cost=0.01..158384.05 rows=709504 width=2341) (actual
>>> time=0.008..0.009 rows=0 loops=1)
>>>   One-Time Filter: (current_setting('my.wfsuser'::text, true) = 
>>> 'admin'::text)
>>>   ->  Seq Scan on mytable  (cost=0.01..158384.05 rows=709504
>>> width=2341) (never executed)
>>> Planning Time: 0.163 ms
>>> Execution Time: 0.068 ms
>>>
>>> 3.) Yet when both filters are combined with OR, postgresql executes a 
>>> SeqScan:
>>> select * from mytable where owner = current_setting('my.wfsuser',
>>> true) OR current_setting('my.wfsuser'::text, true) = 'admin'::text;
>>> Gather  (cost=1000.00..158909.23 rows=7077 width=2341) (actual
>>> time=2783.728..2786.520 rows=0 loops=1)
>>>   Workers Planned: 2
>>>   Workers Launched: 2
>>>   ->  Parallel Seq Scan on mytable  (cost=0.00..157201.53 rows=2949
>>> width=2341) (actual time=2744.147..2744.147 rows=0 loops=3)
>>>         Filter: (((owner)::text = current_setting('my.wfsuser'::text,
>>> true)) OR (current_setting('my.wfsuser'::text, true) = 'admin'::text))
>>>         Rows Removed by Filter: 236501
>>> Planning Time: 0.217 ms
>>> Execution Time: 2786.575 ms
>>>
>>> Thanks and best regards, Clemens
>>>
>>>


Reply via email to