Skip to content

Conversation

LilyFirefly
Copy link
Contributor

@LilyFirefly LilyFirefly commented Jun 5, 2021

Copy link
Member

@charettes charettes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the patch Ian! We've had recent questions about this exact usage cases.

I think we should defer the decision around __gt__ and friends and focus on making Lookup implement as much of the expression API as possible. That will not only pave the way for annotations but also direct usage in filter(...) and friends which can be useful under certain circumstances e.g. https://groups.google.com/g/django-developers/c/iiOnxAn3vy4

@LilyFirefly LilyFirefly force-pushed the ticket_27021 branch 3 times, most recently from 10fd9d6 to 3175ca5 Compare June 6, 2021 12:51
Copy link
Member

@felixxm felixxm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ian-Foote Great job, thanks 🚀

Release notes are missing. I would also

Can we add extra tests?

  • combined lookups, e.g. Exact(F('integer'), 42) | GreaterThan(F('integer'), 42),
  • using lookups directly in the QuerySet.filter(), e.g. .filter(GreaterThan(F('integer'), 42)),
  • using lookups in the Queryset.alias().

@LilyFirefly LilyFirefly force-pushed the ticket_27021 branch 2 times, most recently from 4ae8d11 to 0e7ef74 Compare June 8, 2021 21:17
Copy link
Member

@charettes charettes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the adjustments Ian!

@charettes
Copy link
Member

Note that the making Lookup(Expression) and implementing .resolve_expression will allow some simplification in Where.resolve_expression

@staticmethod
def _resolve_leaf(expr, query, *args, **kwargs):
if hasattr(expr, 'resolve_expression'):
expr = expr.resolve_expression(query, *args, **kwargs)
return expr
@classmethod
def _resolve_node(cls, node, query, *args, **kwargs):
if hasattr(node, 'children'):
for child in node.children:
cls._resolve_node(child, query, *args, **kwargs)
if hasattr(node, 'lhs'):
node.lhs = cls._resolve_leaf(node.lhs, query, *args, **kwargs)
if hasattr(node, 'rhs'):
node.rhs = cls._resolve_leaf(node.rhs, query, *args, **kwargs)
def resolve_expression(self, *args, **kwargs):
clone = self.clone()
clone._resolve_node(clone, *args, **kwargs)
clone.resolved = True
return clone

@LilyFirefly
Copy link
Contributor Author

@charettes I'm not clear what change you're suggesting in Where.resolve_expression.

@charettes
Copy link
Member

charettes commented Jun 16, 2021

@Ian-Foote not a big deal and it can be handled in a following the PR.

The idea is that Where.resolve_expression does a lot of duck-type digging where it could possibly simply call resolve_expression on all it's children now that Lookup.resolve_expression is implemented.

e.g. (still untested)

def resolve_expression(self, *args, **kwargs): 
    clone = self.clone()
    clone.children = [
        child.resolve_expression(*args, **kwargs) for child in clone.children
    ]
    clone.resolved = True
    return clone

@LilyFirefly
Copy link
Contributor Author

@charettes I just tested that and got a bunch of errors. I spent a little time trying to debug, but decided to leave it for now.

@charettes
Copy link
Member

@Ian-Foote sounds good, thanks for having a look!

@LilyFirefly
Copy link
Contributor Author

@felixxm Good call on the combined expressions test - that needed some more tweaks to WhereNode.

@LilyFirefly LilyFirefly force-pushed the ticket_27021 branch 2 times, most recently from 6b924a0 to 66aa6fd Compare June 16, 2021 22:49
Copy link
Member

@felixxm felixxm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ian-Foote Thanks for updates 👍 We're close 🚀. I pushed edits to fix failures on Oracle.

I would add a few more tests:

  • using combined lookups directly in QuerySet.filter(),
  • using annotation of combined lookups in QuerySet.filter(),
  • using combined lookups/lookups in QuerySet.aggregate().

@felixxm felixxm force-pushed the ticket_27021 branch 2 times, most recently from a496b71 to ca95e5a Compare June 24, 2021 07:41
@LilyFirefly
Copy link
Contributor Author

@felixxm I added those extra tests and fixed another edge-case. I'm not sure quite how to get the docs to build correctly.

Copy link
Member

@felixxm felixxm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ian-Foote Thanks for updates 👍 I skipped one test on Oracle, pushed small edits to docs, and left comments. Can we add one more test 😉? i.e. using lookups directly in Case().

@charettes
Copy link
Member

@felixxm I didn't look into details of the Oracle failure but lmk if you hit a wall.

@felixxm
Copy link
Member

felixxm commented Jun 28, 2021

@felixxm I didn't look into details of the Oracle failure but lmk if you hit a wall.

@Ian-Foote test_annotation_aggregate_with_m2o fails because book_contact_set__isnull is unnecessarily added to the GROUP BY clause:

SELECT
    "ANNOTATIONS_AUTHOR"."NAME",
    CASE WHEN "ANNOTATIONS_BOOK"."ID" IS NULL THEN 0 ELSE MAX(T4."PAGES") END AS "MAX_PAGES"
FROM
    "ANNOTATIONS_AUTHOR"
    LEFT OUTER JOIN "ANNOTATIONS_BOOK" ON ("ANNOTATIONS_AUTHOR"."ID" = "ANNOTATIONS_BOOK"."CONTACT_ID")
    LEFT OUTER JOIN "ANNOTATIONS_BOOK_AUTHORS" ON ("ANNOTATIONS_AUTHOR"."ID" = "ANNOTATIONS_BOOK_AUTHORS"."AUTHOR_ID")
    LEFT OUTER JOIN "ANNOTATIONS_BOOK" T4 ON ("ANNOTATIONS_BOOK_AUTHORS"."BOOK_ID" = T4."ID")
WHERE "ANNOTATIONS_AUTHOR"."AGE" < 30
GROUP BY
    "ANNOTATIONS_AUTHOR"."ID",
    "ANNOTATIONS_AUTHOR"."NAME",
    "ANNOTATIONS_AUTHOR"."AGE",
    CASE WHEN "ANNOTATIONS_BOOK"."ID" IS NULL THEN 1 ELSE 0 END

previously

SELECT
    "ANNOTATIONS_AUTHOR"."NAME",
    CASE WHEN "ANNOTATIONS_BOOK"."ID" IS NULL THEN 0 ELSE MAX(T4."PAGES") END AS "MAX_PAGES"
FROM
    "ANNOTATIONS_AUTHOR"
    LEFT OUTER JOIN "ANNOTATIONS_BOOK" ON ("ANNOTATIONS_AUTHOR"."ID" = "ANNOTATIONS_BOOK"."CONTACT_ID")
    LEFT OUTER JOIN "ANNOTATIONS_BOOK_AUTHORS" ON ("ANNOTATIONS_AUTHOR"."ID" = "ANNOTATIONS_BOOK_AUTHORS"."AUTHOR_ID")
    LEFT OUTER JOIN "ANNOTATIONS_BOOK" T4 ON ("ANNOTATIONS_BOOK_AUTHORS"."BOOK_ID" = T4."ID")
WHERE "ANNOTATIONS_AUTHOR"."AGE" < 30
GROUP BY
    "ANNOTATIONS_AUTHOR"."ID",
    "ANNOTATIONS_AUTHOR"."NAME",
    "ANNOTATIONS_AUTHOR"."AGE",
    "ANNOTATIONS_BOOK"."ID"

@felixxm
Copy link
Member

felixxm commented Jul 7, 2021

@Ian-Foote Do you have time to keep working on this? If not I can try to fix remaining comments.

@LilyFirefly
Copy link
Contributor Author

I had a look last night, but I didn't get anywhere (not having a working oracle setup was the biggest blocker).

@felixxm
Copy link
Member

felixxm commented Jul 7, 2021

I had a look last night, but I didn't get anywhere (not having a working oracle setup was the biggest blocker).

You could check other comments and leave an Oracle issue to me 🕵️

@LilyFirefly
Copy link
Contributor Author

Yeah, I'll do that now.

@felixxm
Copy link
Member

felixxm commented Jul 7, 2021

Yeah, I'll do that now.

Great, thanks ❤️

@LilyFirefly
Copy link
Contributor Author

@felixxm All yours ;)

@felixxm
Copy link
Member

felixxm commented Jul 8, 2021

@Ian-Foote I fixed Oracle failure by adding get_group_by_cols() ☺️

@felixxm felixxm changed the title Fixed #27021 -- Support annotating lookups Fixed #27021 -- Allowed lookup expressions in annotations, aggregations, and QuerySet.filter(). Jul 8, 2021
@felixxm
Copy link
Member

felixxm commented Jul 8, 2021

@Ian-Foote Thanks for updates 👍 We are almost there 🏃

I rebased, squashed commits, fix tests failures on Oracle, added new tests test_lookup_in_order_by, test_conditional_expression, test_filter_lookup_lhs and fixed related failures on Oracle, and pushed small edits.

Unfortunately test_filter_lookup_lhs crashes on PostgreSQL 😭 , it's an issue similar to the 170b006. It seems that LHS should be wrapped in parentheses to respect operator precedence 🤔 (\cc @charettes).

@felixxm
Copy link
Member

felixxm commented Jul 9, 2021

Unfortunately test_filter_lookup_lhs crashes on PostgreSQL sob , it's an issue similar to the 170b006. It seems that LHS should be wrapped in parentheses to respect operator precedence thinking (\cc @charettes).

Fixed 🥳

Comment on lines +97 to +99
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if a crash similar to 170b006 can happen if the lhs is a Subquery annotation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Challenge accepted 😉 I added two more tests test_filter_exists_lhs and test_filter_subquery_lhs.

…ns, and QuerySet.filter().

Thanks Hannes Ljungberg and Simon Charette for reviews.

Co-authored-by: Mariusz Felisiak <[email protected]>
@felixxm felixxm merged commit f42ccdd into django:main Jul 9, 2021
@LilyFirefly LilyFirefly deleted the ticket_27021 branch July 9, 2021 14:50
@LilyFirefly
Copy link
Contributor Author

Thanks for getting this over the line @felixxm and thanks @charettes, @hannseman and @pauloxnet for the reviews!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants