út 11. 1. 2022 v 10:54 odesílatel <benj....@laposte.net> napsal:

> Hi,
>
> I would like to submit a problem (bug ?) that I encountered while handling
> temporary tables in plpgsql functions.
>
> First, if I create a TABLE and a TEMP TABLE with the same name, and I
> request without specified the schema, the temporary table is used.
>
> -- SHOW search_path; -- => "$user", public
> -- Exectute next commands in the same transaction
> /* Début Transaction 1 */
> DROP TABLE IF EXISTS public.myexemple;
> DROP TABLE IF EXISTS pg_temp.myexemple;
> CREATE TABLE IF NOT EXISTS myexemple(i INT);
> CREATE TEMP TABLE IF NOT EXISTS myexemple(i INT) ON COMMIT DROP;
> INSERT INTO public.myexemple VALUES(1);
> INSERT INTO public.myexemple VALUES(2);
> INSERT INTO public.myexemple VALUES(3);
> INSERT INTO myexemple VALUES(7);
> SELECT 'public', * FROM public.myexemple
>  UNION ALL SELECT 'noschema', * FROM myexemple
>  UNION ALL SELECT 'pg_temp', * FROM pg_temp.myexemple;
> /* Fin Transaction 1 */
> =>
> public;1
> public;2
> public;3
> noschema;7
> pg_temp;7
>
> Here, all is fine It's the expected behavior.
>
> But If I create the the TEMP TABLE in a function, it's (sometimes) the
> public table which is used and not the temporary table.
>
> CREATE OR REPLACE FUNCTION test_search_path_v1(tmp boolean)
>     RETURNS TABLE(ori text, i int)
>     LANGUAGE plpgsql
>     AS $_$
> DECLARE
>     txt text;
> BEGIN
>   CREATE TEMP TABLE IF NOT EXISTS return_table(ori TEXT, i INT) ON COMMIT
> DROP;
>
>   IF $1 THEN
>       CREATE TEMP TABLE IF NOT EXISTS my_table(i INT) ON COMMIT DROP;
>   ELSE
>       CREATE TABLE IF NOT EXISTS my_table(i INT);
>   END IF;
>
>   SELECT setting FROM pg_settings WHERE name = 'search_path' INTO txt;
>   RAISE INFO 'search_path = %', txt;
>
>   INSERT INTO my_table VALUES((random() * 100)::INT);
>
>   FOR txt IN SELECT schemaname FROM pg_tables WHERE tablename = 'my_table'
> LOOP
>       RAISE INFO '==> %', txt;
>   END LOOP;
>
>   IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname ='public' AND
> tablename = 'my_table') THEN
>       RAISE INFO 'public.my_table exists';
>       INSERT INTO return_table SELECT 'public', t.i FROM public.my_table t;
>   END IF;
>
>   IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname LIKE 'pg_temp%' AND
> tablename = 'my_table') THEN
>       RAISE INFO 'pg_temp.my_table exists';
>       INSERT INTO return_table SELECT 'pg_temp', t.i FROM pg_temp.my_table
> t;
>   END IF;
>
>   INSERT INTO return_table SELECT '', t.i FROM my_table t;
>   RETURN QUERY SELECT t.ori, t.i FROM return_table t;
> END;
> $_$;
>
> SHOW search_path -- => "$user", public;
> DROP TABLE IF EXISTS my_table;
> -- Executing each row on differents transactions but in the same session
> /*Session A - Transaction 1*/ SELECT * FROM test_search_path_v1(true);  --
> => OK takes table from pg_temp (no existing table in public)
> /*Session A - Transaction 2*/ SELECT * FROM test_search_path_v1(false); --
> => OK takes table from public
> /*Session A - Transaction 3*/ SELECT * FROM test_search_path_v1(true);  --
> => OK takes table from pg_temp (and the existing from public)
> /*Session A - Transaction 4*/ SELECT * FROM test_search_path_v1(false); --
> => OK takes table from public
> /*Session A - Transaction 5*/ SELECT * FROM test_search_path_v1(true);  --
> => NOK => it takes public and not pg_temp
>
>
I cannot reproduce any fails in this test.


> In fact, if I call 0 or 1 time "test_search_path_v1(false)" all the call
> with "true" will be ok.
> But AFTER the second call with false, all subsequent call with true will
> failed
>

What is error message?

Regards

Pavel


