Skip to content

Conversation

@wokalski
Copy link

⚠️ This is a POC / first pass - AI-generated but human reviewed and tested. I'm happy to properly review and clean this up if you agree with the approach.

Rough prototype to track temp tables created via CREATE TEMP TABLE in EXECUTE statements, so the linter doesn't report false "relation does not exist" errors.

Uses scope-based tracking - temp tables created inside branches (IF, CASE, LOOP) are only visible within that branch and cleaned up on exit. This means code that uses a temp table outside the branch where it was created will still error, which I think is correct behavior.

Compiled and tested on PostgreSQL 17. All regression tests pass.

Question: Do you agree with the scope-based approach? If yes, I'll clean this up properly.

Track temporary tables created via CREATE TEMP TABLE in EXECUTE statements
so the linter doesn't report false "relation does not exist" errors.

Uses scope-based tracking where temp tables created inside branches (IF, CASE,
LOOP, etc.) are only visible within that branch and are cleaned up when the
branch exits.
@wokalski wokalski force-pushed the temp-table-scope-tracking branch from 4df92d7 to bf969e7 Compare January 21, 2026 20:39
@okbob
Copy link
Owner

okbob commented Jan 22, 2026

Hi

parsing DDL statements and use this information is interesting idea that can possibly to reduce a necessity to use pragma PRAGMA:TABLE.

I don't know about any rule that say so temporary table should be used only in current branch. So the main goal of this PR is not correct. Generally life cycle of any database objects is independent on structure of plpgsql statements. Temporary table exists until it is dropped.

So this case is fully correct (and you should not to raise any error)

create or replace function test_temp_table_both_branches()
returns void as $$
declare v int := 1;
begin
  if v > 0 then
    create temp table temp_either(id int);
  else
    create temp table temp_either(id int);
  end if;
  -- Should error - table created in branches, not visible outside !!! this is wrong
  insert into temp_either values (1);

More - creating and dropping temporary tables is slow (when it is very frequent), so very common pattern is just create temporary table in login trigger, and later use it without creating and dropping (use truncate instead drop).

I believe so following pattern can be common:

BEGIN
  IF NOT pg_table_is_visible("mytmptab") THEN
   CREATE TEMP TABLE mytmptab (a int);
  ELSE
    TRUNCATE TABLE mytmptab;
  END IF;
  INSERT INTO mytmptab VALUES(1);
  ...

or

BEGIN
  BEGIN
    CREATE TEMP TABLE mytmptab(a int);
  EXCEPTION WHEN OTHERS THEN
    TRUNCATE TABLE mytmptab;
  END;
  INSERT INTO mytmptab VALUES(1);
  ...

Do you have some real problem, that you would to detect?

Implementation is little bit broken and dirty (how you write) - you can see broken tests. Execution of CREATE TABLE AS SELECT .. is extremely dangerous. All statements are already parsed, so you should to read data from already parsed plans.

@wokalski
Copy link
Author

I see. So I was thinking about that case

  if v > 0 then
    create temp table temp_either(id int);
  else
    create temp table temp_either(id int);
  end if;

and in the general case it's not possible as you know for sure (Rice's theorem). IMO this code arguably "should not pass". Just like a lot of correct code is often rejected by type checkers in other languages - they ask the user to reorganize the code and in return the user gets the benefit of having a reliable type system.

Do you have some real problem, that you would to detect?

Yes, we had some functions that created temp tables inside of them and that's why I decided to look into it.

I don't know about any rule that say so temporary table should be used only in current branch. So the main goal of this PR is not correct. Generally life cycle of any database objects is independent on structure of plpgsql statements. Temporary table exists until it is dropped.

You are 100% correct. This PR was created with an assumption that it would introduce a slightly opinionated way to track this.

I am wondering if you'd be open to this idea if we also add some sort of -- @plpgsql_check_option: nocheck-temp-tables or something that would retain the current behavior. In the end in all static checkers and type systems you end up with something like this (OCaml's Obj.magic, Rust's unsafe, TypeScripts ignore and this linter is closest to TypeScript in a way).

let me know and i'll eiether close the PR or work on it, this was just a conversation starter.

@okbob
Copy link
Owner

okbob commented Jan 22, 2026 via email

@wokalski
Copy link
Author

In this particular case it's not a problem indeed and heuristics can be applied but in general if you have some wider tables it's not possible in the general case. For example:

    IF is_mammal THEN
        CREATE TEMP TABLE feeding_schedule (
            id SERIAL PRIMARY KEY,
            food_type TEXT
        );
    END IF;

  if x.race = 'human' then
    perform * from feeding_schedule;
  fi

The application developer knows that a runtime error is "impossible" (wink wink) here but the type checker cannot know this.

It's same as in some languages you can make code like this that works:

if x.is_mammal:
    feeding_schedule = {
        "id": 1,
        "food_type": "Omnivore Diet"
    }
if x.race == "human":
    print(f"Human detected. Schedule: {feeding_schedule}")

and in others it won't

    if (x.is_mammal) {
        let feeding_schedule = {
            id: 1
            food_type: "Omnivore Diet",
       } 
    }

    if (x.race == "human") {
        console.log("Human detected. Schedule: ", feeding_schedule);
    }

That said I do agree that there are some trivial cases like above that we could apply some heuristics for but I don't know if it's worth it because it can be simply refactored into:

   create temp table temp_either(id int);
   if v > 0 then
     -- do something
   else
     -- do something else
   end if;

@okbob
Copy link
Owner

okbob commented Jan 22, 2026

Now, I better understand to your case, and I afraid so this cannot be implemented like you proposed.

This really needs real list of branches and dependencies, and some partial solution based on forcing convention is worse than better.

Few years ago there was similar request related to better work with polymorphic arguments (or maybe triggers) if I remember well.

I don't think so this is possible in current design of plpgsql_check. It needs different implementation where branches are stored in better graph form with much deeper analyses of expressions. Internal structures of plpgsql_check are based on plpgsql structures, and I it is not optimized for analyses like this.

More I afraid so it cannot work well, when the context of the analyse is limited only to one specific function.

@wokalski
Copy link
Author

Understood, closing this then. Regardless thanks for the project it's really cool 😄.

@wokalski wokalski closed this Jan 22, 2026
@wokalski wokalski deleted the temp-table-scope-tracking branch January 22, 2026 16:56
@okbob
Copy link
Owner

okbob commented Jan 22, 2026

Thank you :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants