Re: Idiot's query: proper way to handle NULL in SELECT query?

Daniele Varrazzo <[email protected]> Mon, 10 May 2010 15:47:48 +0100
Newsgroups gmane.comp.python.db.psycopg.devel
Message-ID <[email protected]>
On Mon, May 10, 2010 at 2:50 AM, Frank Miles <[email protected]> wrote:

> I was hoping that I was missing some higher-level capability within
> psycopg, but if it's not there, and there is no realistic hope that it
> will be within its capabilities, I will continue to do what I have in
> the past: essentially rewriting the query depending on whether the
> value[s] were NULL/None or not.
>
> Thanks for helping me understand the limitations of psycopg.

I will tell you how to solve the problem. Then why the solution is
deeply flawed. Then how to solve it definitely.

There is an adapter trick you can apply: wrap the object into an
object that, when adapted, adds the operator for you.

    from psycopg2.extensions import adapt, AsIs

    class Eq(object):
        def __init__(self, wrapped):
            self.wrapped = wrapped

    def adapt_eq(eq):
        if eq.wrapped is None:
            return AsIs('IS NULL')
        else:
            return AsIs("= %s" % adapt(eq.wrapped))

    psycopg2.extensions.register_adapter(Eq, adapt_eq)

This does what you need:

    >>> cur.mogrify("select foo from blah where bar %s", (Eq("O'Reilly"),))
    "select foo from blah where bar = 'O''Reilly'"

    >>> cur.mogrify("select foo from blah where bar %s", (Eq(datetime.now()),))
    "select foo from blah where bar = '2010-05-09T11:23:09.977271'"

    >>> cur.mogrify("select foo from blah where bar %s", (Eq(None),))
    'select foo from blah where bar IS NULL'

This currently works fine, but it is actually a huge hack: adaptation
should be performed on parameter values only, here it generates a
bigger chunk of expression. If Psycopg started sending queries using
different libpq functions this trick would stop working (this switch
is not currently in program anyway). This is the same reason for which
an adapter to pass table/field names to a query ("select * from
%s"...) has never been included in Psycopg.

So what? There is actually a Postgres operator that does what you
need: "IS [NOT] DISTINCT FROM": it behaves like =/<> but treats NULLs
as regular values
(http://www.postgresql.org/docs/8.4/static/functions-comparison.html):

    test=> select 10 is not distinct from 10;
    ?column? | t

    test=> select null is not distinct from 10;
    ?column? | f

    test=> select null is not distinct from null;
    ?column? | t

Well, dusty corners of the documentation...

So, Psycopg has actually some limitations: that's because it is a
driver, not a complete solution to every postgres-related need and
doesn't deal with policies, only with syntax, allowing more high level
solutions to be built upon it. It is flexible enough to allow for some
nice trick, but probably shouldn't be taken too far. In your case
probably the DISTINCT operator is the best solution.

Ah, I think POLA stands for "Principle of Least Astonishment"
(http://www.c2.com/cgi/wiki?PrincipleOfLeastAstonishment)

-- Daniele