> => using public instead of pg_temp for the INSERT INTO my_table
> VALUES((random() * 100)::INT)
>
> If I do the test with changing session before exh call, the problem
> doesn't appear
> -- Executing each row on differents session
> /*Session A */ SELECT * FROM test_search_path_v1(true);  -- => OK takes
> table from pg_temp (no existing table in public)
> /*Session B */ SELECT * FROM test_search_path_v1(false); -- => OK takes
> table from public
> /*Session C */ SELECT * FROM test_search_path_v1(true);  -- => OK takes
> table from pg_temp (and the existing from public)
> /*Session D */ SELECT * FROM test_search_path_v1(false); -- => OK takes
> table from public
> /*Session E */ SELECT * FROM test_search_path_v1(true);  -- => OK takes
> table from pg_temp (and the existing from public)
>
>
> It's possible to bypass te problem with enforce the use of pg_temp like in
> this second version.
>
> CREATE OR REPLACE FUNCTION test_search_path_v2(tmp boolean) RETURNS
> table(ori text, i int)
>     LANGUAGE plpgsql
>     AS $_$
> DECLARE
>     txt text;
> BEGIN
>   CREATE TEMP TABLE IF NOT EXISTS return_table(ori TEXT, i INT) ON COMMIT
> DROP;
>
>   IF $1 THEN
>       PERFORM set_config('search_path', 'pg_temp, "$user", public', true);
> -- is_local = true
>       CREATE TEMP TABLE IF NOT EXISTS my_table(i INT) ON COMMIT DROP;
>   ELSE
>       PERFORM set_config('search_path', '"$user", public', true); --
> is_local = true
>       CREATE TABLE IF NOT EXISTS my_table(i INT);
>   END IF;
>
>   SELECT setting FROM pg_settings WHERE name = 'search_path' INTO txt;
>   RAISE INFO 'search_path = %', txt;
>
>   INSERT INTO my_table VALUES((random() * 100)::INT);
>
>   FOR txt IN SELECT schemaname FROM pg_tables WHERE tablename = 'my_table'
> LOOP
>       RAISE INFO '==> %', txt;
>   END LOOP;
>
>   IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname LIKE 'pg_temp%' AND
> tablename = 'my_table') THEN
>       RAISE INFO 'pg_temp.my_table exists';
>       INSERT INTO return_table SELECT 'pg_temp', t.i FROM pg_temp.my_table
> t;
>   END IF;
>
>   IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname ='public' AND
> tablename = 'my_table') THEN
>       RAISE INFO 'public.my_table exists';
>       INSERT INTO return_table SELECT 'public', t.i FROM public.my_table t;
>   END IF;
>
>   INSERT INTO return_table SELECT '', t.i FROM my_table t;
>   RETURN QUERY SELECT t.ori, t.i FROM return_table t;
> END;
> $_$;
>
> SHOW search_path -- => "$user", public
> DROP TABLE IF EXISTS my_table
> -- Executing each row on differents transactions but in the same session
> /*Session A - Transaction 1*/ SELECT * FROM test_search_path_v1(true);  --
> => OK takes table from pg_temp (no existing table in public)
> /*Session A - Transaction 2*/ SELECT * FROM test_search_path_v1(false); --
> => OK takes table from public
> /*Session A - Transaction 3*/ SELECT * FROM test_search_path_v1(true);  --
> => OK takes table from pg_temp (and the existing from public)
> /*Session A - Transaction 4*/ SELECT * FROM test_search_path_v1(false); --
> => OK takes table from public
> /*Session A - Transaction 5*/ SELECT * FROM test_search_path_v1(true);  --
> => OK takes table from pg_temp (and the existing from public)
>
> SELECT version() -- PostgreSQL 12.9 (Ubuntu 12.9-0ubuntu0.20.04.1) on
> x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0,
> 64-bit
>
> I can bypass but I would like to understand what is the cause of this
> problem to avoid unexpected problems.
> I don't understand why the probleme appears afters some (two) calls that
> works with public table and not everytime.
> I don't understand why I need to explicit pg_temp in first choice of
> search_path. I thought that is the default behavior.
> I don't understant why there may be a difference between case in different
> session and case in same session. Is the problem linked with the reuse of
> number of pg_temp_X ?
>
> I have searched and found a thread with similarity
>
> https://dba.stackexchange.com/questions/60997/is-this-temp-table-behaviour-documented
> relied on
>
> https://www.postgresql.org/message-id/20140315165011.20722.74...@wrigleys.postgresql.org
> but in this case the problem is between SQL VS PLPGSQL and it seems to
> demonstrate that in PLPGSQL the normal attemps is to use pg_temp first.
>
> Thanks for yours helps,
> Regards,
>
>

Reply via email to