diff --git a/contrib/Makefile b/contrib/Makefile index 25263c0..4bd456f 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -40,6 +40,7 @@ SUBDIRS = \ pgstattuple \ pg_visibility \ postgres_fdw \ + require_where \ seg \ spi \ tablefunc \ diff --git a/contrib/require_where/Makefile b/contrib/require_where/Makefile new file mode 100644 index 0000000..731f9fb --- /dev/null +++ b/contrib/require_where/Makefile @@ -0,0 +1,15 @@ +MODULE_big = require_where +OBJS = require_where.o + +PGFILEDESC = 'require_where - require DELETE and/or UPDATE to have a WHERE clause' + +ifdef USE_PGXS + PG_CONFIG = pg_config + PGXS = $(shell $(PG_CONFIG) --pgxs) + include $(PGXS) +else + subdir = contrib/require_where + top_builddir = ../.. + include $(top_builddir)/src/Makefile.global + include $(top_builddir)/contrib/contrib-global.mk +endif diff --git a/contrib/require_where/require_where.c b/contrib/require_where/require_where.c new file mode 100644 index 0000000..556101a --- /dev/null +++ b/contrib/require_where/require_where.c @@ -0,0 +1,81 @@ +/* + * -------------------------------------------------------------------------- + * + * require_where.c + * + * Copyright (C) 2016, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/require_where/require_where.c + * + * -------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "fmgr.h" + +#include "parser/analyze.h" + +#include "utils/elog.h" +#include "utils/guc.h" + +PG_MODULE_MAGIC; + +void _PG_init(void); + +static post_parse_analyze_hook_type original_post_parse_analyze_hook = NULL; +static bool delete_requires_where = false; +static bool update_requires_where = false; + +static void +requires_where_check(ParseState *pstate, Query *query) +{ + + if (delete_requires_where && query->commandType == CMD_DELETE) + { + Assert(query->jointree != NULL); + if (query->jointree->quals == NULL) + ereport(ERROR, + (errcode(ERRCODE_CARDINALITY_VIOLATION), + errmsg("DELETE requires a WHERE clause"), + errhint("To delete all rows, use \"WHERE true\" or similar"))); + } + + if (update_requires_where && query->commandType == CMD_UPDATE) + { + Assert(query->jointree != NULL); + if (query->jointree->quals == NULL) + ereport(ERROR, + (errcode(ERRCODE_CARDINALITY_VIOLATION), + errmsg("UPDATE requires a WHERE clause"), + errhint("To update all rows, use \"WHERE true\" or similar"))); + } + + if (original_post_parse_analyze_hook != NULL) + (*original_post_parse_analyze_hook) (pstate, query); +} + +void +_PG_init(void) +{ + DefineCustomBoolVariable("requires_where.delete", + "Require every DELETE statement to have a WHERE clause.", + NULL, + &delete_requires_where, + false, + PGC_USERSET, + false, + NULL, NULL, NULL); + + DefineCustomBoolVariable("requires_where.update", + "Require every UPDATE statement to have a WHERE clause.", + NULL, + &update_requires_where, + false, + PGC_USERSET, + false, + NULL, NULL, NULL); + + original_post_parse_analyze_hook = post_parse_analyze_hook; + post_parse_analyze_hook = requires_where_check; +} diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index c8708ec..48ca717 100644 --- a/doc/src/sgml/contrib.sgml +++ b/doc/src/sgml/contrib.sgml @@ -135,6 +135,7 @@ CREATE EXTENSION module_name FROM unpackaged; &pgtrgm; &pgvisibility; &postgres-fdw; + &require-where; &seg; &sepgsql; &contrib-spi; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 4383711..737565e 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -140,6 +140,7 @@ + diff --git a/doc/src/sgml/require-where.sgml b/doc/src/sgml/require-where.sgml new file mode 100644 index 0000000..b5cb309 --- /dev/null +++ b/doc/src/sgml/require-where.sgml @@ -0,0 +1,91 @@ + + + + require-where + + + require_where + + + + This module allows a superuser to require that WHERE clauses on + either DELETE or UPDATE not be empty using a custom GUC to control + each. + + + + To use this module, you must include require_where + in the parameter. + + + + Here is an example showing how to set up a database cluster with + require_where. + +$ psql -U postgres +# SHOW shared_preload_libraries; /* Make sure not to clobber something by accident */ + +If you found something, +# ALTER SYSTEM SET shared_preload_libraries='the,stuff,you,found,require_where'; + +Otherwise, +# ALTER SYSTEM SET shared_preload_libraries='require_where'; + +Then restart PostgreSQL + + + + + + GUC Parameters + + + + + require_where.delete (boolean) + + require_where.delete configuration parameter + + + + + This parameter defaults to false. When set + to true, every DELETE requires + some kind of WHERE clause in order to + execute. When DELETE is meant to span the + entire relation, one can issue a WHERE true + or equivalent. + + + + + + require_where.update (boolean) + + require_where.update configuration parameter + + + + + This parameter defaults to false. When set + to true, every UPDATE requires + some kind of WHERE clause in order to + execute. When UPDATE is meant to span the + entire relation, one can issue a WHERE true + or equivalent. + + + + + + + + Authors + + + David Fetter david@fetter.org + Robert Haas robertmhaas@gmail.com + Andrew Gierth andrew@tao11.riddles.org.uk + + +