How to save and restore value of ON_ERROR_STOP? - postgresql

Is there a way to save, temporarily change, and then restore the value of the psql ON_ERROR_STOP variable?
Basically, I'd like to have the "moral equivalent" of the following in a psql script:
save_on_error_stop=ON_ERROR_STOP
\unset ON_ERROR_STOP
ALTER TABLE foo DROP COLUMN bar; -- (for example)
\set ON_ERROR_STOP save_on_error_stop
ALTER TABLE foo ADD COLUMN bar;
The point being that the '\set' command at the end won't actually set ON_ERROR_STOP unless it was set before.

I don't think it is possible to do this in a single psql session. According to the manual, one can use \set command on it's own to list all psql variables.
One can memorize the setting in shell, but this makes the whole thing useless, as it is much more simple just to execute the desired set of queries enforcing the desired ON_ERROR_STOP value.
Another alternative is to write an anonymous code block and DO some extra logic to detect, whether column needs to be dropped before adding it.
BTW, What is the purpose of dropping and adding column straight after?
If it is only to make sure no such column exists, how bout this DO block:
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema='public' AND table_name='foo'
AND column_name='bar')
THEN
EXECUTE format('ALTER TABLE %I.%I ADD %I text',
'public','foo','bar');
END IF;
END; $$;
You can also create a function if you tend to do such check quite often.

Related

How to prevent or avoid running update and delete statements without where clauses in PostgreSQL

How to prevent or avoid running update or delete statements without where clauses in PostgreSQL?
Same as SQL_SAFE_UPDATES statement in MySQL is needed for PostgreSQL.
For example:
UPDATE table_name SET active=1; -- Prevent this statement or throw error message.
UPDATE table_name SET active=1 WHERE id=1; -- This is allowed
My company database has many users with insert and update privilege any one of the users do that unsafe update.
In this secoario how to handle this.
Any idea can write trigger or any extension to handle the unsafe update in PostgreSQL.
I have switched off autocommits to avoid these errors. So I always have a transaction that I can roll back. All you have to do is modify .psqlrc:
\set AUTOCOMMIT off
\echo AUTOCOMMIT = :AUTOCOMMIT
\set PROMPT1 '%[%033[32m%]%/%[%033[0m%]%R%[%033[1;32;40m%]%x%[%033[0m%]%# '
\set PROMPT2 '%[%033[32m%]%/%[%033[0m%]%R%[%033[1;32;40m%]%x%[%033[0m%]%# '
\set PROMPT3 '>> '
You don't have to insert the PROMPT statements. But they are helpful because they change the psql prompt to show the transaction status.
Another advantage of this approach is that it gives you a chance to prevent any erroneous changes.
Example (psql):
database=# SELECT * FROM my_table; -- implicit start transaction; see prompt
-- output result
database*# UPDATE my_table SET my_column = 1; -- missed where clause
UPDATE 525125 -- Oh, no!
database*# ROLLBACK; -- Puh! revert wrong changes
ROLLBACK
database=# -- I'm completely operational and all of my circuits working perfectly
There actually was a discussion on the hackers list about this very feature. It had a mixed reception, but might have been accepted if the author had persisted.
As it is, the best you can do is a statement level trigger that bleats if you modify too many rows:
CREATE TABLE deleteme
AS SELECT i FROM generate_series(1, 1000) AS i;
CREATE FUNCTION stop_mass_deletes() RETURNS trigger
LANGUAGE plpgsql AS
$$BEGIN
IF (SELECT count(*) FROM OLD) > TG_ARGV[0]::bigint THEN
RAISE EXCEPTION 'must not modify more than % rows', TG_ARGV[0];
END IF;
RETURN NULL;
END;$$;
CREATE TRIGGER stop_mass_deletes AFTER DELETE ON deleteme
REFERENCING OLD TABLE AS old FOR EACH STATEMENT
EXECUTE FUNCTION stop_mass_deletes(10);
DELETE FROM deleteme WHERE i < 100;
ERROR: must not modify more than 10 rows
CONTEXT: PL/pgSQL function stop_mass_deletes() line 1 at RAISE
DELETE FROM deleteme WHERE i < 10;
DELETE 9
This will have a certain performance impact on deletes.
This works from v10 on, when transition tables were introduced.
If you can afford making it a little less convinient for your users, you might try revoking UPDATE privilege for all "standard" users and creating a stored procedure like this:
CREATE FUNCTION update(table_name, col_name, new_value, condition) RETURNS void
/*
Check if condition is acceptable, create and run UPDATE statement
*/
LANGUAGE plpgsql SECURITY DEFINER
Because of SECURITY DEFINER this way your users will be able to UPDATE despite not having UPDATE privilege.
I'm not sure if this is a good approach, but this way you can force as strict UPDATE (or anything else) requirements as you wish.
Of course the more complicated UPDATES are required, the more complicated has to be your procedure, but if this is mostly just about updating single row by ID (as in your example) this might be worth a try.

Postgres run statement against multiple schemas

