I am trying to replicate the IF function from MySQL into PostgreSQL.
The syntax of IF function is IF(condition, return_if_true, return_if_false)
I created following formula:
CREATE OR REPLACE FUNCTION if(boolean, anyelement, anyelement)
RETURNS anyelement AS $$
BEGIN
CASE WHEN ($1) THEN
RETURN ($2);
ELSE
RETURN ($3);
END CASE;
EXCEPTION WHEN division_by_zero THEN
RETURN ($3);
END;
$$ LANGUAGE plpgsql;
It works well with most of the things like if(2>1, 2, 1) but it raises an error for:
if( 5/0 > 0, 5, 0)
fatal error division by zero.
In my program I can’t check the denominator as the condition is provided by user.
Is there any way around? Maybe if we can replace first parameter from boolean to something else, as in that case the function will work as it will raise and return the exception.
PostgreSQL is following the standard
This behaviour appears to be specified by the SQL standard. This is the first time I’ve seen a case where it’s a real problem, though; you usually just use a
CASEexpression or a PL/PgSQLBEGIN ... EXCEPTIONblock to handle it.MySQL’s default behaviour is dangerous and wrong. It only works that way to support older code that relies on this behaviour. It has been fixed in newer versions when strict mode is active (which it absolutely always should be) but unfortunately has not yet been made the default. When using MySQL, always enable
STRICT_TRANS_TABLESorSTRICT_ALL_TABLES.ANSI-standard zero division is a pain sometimes, but it’ll also protect against mistakes causing data loss.
SQL injection warning, consider re-design
If you’re executing expressions from the user then you quite likely have SQL injection problems. Depending on your security requirements you might be able to live with that, but it’s pretty bad if you don’t totally trust all your users. Remember, your users could be tricked into entering the malicious code from elsewhere.
Consider re-designing to expose an expression builder to the user and use a query builder to create the SQL from the user expressions. This would be much more complicated, but secure.
If you can’t do that, see if you can parse the expressions the user enters into an abstract syntax, validate it before execution, and then produce new SQL expressions based on the parsed expression. That way you can at least limit what they can write, so they don’t slip any nasties into the expression. You can also rewrite the expression to add things like checks for zero division. Finding (or writing) parsers for algebraic expressions isn’t likely to be hard, but it’ll depend on what kinds of expressions you need to let users write.
At minimum, the app needs to be using a role (“user”) that has only
SELECTprivileges on the tables, is not a superuser, and does not own the tables. That’ll minimise the harm any SQL injection will cause.CASE won’t solve this problem as written
In any case, because you currently don’t validate and can’t inspect the expression from the user, you can’t use the SQL-standard
CASEstatement to solve this. Forif( a/b > 0, a, b)you’d usually write something like:This explicitly handles the zero denominator case, but is only possible when you can break the expression up.
Ugly workaround #1
An alternative solution would be to get Pg to return a placeholder instead of raising an exception for division by zero by defining a replacement division operator or function. This will only solve the divide-by-zero case, not others.
I wanted to return
'NaN'as that’s the logical result. Unfortunately, ‘NaN’ is greater than numbers not less then, and you want a less-than or false-like result.This means we have to use the icky hack of returning NULL instead:
with usage:
Your app can rewrite ‘/’ in incoming expressions into
@/@or whatever operator name you choose pretty easily.There’s one pretty critical problem with this approach, and that’s that
@/@will have different precedence to/so expressions without explicit parentheses may not be evaluated as you expect. You might be able to get around this by creating a new schema, defining an operator named/in that schema that does your null-on-error trick, and then adding that schema to yoursearch_pathbefore executing user expressions. It’s probably a bad idea, though.Ugly workaround #2
Since you can’t inspect the denominator, all I can think of is to wrap the whole thing in a
DOblock (Pg 9.0+) or PL/PgSQL function and catch any exceptions from the evaluation of the expression.Erwin’s answer provides a better example of this than I did, so I’ve removed this. In any case, this is an awful and dangerous thing to do, do not do it. Your app needs to be fixed.