Skip to content

PostgreSQLQueryBuilder returns invalid query with no columns to update #832

@hfaua

Description

@hfaua

Consider following code:

from pypika import PostgreSQLQuery as Query
from pypika import Table
t = Table("t")
q1 = Query.update(t).where(t.id == 1)
q2 = Query.update(t).where(t.id == 1).returning(t.star)

Those queries will result in following strings

>>> str(q1)
''
>>> str(q2)
' RETURNING "t".*'

The reason for such behaviour is that in PostgreSQLQueryBuilder.get_sql there is no check if super(PostgreSQLQueryBuilder, self).get_sql() returns an empty string.

Rationale:
You may be wondering if such a query is even valid and I would argue that it is and has some usages. My arguments are as follow:

  1. When building an update query conditionally, it can happen that set() wouldn't be called because eg. a request wouldn't perform any update. See code below:
query = Query.update(t)
if param1 is not None:
    query = query.set(t.param1, param1)
if param2 is not None:
    query = query.set(t.param2, param2)

return execute(query.where(t.id = 1).returning(t.star))

In the above example te most elegant way IMO is to return an empty string for such query. Alternatives would be to either inspect private members QueryBuilder._updates (no-no) or checking if at least one parameter is not None (too much unnecessary code)

  1. Behaviour of QueryBuilder.get_sql for an empty update query is defined in such a way that if no column is updated, then an empty string is returned. I think that for the sake of consistency, the implementation of PostgreSQLQueryBuilder should follow the same behaviour.

Proposal:
If you agree with me and I have your blessing, I can implement the fix. My fix would return empty string if super(PostgreSQLQueryBuilder, self).get_sql() returns an empty string. From what I can see, very similar situation also happens with PostgreSQLQueryBuilder.on_conflict, so this case will also be covered.

Three unit tests for the following queries will be added:

q1 = Query.update(t).where(t.id == 1).returning(t.star)
q2 = Query.into(t).on_conflict(t.a).do_nothing()
q3 = Query.into(t).returning(t.star)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions