18

I am using postgresql 9.4 and while writing functions I want to use self-defined error_codes (int). However I may want to change the exact numeric values later.
For instance
-1 means USER_NOT_FOUND.
-2 means USER_DOES_NOT_HAVE_PERMISSION.

I can define these in a table codes_table(code_name::text, code_value::integer) and use them in functions as follows

(SELECT codes_table.code_value FROM codes_table WHERE codes_table.code_name = 'USER_NOT_FOUND')

Is there another way for this. Maybe global variables?

3
  • 1
    Just stick to the table. Wrap it up in a simple SQL function if you must. Really, though, I see little point in changing the values, why not just make them static? Commented Jul 9, 2015 at 12:33
  • @CraigRinger We are developing a new system and a format will be set for the error codes. For instance, [-9 -1] is for permission related errors, [-19, -10] for check constraint errors. However that format is not set for now. And maybe once it is set, later it will be reset. So I want to be ready. Commented Jul 9, 2015 at 12:56
  • those aren't error codes, you return error codes with RAISE EXCEPTION and reasonably recent versions up allow you to specify the code that you raise. Commented Jul 10, 2015 at 6:19

6 Answers 6

18

Postgres does not have global variables. However you can define custom configuration parameters. To keep things clear define your own parameters with a given prefix, say glb.

This simple function will make it easier to place the parameter in queries:

create or replace function glb(code text)
returns integer language sql as $$
    select current_setting('glb.' || code)::integer;
$$;

set glb.user_not_found to -1;
set glb.user_does_not_have_permission to -2;

select glb('user_not_found'), glb('user_does_not_have_permission');

User-defined parameters are local in the session, therefore the parameters should be defined at the beginning of each session.

Sign up to request clarification or add additional context in comments.

2 Comments

This configuration parameters could do the job. However is there a way to make them permanent (until I change them)?
@Nuri: See ALTER SYSTEM, new in 9.4
15

Building on @klin's answer, there are a couple of ways to persist a configuration parameter beyond the current session. Note that these require superuser privieges.

To set a value for all connections to a particular database:

ALTER DATABASE db SET abc.xyz = 1;

You can also set a server-wide value using the ALTER SYSTEM command, added in 9.4. It only seems to work for user-defined parameters if they have already been SET in your current session. Note also that this requires a configuration reload to take effect.

SET abc.xyz = 1;
ALTER SYSTEM SET abc.xyz = 1;
SELECT pg_reload_conf();

Pre-9.4, you can accomplish the same thing by adding the parameter to your server's postgresql.conf file. In 9.1 and earlier, you also need to register a custom variable class.

3 Comments

I can see the value with SHOW abc.xyz. However I couldn't use it in a SELECT. How can I use its value?
Use the function from my answer (of course you should change the prefix glb).
is there a limit on size of the config parameters, or a practical limit before it begins to hurt system performance? For example, could it go into say 100mb (on a 100gb server)?
6

You can use this

CREATE OR REPLACE FUNCTION globals.maxCities()
  RETURNS integer AS
  $$SELECT 100 $$ LANGUAGE sql IMMUTABLE;

.. and directly use globals.maxCities() in the code.

Comments

2

Postgresql does not support global variables on the DB level. Why not add it:

CREATE TABLE global_variables (
  key text not null PRIMARY KEY
  value text
);

INSERT INTO global_variables (key, value) VALUES ('error_code_for_spaceship_engine', '404');

If different types may be the values, consider JSON to be the type for value, but then deserialization code is required for each type.

Comments

1

You can use a trick and declare your variables as a 1-row CTE, which you then CROSS JOIN to the rest. See example:

WITH
variables AS (
    SELECT 'value1'::TEXT AS var1, 10::INT AS var2
)
SELECT t.*, v.*
FROM
    my_table AS t
    CROSS JOIN variables AS v
WHERE t.random_int_column = var2;

1 Comment

you can simplify the variables CTE to with variables (var1, var2, trip_type_var as (values ('value1', 'value2', 'one')) ...
1

Note that if you work with database-level settings, as in @nick-barnes his answer, these will be set as session defaults when the user connects to that database, but can be overridden for that session (or a transaction).

If you wish to enforce a database setting, you can do so by going directly to the pg_db_role_setting system catalog, as done by this function from my pg_safer_settings extension (copied here with my explicit permission 😉):

create function pg_db_setting(pg_setting_name$ text, pg_role$ regrole = 0)
    returns text
    stable
--    security definer
    return (
        select
            regexp_replace(expanded_settings.raw_setting, E'^[^=]+=', '')
        from
            pg_catalog.pg_db_role_setting
        inner join
            pg_catalog.pg_database
            on pg_database.oid = pg_db_role_setting.setdatabase
        cross join lateral
            unnest(pg_db_role_setting.setconfig) as expanded_settings(raw_setting)
        where
            pg_database.datname = current_database()
            and pg_db_role_setting.setrole = coalesce(
                pg_role$,
                0  -- 0 means “not role-specific”
            )
            and expanded_settings.raw_setting like pg_setting_name$ || '=%'
        limit 1
    );

Here's an example to illustrate the difference between the semantics of the pg_catalog.current_setting() function and my pg_db_role_setting() function:

CREATE DATABASE mydb;
CONNECT TO mydb
CREATE ROLE myrole;
ALTER DATABASE mydb
    SET app.settings.bla = 1::text;
ALTER ROLE myrole
    IN DATABASE mydb
    SET app.settings.bla = 2::text;
SET ROLE myrole;
SET app.settings.bla TO 3::text;
SELECT current_setting('app.settings.bla', true);  -- '3'
SELECT pg_db_role_setting('app.settings.bla');  -- '1'
SELECT pg_db_role_setting('app.settings.bla', current_user);  -- '2'

Another common pattern is to store settings as IMMUTABLE functions or in a table. If you store them in a table, do me a favor and store them as columns, not rows. My pg_safer_settings extension features a pg_safer_settings_table which combines these 2 paradigms, by allowing you to store your settings as columns, and automatically maintaining a current_<col_name>() function for each of these columns. (The current_ prefix is configurable.)

5 Comments

Out of curiosity, why store them as columns as opposed to rows? It seems to me that locking is a lot less likely when stored in separate rows than when storing it all in the same row. Love to hear your reasoning.
Good question, @ThomasHeijtink. The choice for columns is motivated by wanting to have as strong as possible typing and constraints. I don't personally have any use cases involving frequent, parallel setting changes, which is why I don't worry about locking and only worry about data integrity. I love being able to add a setting like default_billing_interval and have it being an actual interval. I could have gone for rows and add a setting_regtype column. But, then I feel a bit like I'm reimplementing SQL semantics on top of DB semantics.
@ThomasHeijtink, If you make an extension that stores the settings in rows instead of columns, I would totally link to it from the README.md of pg_safer_settings.
Prompted by you, @ThomasHeijtink, I've documented this design choice in the README.md now: github.com/bigsmoke/…
thank you for clarifying. Yeah that does make sense. Different needs require different approaches. Thanks for explaining. 👍

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.