Postgres Session Variables – Neat.
After futzing around a bit, and once again having my suspicions confirmed, I came up with the following solution to my session variables problem: temporary tables. They are dropped at the end of the session, so it all pans out nicely. I still didn’t like having to make front-ends do all the work (plus I was all excited after figuring it out), so I slapped together a pretty basic couple of wrapper functions:
/* * Session Variables in PostgreSQL via PL/pgSQL * Written/tested on version 8.4.2, but should work anywhere * * Code written by Stephen "sycobuny" Belcher * * Free to use, I enjoy writing stuff like this. * Just give me some props if you do. * */ /* -- These lines may not be necessary, lang/schema may exist -- Remove commenting only if they need to be created CREATE LANGUAGE 'plpgsql'; CREATE SCHEMA SV; */ -- Make sure the session_variables temporary table exists -- TODO: Make sure it has the right columns, too CREATE OR REPLACE FUNCTION SV.ensure_session_table_exists() RETURNS VOID AS $BODY$BEGIN PERFORM * FROM pg_catalog.pg_class WHERE relname = 'session_variables' AND relnamespace = pg_catalog.pg_my_temp_schema(); IF NOT FOUND THEN CREATE TEMPORARY TABLE session_variables ( "key" TEXT PRIMARY KEY, "value" TEXT ); RETURN; END IF; END;$BODY$ LANGUAGE 'plpgsql'; -- Set a variable. Yep. CREATE OR REPLACE FUNCTION SV.set(IN xKey TEXT, INOUT xValue TEXT) AS $BODY$BEGIN PERFORM SV.ensure_session_table_exists(); PERFORM * FROM session_variables WHERE "key" = xKey; IF FOUND THEN UPDATE session_variables SET "value" = xValue WHERE "key" = xKey; ELSE INSERT INTO session_variables ("key", "value") VALUES (xKey, xValue); END IF; RETURN; END;$BODY$ LANGUAGE 'plpgsql'; -- Get a variable's value. It's just that easy! CREATE OR REPLACE FUNCTION SV.get(IN xKey TEXT, OUT xValue TEXT) AS $BODY$BEGIN PERFORM SV.ensure_session_table_exists(); PERFORM "value" FROM session_variables WHERE "key" = xKey; IF NOT FOUND THEN RAISE WARNING 'Variable % does not exist', xKey; END IF; SELECT "value" INTO xValue FROM ession_variables WHERE "key" = xKey; RETURN; END;$BODY$ LANGUAGE 'plpgsql';
From this point, you just do:
SELECT SV.set('my session variable', 'its value'); SELECT SV.get('my session variable');
It handles creating and updating the table and values independently, without any hassles.
Well, there’s a couple hassles:
If it’s the first time you access it in a session, it barfs out warnings because PostgreSQL likes to warn you when it creates new indexes and constraints implicitly; whether this causes any libraries such as ActiveRecord to croak, I’m not sure – I will be testing that one shortly at least. Also, the get() function itself throws up a warning if you try to get a variable that hasn’t been defined yet. This is because the value of any variable can actually be NULL, but there’s nothing else to return if it hasn’t been defined yet. It’s an intrinsically implementation-specific concern whether this behavior is desired, so I split the difference: you can do it, but you’re going to be tut-tutted by the database.
So, there you have it. Of course, changing the schema into which I put these functions should be trivial; I put them there because I like the simple syntactic sugar it provides, but, as they are not tied to a permanent table it should be as simple as just changing the function names. I’ll probably wind up doing it myself, as my schemas are thematically named (individual “projects” and their “code names”).
There’s one final gotcha, which I didn’t fully account for because I ran out of ideas how to ensure it continues working right: PostgreSQL processes statements directed towards temporary tables first (if you don’t specify the fully qualified name), before checking the schemas in the search_path. I’m not sure if this is an SQL-standard way of doing things; if it isn’t, I’m sure they’ll correct it at some point, and then my code will be broken. Unfortunately, I’m not entirely sure how best to perform a query on a temporary schema (which is assigned a technically-random name by the database) without constructing the query as string, which totally removes any optimization done by preparing the statements ahead of time. If there’s some way around that, I’d be quite interested to hear it. Oh yeah, and it hides your “session_variables” table, if you’ve made one yourself. Sorry. Qualify your names and it won’t be a problem, though.