I have a mult-tenant application where tenants are set up on different schemas within the same database. The reason being that they have some shared data they use on one of the schemas.
Up until now I've been using a bash script with a list of the schemas in it that needs to be updated whenever a new schema is added and I need to do things like table schema changes across the accounts. For instance adding a new column to a table.
Is there a way in Postgres, psql, etc... to run for instance
ALTER TABLE some_table ADD COLUMN some_column TEXT NOT NULL DEFAULT '';
without having to do string replacement in another script like bash for instance.
So in other words is there an easy enough way to get the schemas, and write in psql a for loop that will iterate through the schemas and run the statement each by setting search_path for instance.
The reason being that the number of tenants is growing, and new tenants can be added by admin users that aren't devs, so I'm constantly updating my shell scripts. This will only grow exponentially. Is there a standard way of handling this kind of problem?
You can do that with a little PL/pgSQL block:
do
$$
declare
s_rec record;
begin
for s_rec in select schema_name
from information_schema.schemata
where schema_name not in ('pg_catalog', 'information_schema')
loop
execute format ('ALTER TABLE if exists %I.some_table ADD COLUMN some_column TEXT NOT NULL DEFAULT ''''), s_rec.schema_name);
end loop;
end;
$$
The if exists will make sure the statement doesn't fail if that table doesn't exist in the schema.
If you over-simplified your question and want in fact run complete scripts once for each schema, generating a script for each schema that includes the actual script is probably easier:
select concat(format('set search_path = %I;', schema_name),
chr(10),
'\i complete_migration_script.sql')
from information_schema.schemata
where schema_name not in ('pg_catalog', 'information_schema')
You can spool the output of that statement into a file and then run that file using psql (of course you need to replace complete_migration_script.sql with the actual name of your script)

Triggers in Postgresql/postgis

I have a shapefile loaded into a postgis database. This shapefile is frequently updated by the source and thus my current process is:
Use shp2pgql with -a option to generate insert statements.
Run the SQL generated in step 1 to append to database.
Of course, I end up with all the rows from both versions of the shapefile, and what I need is to get rid of all the previous rows and load only the rows from the updated shapefile.
I tried creating a trigger and trigger function in the database:
CREATE TRIGGER drop_all_rows_from_owner_table_trigger
BEFORE INSERT
ON owner_polygons_common_ownership_layer
FOR EACH STATEMENT
EXECUTE PROCEDURE drop_all_rows_from_owner_table();
Here's the trigger function:
CREATE OR REPLACE FUNCTION drop_all_rows_from_owner_table()
RETURNS trigger AS $$
BEGIN DELETE FROM owner_polygons_common_ownership_layer;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
I believe all I have accomplished is to delete all rows from the table, insert the new rows, then delete them again, because when I look at the table after the process ends I have zero rows. I used the FOR EACH STATEMENT clause because shp2sql created one INSERT statement.
My question is: Are triggers the way to go to accomplish this?
Your trigger function seems right.
However, I don't think this is the way to go: you cannot be sure that shp2pgsql produces a single statement.
If your shapefile grows, it could split your inserts in multiple statements.
So, if you can't use the -d option (that delete and recreate the table), I'd add a step to the process, between 1 and 2, to truncate the table.
You could also prepend the truncate statement in the generated sql file, or you can execute another psql command to truncate the table.

How to use a subquery as a database name in a DDL command?

I am wondering if it's possible to use the result of a subquery as database name in a PostgreSQL (9.5.1) DDL statement.
For example, I wanted to alter the current database with something like:
ALTER DATABASE (SELECT current_database()) SET a_var TO 'a_value';
If I run this, an error occurs:
ERROR: syntax error at or near "("
LINE 1: ALTER DATABASE (SELECT current_database()) SET ...
What's the correct way to use the sub-query (if possible)?
You need dynamic SQL for that:
DO
$do$
BEGIN
EXECUTE format($f$ALTER DATABASE %I SET x.a_var TO 'a_value'$f$, current_database());
END
$do$;
Using format() to escape the db name safely while being at it.
BTW, to unset:
ALTER DATABASE your_db RESET x.a_var;
To see the current setting:
SELECT current_setting('x.a_var');
(The DB default is not active before you start a new session.)
Related:
Table name as a PostgreSQL function parameter
Error when setting n_distinct using a plpgsql variable

Suppressing "Notice: Relation exists" when using "CREATE ... IF NOT EXISTS"

I have a function that creates a temporary table to store and process data. Problem is I want to run this function on the order of 1M times within a single transaction, without having:
NOTICE: relation "foo" already exists, skipping
output ~1M times. Is there an efficient way to do so?
What is not efficient:
Dropping the table instead
DROP TABLE IF EXISTS
Leads to running out of shared memory
Catching the duplicate_table exception (less efficient than using IF NOT EXISTS?)
BEGIN
CREATE TEMPORARY TABLE foo () ON COMMIT DROP;
EXCEPTION
WHEN duplicate_table THEN --do nothing
END;
As others have pointed out, the client_min_messages setting is what you want. There are a number of ways to configure this.
SET client_min_messages = warning or SELECT set_config('client_min_messages', 'warning', false) will persist for the rest of the current session/connection.
SET LOCAL client_min_messages = warning or SELECT set_config('client_min_messages', 'warning', true) resets at the end of the current transaction.
The CREATE FUNCTION statement's SET clause will scope the setting only to this function; this sounds like the most suitable option in your case. For example:
CREATE FUNCTION f()
RETURNS void
SET client_min_messages = warning
LANGUAGE plpgsql
AS ...
Simply
SET client_min_messages = error;
before running the statement(s).
You can also set it on the psql command line for batch executions
PGOPTIONS="-c client_min_messages=error" psql -f somefile.sql dbname