HiveBrain v1.2.0
Get Started
← Back to all entries
snippetsqlMinor

How to TRIGGER/RETURNING this generator function in PostgreSQL?

Submitted by: @import:stackexchange-dba··
0
Viewed 0 times
thispostgresqltriggerfunctiongeneratorreturninghow

Problem

I am providing a key table to my users in various categories so I am thinking how to TRIGGER/RETURNING the function with each SELECT.
Functions to generate shorthand unique IDs

  • The thread answer Generating human-readable/usable, short but unique IDs but no salt so no.



  • http://hashids.org/ can be a valid option as proposed by the answer here



  • Any default PostgreSQL options?



Example of key table where I need the keys

Module 1 
1. 
2. 
3. ...

Module 2 
1. 
2. 


Keys are bound to the SERIAL primary keys in the table event_log.
I am thinking if the database can do the generation task for you i.e. how to bind your primary key to any algorithm that can generate such shorthand forms.
I have currently the data as the following in the table where SERIAL PRIMARY is not so clear

CREATE TABLE event_log (
    data_id SERIAL PRIMARY KEY NOT NULL,
    name VARCHAR(10) NOT NULL,
    module INTEGER NOT NULL, - - 1 or 2 or 3 as value
    time TIMESTAMP NOT NULL
);


Idea: Bind a TRIGGER and/or RETURNING nice_id to SELECT * FROM event_log WHERE module=1 queries

OS: Debian Linux 8.7

SQL: PostgreSQL 9.4 and/or R sqldf

Solution

I am thinking if the database can do the generation task for you i.e.
how to bind your primary key to any algorithm that can generate such
shorthand forms

Since you mention a salt, I assume you're looking for short strings
that are hard to guess or reproduce by an outsider, despite
being originated with a database sequence.
The permuteseq extension
essentially does that.

The function that produces a short string from an integer has
to be provided, for instance:

CREATE FUNCTION id_to_alpha(n int) RETURNS text
LANGUAGE plpgsql IMMUTABLE STRICT AS $
DECLARE
 alphabet text:='abcdefghijklmnopqrstuvwxyz012345678';
 sign_char char:='9';
 base int:=length(alphabet); 
 _n bigint:=abs(n);
 output text:='';
BEGIN
 LOOP
   output := output || substr(alphabet, 1+(_n%base)::int, 1);
   _n := _n / base;
   EXIT WHEN _n=0;
 END LOOP;
 RETURN CASE WHEN (n<0) THEN output || sign_char::text ELSE output END;
 RETURN output;
END; $;


Then you may set the short id slug in a BEFORE INSERT trigger, like this:

CREATE EXTENSION permuteseq;

CREATE TABLE things( 
 id serial,
 short_id text,
 name varchar(10) not null
);

-- keep the sequence short for short output strings
ALTER SEQUENCE things_id_seq MAXVALUE 1000000;

CREATE FUNCTION generate_short_id() RETURNS TRIGGER AS $
DECLARE
 secret bigint := 123456789;  -- change for a different secret sequence
BEGIN
 NEW.short_id := id_to_alpha(range_encrypt_element(NEW.id, 1, 10000000, secret)::int);
 RETURN NEW;
END;
$ LANGUAGE plpgsql;

CREATE TRIGGER things_trigger BEFORE INSERT ON things 
FOR EACH ROW EXECUTE PROCEDURE generate_short_id();


Testing it:

INSERT INTO things(name) SELECT 'foo' FROM generate_series(1,10);
SELECT * FROM things;


id | short_id | name
----+----------+------
1 | wf3hg | foo
2 | tm6lg | foo
3 | riqbg | foo
4 | p6jp | foo
5 | h3r3c | foo
6 | 3sx5d | foo
7 | w8ecd | foo
8 | km3le | foo
9 | llt1e | foo
10 | xwtxc | foo

In this example, there is both id and short_id in the table, but id
is technically redundant with short_id. You could create a sequence
outside of the table without the SERIALsyntax like this, and not
even have id in the table:

CREATE SEQUENCE seq_pk MAXVALUE 1000000;

CREATE TABLE things2(
 short_id TEXT 
    DEFAULT id_to_alpha(permute_nextval('seq_pk'::regclass, 123456::bigint)::int) 
    PRIMARY KEY,
 name text
);

INSERT INTO things2(name) values('foo');

SELECT * FROM things2;
 short_id | name 
----------+------
 znyh     | foo


If you can't use permuteseq, as an alternative still based on
encrypting the ID, you may look at the plpgsql implementation of
SKIP32,
or XTEA for 64-bit (bigint)
which won't require a compilation or being superuser. The drawback is
that you cannot fine-tune the size of the output by changing the
sequence's range.

Code Snippets

CREATE FUNCTION id_to_alpha(n int) RETURNS text
LANGUAGE plpgsql IMMUTABLE STRICT AS $$
DECLARE
 alphabet text:='abcdefghijklmnopqrstuvwxyz012345678';
 sign_char char:='9';
 base int:=length(alphabet); 
 _n bigint:=abs(n);
 output text:='';
BEGIN
 LOOP
   output := output || substr(alphabet, 1+(_n%base)::int, 1);
   _n := _n / base;
   EXIT WHEN _n=0;
 END LOOP;
 RETURN CASE WHEN (n<0) THEN output || sign_char::text ELSE output END;
 RETURN output;
END; $$;
CREATE EXTENSION permuteseq;

CREATE TABLE things( 
 id serial,
 short_id text,
 name varchar(10) not null
);

-- keep the sequence short for short output strings
ALTER SEQUENCE things_id_seq MAXVALUE 1000000;

CREATE FUNCTION generate_short_id() RETURNS TRIGGER AS $$
DECLARE
 secret bigint := 123456789;  -- change for a different secret sequence
BEGIN
 NEW.short_id := id_to_alpha(range_encrypt_element(NEW.id, 1, 10000000, secret)::int);
 RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER things_trigger BEFORE INSERT ON things 
FOR EACH ROW EXECUTE PROCEDURE generate_short_id();
INSERT INTO things(name) SELECT 'foo' FROM generate_series(1,10);
SELECT * FROM things;
CREATE SEQUENCE seq_pk MAXVALUE 1000000;

CREATE TABLE things2(
 short_id TEXT 
    DEFAULT id_to_alpha(permute_nextval('seq_pk'::regclass, 123456::bigint)::int) 
    PRIMARY KEY,
 name text
);

INSERT INTO things2(name) values('foo');

SELECT * FROM things2;
 short_id | name 
----------+------
 znyh     | foo

Context

StackExchange Database Administrators Q#174771, answer score: 3

Revisions (0)

No revisions yet.