-
-
Notifications
You must be signed in to change notification settings - Fork 33k
Fixed #11964 -- Added support for check constraints in model Meta. #7615
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
django/db/models/constraints.py
Outdated
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.
This won't work because I don't have access to a suitable compiler
. I will also need to add support for as_sql
to Q
objects.
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.
From #8056, it looks like I can do:
compiler = connection.ops.compiler('SQLCompiler')(None, connection, 'default')
f7f256c
to
89b2092
Compare
django/db/models/constraints.py
Outdated
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.
This gets me further. Unfortunately, I appear to need a more complete query
instead of None
, so Col.as_sql
can be evaluated.
tests/migrations/test_operations.py
Outdated
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.
Long term, I want to be able to use Q(pink__gt=2)
here, but for now using this avoids the need to implement Q.as_sql
.
tests/migrations/test_state.py
Outdated
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 haven't really thought whether this is the best way to fix these tests, but it is sufficient for now.
fc2f707
to
835f04e
Compare
I'm not sure where is best to put MySQL error handling. It supports the syntax, but doesn't actually do anything with it, so I think we should fail loud somewhere. Perhaps in a check? |
I'm also not sure how best to handle SQLite. It appears to support the feature, but I get a syntax error from SQLite with the syntax that works on postgres. I think this is because that uses Django does set |
835f04e
to
d928c31
Compare
@jarshwah @MarkusH I'd appreciate your feedback on this. There's definitely some work left to do - docs, MySQL, SQLite, naming consistency, any edge-cases I've missed - but I think the core is mostly there. I'd also like to add a test to check an |
50f4ae0
to
5e97e81
Compare
docs/ref/models/options.txt
Outdated
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.
should be 2.0
00ee601
to
505beef
Compare
44e6a30
to
76217f8
Compare
This now just needs to do something sensible for MySQL and the rest is cleanup and edge-cases. |
django/db/models/sql/query.py
Outdated
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 may be possible to simplify this further. I'm also not 100% certain I've thought of every edge-case, so I may have accidentally deleted something important.
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 feels odd why so much code needs to be duplicated. Have you considered letting Query
subclass this?
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 hadn't considered that. My main focus has been trying to get something that works well for this use case. Refactoring to allow code re-use is a secondary concern.
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.
This is a lot of stuff to duplicate, especially as you note below that you don't understand everything. I'm concerned that future expression refactors/features will be missed in constraints and things will asplode.
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.
Yeah - that's a good point. I'll take another pass and see if I can minimise the amount of duplicated code.
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 out of my depth here; my instinct would be to figure out what the responsibilities of Q / Query / etc. are, which bits are needed for constraints, and refactor to share these bits.
Malcolm's "Dungeon Master's guide to Django's ORM" talk may help.
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.
For example, _force_pk
(duplicated below) was removed in ec50937.
django/db/models/sql/query.py
Outdated
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've not put any significant effort into understanding this method yet, so it may well be possible to simplify.
tests/queries/test_query.py
Outdated
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.
Ideas for more tests to add here would be appreciated.
87f416f
to
f416afe
Compare
django/db/models/sql/query.py
Outdated
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.
kwargs = {'reuse': can_reuse, 'allow_joins': allow_joins}
if isinstance(value, F):
kwargs['simple_col'] = simple_col
value = value.resolve_expression(self, **kwargs)
Hi @Ian-Foote. Thanks for the extended effort here. Reviewing the history there are just a few small points outstanding:
I pulled in your test for the I was going to say, can we make the MySQL
Thus I think we're good there. I would push the test straight here but I didn't understand this:
Perhaps that's not relevant any more? Other than that, this looks super! |
Oh that's annoying - I didn't notice the 2.1 cut-off approaching or I'd have flagged this a bit earlier.
I don't remember where I said that, so I'm guessing it's not relevant any more. I'll make sure the outstanding points about documentation are resolved at the DjangoCon Europe sprints. |
django/db/backends/base/schema.py
Outdated
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 can use unpacking generalization instead of chain
, i.e.
sql = self.sql_create_table % {
'table': self.quote_name(model._meta.db_table),
'definition': ', '.join((*column_sqls, *constraints)),
}
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.
We probably can, but I don't think it's better.
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.
but it's faster because there's no function call overhead
In [1]: from itertools import chain
In [2]: a = ['1', '2']
In [3]: b = ['buckle', 'my', 'shoe']
In [4]: %timeit list(chain(a, b))
506 ns ± 2.19 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [5]: %timeit list((*a, *b))
313 ns ± 4.46 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
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.
Ok - I'll do it then.
django/db/models/constraints.py
Outdated
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.
path
is unused, so we can use a _
instead.
django/db/models/constraints.py
Outdated
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.
Please use hanging indentation.
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 quite clear what style you're recommending here - can you be more explicit?
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.
hanging = (
indentation,
has,
a,
newline,
after,
opening,
bracket,
)
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.
Ah, ok!
django/db/models/constraints.py
Outdated
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.
quote_name
is a temporary variable that's used only once, therefore I think we can remove it and use schema_editor.quote_name
directly, i.e.:
'name': schema_editor.quote_name(self.name),
django/db/models/constraints.py
Outdated
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.
using
argument is unused and IMO can be removed (the same in create_sql()
).
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.
This can be single-lined.
@carltongibson I think I've resolved the outstanding points now. I've included the test you linked, added the missing docs and fixed @felixxm's comments. |
65021bb
to
029c0b4
Compare
OK. Great stuff @Ian-Foote! I will have a look again this week. |
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.
Hi @Ian-Foote.
Thanks for making the changes.
I have a few questions which might mean we need to break it up a little.
It's seems there's three parts to it:
- The new tests on
Query
intests/queries/test_query.py
. - The introduction of
SimpleCol
- Adding the
CheckConstraint
(and related).
So the tests on Query
seem fine but largely unrelated. They reference SimpleCol
but could/should they not be introduced on their own? (It's the final file in the diff, so I got there and was like What's this doing here?)
Then SimpleCol
exists to introduce different SQL to Col
right?
- Is it worth a docstring explaining why it exists?
- Do we need some tests around this? (Neither
Col
norRef
make much appearance in the test suite...) Maybe around the_get_col()
helper and the changed (?) behaviour whensimple_col
isTrue
?
Finally the constraints stuff builds on this, and is great.
I guess my observation if just that, whilst the constraints changes are totally clear, the SimpleCol
addition (and Query
tests) are slightly unexplained. Why are they all in one commit, rather than 2 or 3, with maybe a few extra test cases, particularly on SimpleCol
?
Perhaps it's fine as is, but could you comment on that? 🙂
Ta. (Good stuff!)
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.
This looks great from where I'm standing. Thanks @Ian-Foote! We've talked about this quite a bit so I know the answers to most of the questions @carltongibson asked. But it doesn't hurt having them explained here / in the code.
I'm looking forward to this!
Yes. Just to emphasise, I totally agree: this is great. My queries are merely clarificatory. |
Thanks both!
These are a remnant from when I tried to introduce a simplified
Yes - specifically,
I think this is mostly covered by the additional tests on If you think any of this explanation would work well in a docstring somewhere, let me know and I'll add it. I'm also happy to add some more tests around |
Hey @Ian-Foote. Thanks for that. Super. I think, for me, if you could add a docstring to Beyond that, lets call it RFC. Good work. (Now can we just get forms to parse constraints for validation there...? 🙂) |
@carltongibson Docstring added. |
3429e63
to
c979ab4
Compare
🎉 Thanks for your great work and persistence, @Ian-Foote and everyone else 🎉 |
https://code.djangoproject.com/ticket/11964