-
Notifications
You must be signed in to change notification settings - Fork 26
conform SQLCompiler.execute_sql() and results_iter() to Django's API #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
e5b019b
to
917d340
Compare
@@ -23,14 +23,18 @@ def execute_sql( | |||
# QuerySet.count() | |||
if self.query.annotations == {"__count": Count("*")}: | |||
return [self.get_count()] | |||
# Specify columns if there are any annotations so that annotations are | |||
# computed via $project. | |||
columns = self.get_columns() if self.query.annotations else None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we lost an optimization here where $project
is omitted unless needed (i.e. there are annotations).
For example, the first query in basic.tests.ModelLookupTest.test_equal_lookup
went from:
basic_article.aggregate([{'$match': {'$expr': {'$eq': ['$_id', ObjectId('668da533d38a1c8d3ee7708b')]}}}, {'$limit': 21}]);`
to
basic_article.aggregate([{'$match': {'$expr': {'$eq': ['$_id', ObjectId('668da57447faa4a8f994ebd1')]}}}, {'$project': {'_id': 1, 'headline': 1, 'pub_date': 1}}, {'$limit': 21}]);
We could fix it by making self.get_columns() if self.query.annotations else None
to build_query()
but there might be a better place to put it.
Adding a test to the Django fork for this at some point would probably be a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I added this new layer thinking that is not a big deal. If I change the code a bit, the old behavior is preserved. So, I will change this code in order to keep the previous behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how much it matters performance-wise, but particularly when debugging queries, I think it's cleaner not to include $project if it's not needed, as long as the solution for doing so doesn't get super complicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not. As you mention above, we need to pass a None to build query if not self.query.annotation. Running the test, will update soon.
django_mongodb/compiler.py
Outdated
@@ -43,37 +47,24 @@ def results_iter( | |||
Return an iterator over the results from executing query given | |||
to this compiler. Called by QuerySet methods. | |||
""" | |||
columns = self.get_columns() | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
chop blank line
result.append(value) | ||
if tuple_expected: | ||
result = tuple(result) | ||
result.append(obj.get(name, col.field.get_default())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you added the get_default()
bit back in a separate commit. Were there any test failures before that? The code came from the mongo engine fork and I've been skeptical of it. It seems it could be related to missing columns due to schema changes, but I'm not sure it's the behavior we want. Just wondering if you have any analysis to add.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, there was. A JSONField test, don't remember the name (not_provided wasn't a json). But basically I copied the logic from Django. It is not that deep like types changes or so.
Currently we are flagging those fields with NOT_PROVIDED, then we check if it not provided we assign the default, other case we apply converters. you can see the code here:
https://github.com/WaVEV/django-mongodb/blob/main/django_mongodb/compiler.py#L94
Now a default value is assigned and then don't care about the converters. The only thing to point out is: the code is running converters over default values. But Django does the same if I am not mistaken.
cb05fc7
to
4f52df0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well done!
In order to be compatible with other SQL backends,
execute_sql
andresults_iter
must be changed.The main idea is:
results_iter
will always take values fromexecute_sql
. It won't build queries.The base queries from Django call both methods:
Something results iter after
execute_sql
:We can see this on:
https://github.com/mongodb-forks/django/blob/mongodb-5.0.x/django/db/models/query.py#L91 and then in line 123.
Other times it uses
execute_sql
directly:https://github.com/mongodb-forks/django/blob/mongodb-5.0.x/django/db/models/sql/query.py#L615.
In other cases, it calls the
results_iter
without results. There are plenty of examples in the test cases and also in the Djangoresults_iter
logic.