plpgsql function creates sequences for row based tenancy? - postgresql

Trying to create a function that looks for sequence with particular name if does not exist should create it. Then returns function value of sequence. Part of function that does not seem to be working is where it tests if sequence already exists. Below is code for function
CREATE OR REPLACE FUNCTION public."tenantSequence"(
vtenantid integer,
vtablename character)
RETURNS bigint AS
$BODY$DECLARE
vSeqName character varying;
vSQL character varying;
BEGIN
select ('t' || trim(to_char(vtenantid,'0000')) || vtablename) INTO vSeqName;
if not exists(SELECT 0 FROM pg_class where relkind = 'S' and relname = vSeqName )
then
vSQL := 'create sequence '||vSeqName||';';
execute vSQL;
ELSE
return 0;
end if;
return nextval(vSeqName) * 10000 + vtenantid;
END$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION public."tenantSequence"(integer, character)
OWNER TO postgres;

Problem was case as mentioned by a_horse_with_no_name
changed line assign vSeqName to the following
vSeqName := lower('t' || trim(to_char(vtenantid,'0000')) || vtablename);
Now function works as expected.

Related

postgresql dynamic query

I need to replace schema and table name by parameters in following function (that is currently working perfectly):
CREATE OR REPLACE FUNCTION public.my_function_119()
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE _check INTEGER;
BEGIN
SELECT SUM("length"/1000)
FROM public."National_Grid_multiline"
INTO _check;
RETURN _check;
END
$function$
I have tried following solution (and its numerous variations) :
CREATE OR REPLACE FUNCTION public.my_function_119(schema text, tablename text)
RETURNS INTEGER
LANGUAGE plpgsql
AS
$function$
DECLARE _check INTEGER;
BEGIN
RETURN
'(SELECT SUM((length/1000))::integer FROM ' || schema || '."' || tablename || '")::integer INTO _check' ;
RETURN _check;
END
$function$
but keep running into following error code :
psycopg2.errors.InvalidTextRepresentation: invalid input syntax for type integer: "(SELECT SUM((length/1000))::integer FROM public."National_Grid_multiline")::integer INTO _check"
CONTEXT: PL/pgSQL function my_function_119(text,text) while casting return value to function's return type
Why is this not working ? The 'length' column contains float values.
You have to use dynamic SQL, because you cannot use a parameter for an identifier.
Also, make sure to avoid SQL injection by using format rather than concatenating strings:
EXECUTE
format(
'SELECT SUM((length/1000))::integer FROM %I.%I',
schema,
table_name
)
INTO _check';
You can try this :
CREATE OR REPLACE FUNCTION public.my_function_119(schema text, tablename text)
RETURNS INTEGER
LANGUAGE plpgsql
AS
$function$
DECLARE
res integer ;
BEGIN
EXECUTE E'
(SELECT SUM((length/1000))::integer INTO res FROM ' || schema || '."' || tablename || '"):: integer' ;
RETURN res ;
END ;
$function$

Using NEW.* inside EXECUTE regarding psql

I checked all related questions on SO but none helped in my case.
I have 2 loops(outside for the tables and inside for the columns). Tables are represented by 'r', and columns by 'm'. While being inside the 'm' loop which is supposed to send column values to the to-be-created trigger function. When I try to use 'NEW.m' (with trying many different formatting attempts) compiler always gives error.
Can you kindly advice on it please? Br
FOR r IN SELECT table_name FROM information_schema.tables LOOP
FOR m IN SELECT column_name FROM information_schema.columns WHERE (table_name = r.table_name ) LOOP
function_name := 'dictionary_functions_foreach_trigger';
EXECUTE format('CREATE OR REPLACE FUNCTION %s()
RETURNS trigger AS
$BODY$
DECLARE
BEGIN
IF NEW.m IS NOT NULL AND NEW.m IN (SELECT key FROM tableX.tableX_key)
THEN RETURN NEW;
END IF;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION %s()
OWNER TO mydb;',function_name, function_name);
EXECUTE 'CREATE TRIGGER ' || function_name || ' BEFORE INSERT OR UPDATE ON ' || belonging_to_schema || '.' || r.table_name || ' FOR EACH ROW EXECUTE PROCEDURE ' || function_name || '();';
----Trigger Functions after edit-
EXECUTE format(
'CREATE OR REPLACE FUNCTION %s()
RETURNS trigger AS
$BODY$
DECLARE
insideIs text := %s ;
BEGIN
FOR %s IN 0..(TG_NARGS-1) LOOP
IF %I= TG_ARGV[%s]
THEN insideIs := %s ;
END IF;
END LOOP;
IF NEW.%I IS NOT NULL AND (insideIs =%s) AND NEW.%I IN (SELECT key FROM tableX.tableX_key)
THEN RETURN NEW;
ELSE RETURN OLD;
END IF;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION %s()
OWNER TO mydb;' , function_name, 'notInside', 'i' , m.column_name, 'i' , 'ok', m.column_name, 'ok', m.column_name ,function_name);
You need to use another placeholder for the column name, they way you have written it, the column name "m" is hardcoded in the function.
You also don't really need the outer loop, as the table_name is also available in information_schema.columns.
Your trigger would also fail with a runtime error if the condition is not true as you don't have a return in that case. If you want to abort the statement, use return null;
You should also use format() for the create trigger statement.
FOR m IN SELECT table_schema, table_name, column_name
FROM information_schema.columns
WHERE table_name in (...)
LOOP
function_name := 'dictionary_functions_foreach_trigger';
EXECUTE format('CREATE OR REPLACE FUNCTION %I()
RETURNS trigger AS
$BODY$
DECLARE
BEGIN
IF NEW.%I IS NOT NULL AND NEW.%I IN (SELECT key FROM tableX.tableX_key) THEN
RETURN NEW;
END IF;
RETURN null; --<< you need some kind of return here!
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION %s()
OWNER TO mydb;', function_name, m.column_name, m.column_name, function_name, function_name);
EXECUTE format('CREATE TRIGGER %I BEFORE INSERT OR UPDATE ON %I.%I FOR EACH ROW EXECUTE PROCEDURE %I()',
function_name, m.table_schema, m.table_name, function_name);
END LOOP;
Online example

PostgreSQL plpgsql - variable column names

I am creating a trigger, which uses dynamic names for columns
NEW.name:=2222; -- works fine !
but
dynamic_column:='name';
EXECUTE '$1.'||dynamic_column||':=2222 ' USING NEW; -- raises error
gives an error:
ERROR: syntax error at or near "$1" LINE 1: $1.name:=2222
I found info here: Assign to NEW by key in a Postgres trigger
If we enable the module hstore by:
CREATE EXTENSION hstore;
We can do this:
dynamic_column:='name';
temp_sql_string:='"'||dynamic_column||'"=>"2222"';
NEW := NEW #= temp_sql_string::hstore;
And the RECORD NEW.name now is set to the value 2222.
Thank you tough for making an effort to find a solution #Laurenz Albe
The problem is that this is not a valid SQL statement.
You can access the columns in new with dynamic SQL like this:
EXECUTE 'SELECT $1.id' INTO v_id USING NEW;
There is no comfortable way like that for changing individual columns in NEW.
You could use TG_RELID to get the OID of the table, query pg_attribute for the columns, compose a row literal string composed of the values in NEW and your new value, cast this to the table type and assign the result to NEW. Quite cumbersome.
Here is sample code that does that (I tested it, but there may be bugs left):
CREATE OR REPLACE FUNCTION dyntrig() RETURNS trigger
LANGUAGE plpgsql AS
$$DECLARE
colname text;
colval text;
newrow text := '';
fieldsep text := 'ROW(';
BEGIN
/* loop through the columns of the table */
FOR colname IN
SELECT attname
FROM pg_catalog.pg_attribute
WHERE attrelid = TG_RELID
AND attnum > 0
AND NOT attisdropped
ORDER BY attnum
LOOP
IF colname = 'name' THEN
colval = '2222';
ELSE
/* all other columns than 'name' retain their value */
EXECUTE 'SELECT CAST($1.' || quote_ident(colname) || ' AS text)'
INTO colval USING NEW;
END IF;
/* compose a string that represents the new table row */
IF colval IS NULL THEN
newrow := newrow || fieldsep || 'NULL';
ELSE
newrow := newrow || fieldsep || '''' || colval || '''';
END IF;
fieldsep := ',';
END LOOP;
newrow := newrow || ')';
/* assign the new table row to NEW */
EXECUTE 'SELECT (CAST(' || newrow || ' AS '
|| quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME)
|| ')).*'
INTO NEW;
RETURN NEW;
END;$$;
You already found my answer recommending the hstore operator #= on dba.SE. You may also be interested in the corresponding reference answer here on SO:
How to set value of composite variable field using dynamic SQL
Since you construct the auxiliary hstore value from variables I suggest the simple function hstore():
CREATE OR REPLACE FUNCTION dyn_trigger_func()
RETURNS TRIGGER AS
$func$
DECLARE
dyn_col_name text := 'name';
dyn_col_val text := '2222';
BEGIN
NEW := NEW #= hstore(dyn_col_name, dyn_col_val);
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
Faster / simpler / clearer / more secure this way.
Or, since it's obviously a trigger function, you may want to pass column name and value in CREATE TRIGGER statements:
CREATE OR REPLACE FUNCTION dyn_trigger_func()
RETURNS TRIGGER AS
$func$
BEGIN
NEW := NEW #= hstore(TG_ARGV[0], TG_ARGV[1]);
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
And:
CREATE TRIGGER ins_bef
BEFORE INSERT ON tbl
FOR EACH ROW EXECUTE PROCEDURE dyn_trigger_func('name', '2222');
Provide column name unquoted and case-sensitive.
Related:
Get values from varying columns in a generic trigger
Trigger with dynamic field name

"Function does not exist" error when creating the function with more parameters

I have dropped a PL/pgSQL function and I try to recreate it with one more parameter (with default value). But I get a strange error:
ERROR: function vytvor_kod_sj(text, integer, integer) does not exist
SQL state: 42883
I would expect such an error while dropping or calling the function, not while creating it. I made sure that the error occurs exactly while trying to create it, not in any of the other functions or triggers I created from the same sql file.
I made a dummy function without the last parameter and it works now, but it is definitely not what I want - not only I don't need the function without the last parameter anymore, I usually call the function only with the first two or three parameter, so I need to drop it just after creating the new version of my function to avoid mismatches. Fortunately, there are no errors while doing this, but it is hardly optimal solution.
So does anyone know how to solve this mysterious problem?
I have PostgreSQL 9.3.4 on Windows 32; I use pgAdmin 1.18.1
My code:
CREATE OR REPLACE FUNCTION vytvor_kod_sj( _akce text, typ_sj integer,
podtyp integer DEFAULT 0, styl integer DEFAULT NULL )
RETURNS text AS $$
DECLARE
--styl integer;
_nazev_seq text;
_min integer;
_max integer;
_nazev text;
_soucasna integer;
BEGIN
IF styl IS NULL THEN
SELECT nomenklatura INTO styl FROM akce WHERE kod_akce = _akce;
END IF;
IF NOT EXISTS( SELECT id_nom FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = podtyp ) THEN
IF podtyp = 0 OR NOT EXISTS( SELECT id_nom FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = 0 ) THEN
RAISE NOTICE 'Pro daný typ stratigrafické jednotky není vytvořeno žádné pravidlo!';
RETURN NULL;
ELSE
podtyp := 0;
END IF;
END IF;
_nazev_seq := _akce || '_' || typ_sj || '_' || podtyp || '_seq';
IF NOT EXISTS (SELECT 0 FROM pg_class where relname = _nazev_seq ) THEN
SELECT min, max INTO _min, _max FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = podtyp;
IF _max IS NOT NULL THEN
EXECUTE format('CREATE SEQUENCE %I MINVALUE %s MAXVALUE %s ', _nazev_seq, _min, _max);
ELSE
EXECUTE format('CREATE SEQUENCE %I MINVALUE %s ', _nazev_seq, _min);
END IF;
END IF;
--IF strict IS TRUE THEN
RETURN (
SELECT predpona FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = podtyp
) || CAST(nextval(_nazev_seq) AS TEXT);
--END IF;
END;
$$ LANGUAGE plpgsql
SECURITY DEFINER;
ALTER FUNCTION vytvor_kod_sj( text, integer, integer ) OWNER TO ins_daemon;
The old version had styl variable declared, not as a parameter, and there was no test whether it is null. Otherwise I didn't change anything.
I noticed the cause of the problem just after posting the question:
ALTER FUNCTION vytvor_kod_sj( text, integer, integer ) OWNER TO ins_daemon;
It tries to alter the old function.

Variable substitution in PL/pgSQL

I have a select statement that is generated dynamically based on the supplied parameter. The problem is that postgresql always says:
argument of WHERE must be type boolean, not type character varying no matter what the parameter is. Did I miss anything?
CREATE OR REPLACE FUNCTION getuid(name character varying) RETURNS integer AS $$
DECLARE
statement varchar;
uid integer;
BEGIN
IF ($1 = '') THEN
statement := 'TRUE';
statement := CAST(statement AS BOOLEAN);
ELSE
statement := 'users.keywords ILIKE''' || '%' || $1 || '%''';
END IF;
SELECT INTO uid id FROM users WHERE "statement";
RETURN uid;
END;
$$ LANGUAGE plpgsql
You need EXECUTE if you want to generate dynamic commands inside a function. You could also use two different sections:
CREATE OR REPLACE FUNCTION getuid(name character varying) RETURNS integer AS $$
DECLARE
statement varchar;
uid integer;
BEGIN
IF ($1 = '' OR $1 IS NULL) THEN -- section 1
SELECT id INTO uid FROM users;
ELSE -- section 2
SELECT id INTO uid FROM users WHERE users.keywords ILIKE '%' || $1 || '%';
END IF;
RETURN uid;
END;
$$ LANGUAGE plpgsql;
EXECUTE is a PL/pgSQL statement and not SQL statement. So you have to wrap your dynamic query into PL/pgSQL stored procedure.
Be careful about variable substitution and do not forget to use quote_literal() or quote_nullable() when building up you query.
Have look in documentation here: http://www.postgresql.org/docs/current/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN