-
-
Notifications
You must be signed in to change notification settings - Fork 32.7k
Fixed #35444 -- Added generic support for Aggregate.order_by #18361
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
@charettes I've made some progress on the suggestions. I have the three phases broken out into unique commits and have added some of the documentation and copied the tests from the postgres module into a new class that should run against all of the database backends. I ran into two issues in the process that I wanted to get your opinion on.
As a side note, I named it |
Hello @camuthig, thanks for spinning this up!
It's possible CI might not be be setup here but if you're a *nix setup you can use
Well that's odd for sure.
Usually when we run into these edge cases we add a database feature (e.g.
I think |
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.
Thanks for the patch @camuthig!
I left a few comments and pointers. My main points of feedbacks are
delimiter
should not be wrapped in aValue
as that prevent references to columns and normally string arguments are considered to beF
'ield references.- I appreciate the attention to testing but we should focus them on
StringAgg
behaviour andorder_by
support. Did you notice anything special when subqueries were involved?
warnings.warn( | ||
"The PostgreSQL specific StringAgg function is deprecated. Use " | ||
"django.db.models.aggregate.StringAgg instead.", | ||
category=RemovedInDjango60Warning, |
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 might have to adjust the stacklevel
here so the warning points at the location of StringAgg
instantiation. I think stacklevel=1
is the right one?
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'll need stacklevel=1
at the very least?
django/db/models/aggregates.py
Outdated
def as_sql(self, compiler, connection, **extra_context): | ||
return super().as_sql(compiler, connection, **extra_context) |
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 this one can go?
tests/aggregation/tests.py
Outdated
class StringAggTestModel(Model): | ||
char_field = CharField(max_length=30, blank=True) | ||
text_field = TextField(blank=True) | ||
json_field = JSONField(null=True) | ||
|
||
|
||
class StatTestModel(Model): | ||
int1 = IntegerField() | ||
int2 = IntegerField() | ||
related_field = ForeignKey(StringAggTestModel, SET_NULL, null=True) |
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.
Try reuse the existing models defined in aggregation/models.py
instead. We can't add new tables/models for each new features otherwise the suite slowly gets slower.
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.
These tests are taken directly from the PostgreSQL specific of these tests. My thoughts were
- We are deprecating that class in 6.0, so we will delete all of the StringAgg tests from that module at that time
- Those tests exist for a reason, even if I am not familiar with them, so I didn't want to drop any and cause a regression
This is why I have some tests that may seem very specific, like the subqueries, and why I went about defining these models. It kept me from re-implementing the same behaviors in a way that might cause a regression. However, I am open to using the models already defined in aggregation/models.py
, I'll just need to sit with the data a bit to come up with the right expectations. I think it might be best to keep the tests, like the subqueries, in place to make sure we maintain the coverage. If you think those behaviors are covered in other tests, though, then I'm open to cleaning them up.
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.
That makes sense, if they already exist we should definitely just port them over.
However, I am open to using the models already defined in aggregation/models.py
If you could port them while adapting them to use the existing models that would be awesome.
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.
Also, if we're porting them over we should delete the original ones from postgres_tests
given postgres.aggregates.StringAgg
will only a shim at this point.
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 did some work to merge the tests into the existing AggregateTestCase
, keeping some of the more esoteric ones specific to the StringAgg
behaviors but making them work with the existing data/models. I had to add a JSON field to the existing models to that end, but otherwise, I think it works pretty well.
tests/aggregation/tests.py
Outdated
def test_default_argument(self): | ||
StringAggTestModel.objects.all().delete() | ||
tests = [ | ||
(StringAgg("char_field", delimiter=";", default="<empty>"), "<empty>"), |
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 would expect this to crash as default
must be passed as Value
? Passing str
to Func
should normally attempt resolve to field references (e.g. `"" -> F("")") and crash if missing.
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.
delimiter should not be wrapped in a Value as that prevent references to columns and normally string arguments are considered to be
F
'ield references.
The Value
wrapper on a string comes right from the old implementation in the Postgres-specific version. Would it make more sense to allow for the string to come through and wrap it as a value for 5.2 with a deprecation warning in 6? That does break the ability to use a string as an alias for F
for 5.2, though.
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 I have something together that wraps the __init__
from the Postgres version of the StringAgg
class and does the Value(str(delimiter))
and logs a deprecation warning. The main StringAgg
can then expect an Expression
of some sort.
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 I have something together that wraps the init from the Postgres version of the StringAgg class and does the Value(str(delimiter)) and logs a deprecation warning. The main StringAgg can then expect an Expression of some sort.
That's perfect, using the deprecation period for postgres.StringAgg
to models.StringAgg
seems like a great way to normalize this behavior 🎉
636e617
to
3280e3e
Compare
@charettes I think I have updates for all of your comments at this phase. I was able to build a custom python environment with pyenv and brew using guidance here and test the group concat and ordering behaviors with a more recent version of sqlite, so that is cool. I know there are some conflicts and I can work on cleaning those up. I'm also not totally sure what is wrong with the docs. I ran the same command on my own machine, and everything seems to build properly. |
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 looking great, I left a few comments regarding null handling and a few other things.
I like that you started to breakdown the commits I may suggest a different order and naming scheme.
Refs #35444 -- Deprecated contrib.postgres aggregates ordering for order_by.
(Explain the reasoning to better align with Window.order_by and plans to move support for Aggregate)
Fixed #35444 -- Add generic support for Aggregate.order_by.
(Explain that this now supersedes the contrib.postgres support and introduces StringAgg to exercise this support)
^^ this commit should make OrderableAggMixin
unused.
Refs #35444 -- Deprecated contrib.postgres.OrderableAggMixin
(Explain that it serves no purposes now that Aggregate supports order_by but it is kept around for a deprecation cycle as some users might be relying on it)
"String delimiters will be converted to F statements instead of Value" | ||
"statements. Explicit Value instances should be used instead.", |
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.
"String delimiters will be converted to F statements instead of Value" | |
"statements. Explicit Value instances should be used instead.", | |
"delimiter: str will be resolved as a field reference instead of a string literal" | |
f"on Django 6.0. Pass `delimiter=Value({delimiter!r})` to preserve the previous ." | |
"behaviour.", |
warnings.warn( | ||
"The PostgreSQL specific StringAgg function is deprecated. Use " | ||
"django.db.models.aggregate.StringAgg instead.", | ||
category=RemovedInDjango60Warning, |
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'll need stacklevel=1
at the very least?
django/db/models/expressions.py
Outdated
elif isinstance(param, (BaseExpression, str, F)): | ||
return cls(param) |
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.
elif isinstance(param, (BaseExpression, str, F)): | |
return cls(param) | |
elif isinstance(param, str) or hasattr(param, "resolve_expression"): | |
return cls(param) |
tests/aggregation/tests.py
Outdated
# Different engines treat null STRING_AGG differently, so excluding it for | ||
# consistency. |
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.
Could you elaborate on that?
I seems that SQLite, Postgres, MySQL, and Oracle all return NULL
which should translate to None
?
Did you possibly run into issues because of a lack of ordering on the returned value and the fact some backends default to NULLS LAST
instead of NULLS FIRST
?
If that's the case you should use assertQuerySetEqual(..., ordered=False)
or specify an order_by(OrderBy('agg', nulls_last=True))
.
I think it'd be good to include at least one tests that ensures that None
is returned on an empty not-NULL set.
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.
You are right, I was reading the test results wrong. I went with assertQuerySetEqual(..., ordered=False)
to resolve this.
tests/aggregation/tests.py
Outdated
""" | ||
This test is based on tests taken from existing PostgreSQL specific tests and | ||
kept to avoid regressions as StringAgg is ported to the shared database module. | ||
""" |
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 test is based on tests taken from existing PostgreSQL specific tests and | |
kept to avoid regressions as StringAgg is ported to the shared database module. | |
""" |
def test_ordering_warns_of_deprecation(self): | ||
msg = "The ordering argument is deprecated. Use order_by instead." | ||
with self.assertWarnsMessage(RemovedInDjango60Warning, msg): | ||
values = AggregateTestModel.objects.aggregate( | ||
arrayagg=ArrayAgg("integer_field", ordering=F("integer_field").desc()) | ||
) | ||
self.assertEqual(values, {"arrayagg": [2, 1, 0, 0]}) |
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'll need also need tests for the creation of postgres.StringAgg
.
with self.assertWarnsMessage(RemovedInDjango60Warning, msg): | ||
values = AggregateTestModel.objects.aggregate( | ||
arrayagg=ArrayAgg("integer_field", ordering=F("integer_field").desc()) | ||
) | ||
self.assertEqual(values, {"arrayagg": [2, 1, 0, 0]}) |
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.
The following should ensure that the proper stacklevel
is passed to warnings.warn
.
with self.assertWarnsMessage(RemovedInDjango60Warning, msg): | |
values = AggregateTestModel.objects.aggregate( | |
arrayagg=ArrayAgg("integer_field", ordering=F("integer_field").desc()) | |
) | |
self.assertEqual(values, {"arrayagg": [2, 1, 0, 0]}) | |
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) ctx: | |
values = AggregateTestModel.objects.aggregate( | |
arrayagg=ArrayAgg("integer_field", ordering=F("integer_field").desc()) | |
) | |
self.assertEqual(values, {"arrayagg": [2, 1, 0, 0]}) | |
self.assertEqual(ctx.filename, __file__) |
333e523
to
7f8c533
Compare
@charettes I have update the code to hit all of your comments and get the linters passing. I will take a dive into the failing tests shortly. At least some of them appear to be because we have moved the |
The following should do diff --git a/django/db/models/aggregates.py b/django/db/models/aggregates.py
index 6cf0bd9a60..d070944039 100644
--- a/django/db/models/aggregates.py
+++ b/django/db/models/aggregates.py
@@ -29,6 +29,10 @@ class AggregateFilter(Func):
arity = 1
template = " FILTER (WHERE %(expressions)s)"
+ @property
+ def condition(self):
+ return self.source_expressions[0]
+
def as_sql(self, compiler, connection, **extra_context):
if not connection.features.supports_aggregate_filter_clause:
raise NotSupportedError
@@ -187,7 +191,7 @@ def as_sql(self, compiler, connection, **extra_context):
copy = self.copy()
copy.filter = None
source_expressions = copy.get_source_expressions()
- condition = When(self.filter, then=source_expressions[0])
+ condition = When(self.filter.condition, then=source_expressions[0])
copy.set_source_expressions([Case(condition)] + source_expressions[1:])
return copy.as_sql(compiler, connection, **extra_context) The problem was that we were building the fallback |
3f5a19c
to
26fa18a
Compare
@charettes thanks for the recommendation. It works great. I spent time digging into the other failures in MySQL and determined that the MySQL implementation of First, it allows for multiple expressions, which we can ignore for now. The bigger issues are that MySQL doesn't support filtering on aggregates and it uses a different format order between the delimiter and ordering. Sqlite: So here, if you want to introduce a delimiter you MUST have a different template, which is solvable. The bigger issue is that the order matters between This was further compounded by the fact MySQL doesn't support aggregate filtering and we are forcing a Do you think there are some level of feature flags we can throw on MySQL to make this easier? Or maybe allow for engine-specific default separators to be ignored in our queries? I have a commit with some experimentation here: cbe5012 |
34c8fe0
to
cbe5012
Compare
Thanks for pushing a commit demonstrating the scope of the problem. Looking at it my immediate reaction would be to avoid over complicating
I'm not sure I'm understanding what you mean here. Now that the MySQL tests are passing I'll give Oracle a test run as well. |
buildbot, test on oracle. |
@charettes do you have recommendations on how to test the Oracle engine myself? I am having trouble with the Oracle Docker images. Maybe cause I am on a M1 Mac? I'm not sure. It looks like a pretty common errors, so it seems some change I have made at a base level is causing the compiled queries to be incorrect but I can't really tell why without running against the database. |
@camuthig I usually try two approaches on my M1
I really wish CI ran with Any suggestions @felixxm? |
I managed to get tests running on Oracle here's the non-localized error message
Executed SQL SELECT "AGGREGATION_BOOK"."ID" AS "PK",
"AGGREGATION_BOOK"."ISBN" AS "ISBN",
AVG("AGGREGATION_AUTHOR"."AGE") AS "MEAN_AGE"
FROM "AGGREGATION_BOOK"
LEFT OUTER JOIN "AGGREGATION_BOOK_AUTHORS" ON ("AGGREGATION_BOOK"."ID" = "AGGREGATION_BOOK_AUTHORS"."BOOK_ID")
LEFT OUTER JOIN "AGGREGATION_AUTHOR" ON ("AGGREGATION_BOOK_AUTHORS"."AUTHOR_ID" = "AGGREGATION_AUTHOR"."ID")
WHERE "AGGREGATION_BOOK"."ID" = 1
GROUP BY "AGGREGATION_BOOK"."ID",
"AGGREGATION_BOOK"."ISBN",
"AGGREGATION_BOOK"."NAME",
"AGGREGATION_BOOK"."PAGES",
"AGGREGATION_BOOK"."RATING",
"AGGREGATION_BOOK"."PRICE",
"AGGREGATION_BOOK"."CONTACT_ID",
"AGGREGATION_BOOK"."PUBLISHER_ID",
"AGGREGATION_BOOK"."PUBDATE",
"AGGREGATION_BOOK"."PRINT_INFO" Executed SQL on SELECT "AGGREGATION_BOOK"."ID" AS "PK",
"AGGREGATION_BOOK"."ISBN" AS "ISBN",
AVG("AGGREGATION_AUTHOR"."AGE") AS "MEAN_AGE"
FROM "AGGREGATION_BOOK"
LEFT OUTER JOIN "AGGREGATION_BOOK_AUTHORS" ON ("AGGREGATION_BOOK"."ID" = "AGGREGATION_BOOK_AUTHORS"."BOOK_ID")
LEFT OUTER JOIN "AGGREGATION_AUTHOR" ON ("AGGREGATION_BOOK_AUTHORS"."AUTHOR_ID" = "AGGREGATION_AUTHOR"."ID")
WHERE "AGGREGATION_BOOK"."ID" = 21
GROUP BY "AGGREGATION_BOOK"."ID",
"AGGREGATION_BOOK"."ISBN",
"AGGREGATION_BOOK"."NAME",
"AGGREGATION_BOOK"."PAGES",
"AGGREGATION_BOOK"."RATING",
"AGGREGATION_BOOK"."PRICE",
"AGGREGATION_BOOK"."CONTACT_ID",
"AGGREGATION_BOOK"."PUBLISHER_ID",
"AGGREGATION_BOOK"."PUBDATE" Seems like |
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.
Oracle failures were due to your addition of the print_info
field.
tests/aggregation/models.py
Outdated
@@ -30,6 +30,7 @@ class Book(models.Model): | |||
contact = models.ForeignKey(Author, models.CASCADE, related_name="book_contact_set") | |||
publisher = models.ForeignKey(Publisher, models.CASCADE) | |||
pubdate = models.DateField() | |||
print_info = models.JSONField(null=True, default=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.
@camuthig this is the source of all your Oracle test failures...
Django uses a NCLOB
column to persists JSONField
and Oracle doesn't allow to GROUP BY
a NCLOB
field.
I don't think the usage of a JSONField
is mandatory to test this feature so I'd suggest using a different field type instead.
tests/aggregation/tests.py
Outdated
@skipUnlessDBFeature("supports_json_field", "supports_aggregate_order_by_clause") | ||
def test_string_agg_jsonfield_order_by(self): | ||
values = Book.objects.aggregate( | ||
stringagg=StringAgg( | ||
KeyTextTransform("lang", "print_info"), | ||
delimiter=Value(","), | ||
order_by=KeyTextTransform("lang", "print_info"), |
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 guess this was ported from the Postgres tests.
I suggest creating a distinct model for this particular case instead.
The "easiest" way is to use VM provided by Oracle. |
2322b98
to
ca4efc0
Compare
def test_ordering_and_order_by_causes_error(self): | ||
with self.assertWarns(RemovedInDjango61Warning): | ||
with warnings.catch_warnings(record=True, action="always") as wm: |
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.
Things got a little weird here when I update the warnings to use 7.0. The issue being that interacting with the class this way triggers both a 6.1 warning (for the order_by
usage) and 7.0 warnings (for using the PostgreSQL StringAgg). I couldn't get assertWarns
to work with both getting thrown so dug in and tried using the underlying behaviors of it to get it down.
Let me know if there is a better way to write this, and I will refactor it.
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 the way you did it is great, it's a peculiar case given it triggers two distinct warnings of different categories 👍
ca4efc0
to
a8ab0e8
Compare
I think everything should be up to date and marked as deprecated as of 6.0 and to be removed in 7.0. I updated the documentation as requested as well. I ran into testing issues around the layers of deprecation warnings. I have a working solution for it, but I'm not sure it is the right pattern. |
a8ab0e8
to
0b586c4
Compare
buildbot, test on oracle. |
8115db1
to
5f75fc9
Compare
@camuthig I have checked the deprecations and rebased 👍 --- a/django/db/models/aggregates.py
+++ b/django/db/models/aggregates.py
@@ -169,7 +169,7 @@ class Aggregate(Func):
@property
def default_alias(self):
expressions = [
- expr for expr in self.get_source_expressions() if expr is not None
+ expr for expr in self.get_source_expressions() if expr
]
if len(expressions) == 1 and hasattr(expressions[0], "name"):
return "%s__%s" % (expressions[0].name, self.name.lower())
diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py
index 68e5d2667e..b11147987a 100644
--- a/django/db/models/expressions.py
+++ b/django/db/models/expressions.py
@@ -298,7 +298,7 @@ class BaseExpression:
source_expressions = [
(
expr.resolve_expression(query, allow_joins, reuse, summarize)
- if expr is not None
+ if expr
else None
)
for expr in c.get_source_expressions() But I think @charettes might be best placed to advise here |
I should be able to have a look at it shortly. Looks at the test failures I assume this is happening because some aggregate is stashing The |
django/db/models/aggregates.py
Outdated
self.order_by = order_by | ||
self.default = default | ||
self.order_by = order_by and AggregateOrderBy.from_param( | ||
f"{self.__class__.__name__}.order_by", order_by | ||
) |
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.
Something when wrong with the rebase here, we assign to self.order_by
twice?
@sarahboyce I haven't figured out in which commit these should be folded but here are the required changes to get the tests passing diff --git a/django/contrib/postgres/aggregates/mixins.py b/django/contrib/postgres/aggregates/mixins.py
index a6849c3930..8d3e40177b 100644
--- a/django/contrib/postgres/aggregates/mixins.py
+++ b/django/contrib/postgres/aggregates/mixins.py
@@ -16,7 +16,7 @@ def __init__(self, *expressions, ordering=(), order_by=(), **extra):
if order_by:
raise TypeError("Cannot specify both order_by and ordering.")
order_by = ordering
-
+ order_by = order_by or None
super().__init__(*expressions, order_by=order_by, **extra)
diff --git a/django/db/models/aggregates.py b/django/db/models/aggregates.py
index 8b644e3599..48e00aa946 100644
--- a/django/db/models/aggregates.py
+++ b/django/db/models/aggregates.py
@@ -95,7 +95,6 @@ def __init__(
self.distinct = distinct
self.filter = filter and AggregateFilter(filter)
- self.order_by = order_by
self.default = default
self.order_by = order_by and AggregateOrderBy.from_param(
f"{self.__class__.__name__}.order_by", order_by |
We might want to also consider having diff --git a/django/db/models/aggregates.py b/django/db/models/aggregates.py
index 8b644e3599..a791593508 100644
--- a/django/db/models/aggregates.py
+++ b/django/db/models/aggregates.py
@@ -95,9 +95,8 @@ def __init__(
self.distinct = distinct
self.filter = filter and AggregateFilter(filter)
- self.order_by = order_by
self.default = default
- self.order_by = order_by and AggregateOrderBy.from_param(
+ self.order_by = AggregateOrderBy.from_param(
f"{self.__class__.__name__}.order_by", order_by
)
super().__init__(*expressions, **extra)
diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py
index 68e5d2667e..ced5557732 100644
--- a/django/db/models/expressions.py
+++ b/django/db/models/expressions.py
@@ -1466,7 +1466,11 @@ def __init__(self, *expressions, **extra):
@classmethod
def from_param(cls, context, param):
+ if param is None:
+ return None
if isinstance(param, (list, tuple)):
+ if not param:
+ return None
return cls(*param)
elif isinstance(param, str) or hasattr(param, "resolve_expression"):
return cls(param)
@@ -1937,8 +1941,7 @@ def __init__(
self.partition_by = (self.partition_by,)
self.partition_by = ExpressionList(*self.partition_by)
- if self.order_by is not None:
- self.order_by = OrderByList.from_param("Window.order_by", self.order_by)
+ self.order_by = OrderByList.from_param("Window.order_by", self.order_by)
super().__init__(output_field=output_field)
self.source_expression = self._parse_expressions(expression)[0] |
f00f27a
to
20c8a9b
Compare
This moves the behaviors of `order_by` used in Postgres aggregates into the `Aggregate` class. This allows for creating aggregate functions that support this behavior across all database engines. This is shown by moving the `StringAgg` class into the shared `aggregates` module and adding support for all databases. The Postgres `StringAgg` class is now a thin wrapper on the new shared `StringAgg` class. Thank you Simon Charette for the review.
20c8a9b
to
6f8a3e4
Compare
buildbot, test on oracle. |
This commit does not create any functional changes, but marks the existing `OrderableAggMixin` class as deprecated so that developers using it directly can be made aware of its future removal.
6f8a3e4
to
7a7aabf
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.
Thank you! Great job on this ⭐
Thank you for sticking around and seeing this through @camuthig, that was quite the 10 months adventure. Hopefully it was a valuable experience for you! |
Woo! We did it! Thanks for all the help along the way, @charettes . It's definitely been a good experience for me. I learned a lot about how the ORM is working behind the scenes, which was my own goal. Now that we have this part done, I look forward to moving some of the other Postgres-specific functions over to general support. At least |
Trac ticket number
ticket-35444
Branch description
There are three commits to this draft that represent the three phases of the change:
ordering
argument on the PostgreSQL aggregate functions and replace it withorder_by
to create a consistent naming conventionAggregate.order_by
and migrate the Postgres-specificStringAgg
class to the sharedaggregates
module, deprecating the Postgres version.OrderableAggMixin
Checklist
main
branch.