#include "postgres.h"

#include "fmgr.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/analyze.h"
#include "utils/guc.h"

PG_MODULE_MAGIC;

void _PG_init(void);
static bool safeupdate_enabled;
static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL;

static void
#if PG_VERSION_NUM >= 190000
delete_needs_where_check(ParseState *pstate, Query *query, const JumbleState *jstate)
#else
delete_needs_where_check(ParseState *pstate, Query *query, JumbleState *jstate)
#endif
{
	ListCell *l;
	Query *ctequery;

	if (prev_post_parse_analyze_hook != NULL)
		(*prev_post_parse_analyze_hook)(pstate, query, jstate);

	if (IsBinaryUpgrade)
		return;

	if (!safeupdate_enabled)
		return;

	if (query->hasModifyingCTE)
	{
		foreach (l, query->cteList)
		{
			CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
			ctequery = castNode(Query, cte->ctequery);
			delete_needs_where_check(pstate, ctequery, jstate);
		}
	}

	switch (query->commandType)
	{
		case CMD_DELETE:
			Assert(query->jointree != NULL);
			if (query->jointree->quals == NULL)
				ereport(ERROR,
						(errcode(ERRCODE_CARDINALITY_VIOLATION),
						 errmsg("DELETE requires a WHERE clause")));
			break;
		case CMD_UPDATE:
			Assert(query->jointree != NULL);
			if (query->jointree->quals == NULL)
				ereport(ERROR,
						(errcode(ERRCODE_CARDINALITY_VIOLATION),
						 errmsg("UPDATE requires a WHERE clause")));
			break;
		default:
			break;
	}
}

void
_PG_init(void)
{
	DefineCustomBoolVariable("safeupdate.enabled",
							 "Enforce qualified updates",
							 "Prevent DML without a WHERE clause",
							 &safeupdate_enabled,
							 true,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);
	prev_post_parse_analyze_hook = post_parse_analyze_hook;
	post_parse_analyze_hook = delete_needs_where_check;
}
