Skip to content

Translate string.Join/Concat with ordering on SQLite#38344

Merged
AndriySvyryd merged 3 commits into
dotnet:mainfrom
Aykuttonpc:sqlite-aggregate-orderby
Jun 4, 2026
Merged

Translate string.Join/Concat with ordering on SQLite#38344
AndriySvyryd merged 3 commits into
dotnet:mainfrom
Aykuttonpc:sqlite-aggregate-orderby

Conversation

@Aykuttonpc
Copy link
Copy Markdown
Contributor

Translates string.Join/string.Concat over an ordered grouping to SQLite's group_concat with an ORDER BY clause, supported since SQLite 3.44.0. Previously such queries fell back to client evaluation.

-- string.Join("|", g.OrderByDescending(e => e.Id).Select(e => e.String))
COALESCE(group_concat("b"."String", '|' ORDER BY "b"."Id" DESC), '')

Changes

  • Add SqliteAggregateFunctionExpression (carries the orderings), mirroring SqlServerAggregateFunctionExpression
  • SqliteStringAggregateMethodTranslator: stop refusing translation when an ordering is present; emit the new expression (plain group_concat when there's no ordering — SQL unchanged)
  • SqliteQuerySqlGenerator: render group_concat(value, separator ORDER BY ...). Note SQLite places the ordering inside the parentheses, unlike SQL Server's WITHIN GROUP
  • SqliteSqlNullabilityProcessor: handle the new expression
  • Enable Join_with_ordering in StringTranslationsSqliteTest

Notes

  • Scope is group_concat (string aggregation); ORDER BY is meaningless for Min/Max.
  • group_concat(DISTINCT x ORDER BY y) is restricted in SQLite (DISTINCT + ORDER BY only when ordering by the distinct column); no base test covers this combination.

Fixes #32201

SQLite 3.44.0 added support for ORDER BY inside aggregate functions
(group_concat(value, separator ORDER BY ...)). Translate string.Join and
string.Concat over an ordered source instead of falling back to client
evaluation.

- Add SqliteAggregateFunctionExpression carrying the orderings
- Render the ORDER BY inside the function in SqliteQuerySqlGenerator
- Handle the new expression in SqliteSqlNullabilityProcessor
- Enable the Join_with_ordering test

Fixes dotnet#32201
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR enables translation of string.Join/string.Concat over an ordered grouping in the SQLite provider by emitting SQLite’s group_concat(... ORDER BY ...) syntax (SQLite >= 3.44.0), avoiding client evaluation for these queries.

Changes:

  • Introduces SqliteAggregateFunctionExpression to represent aggregate function invocations that carry ORDER BY information.
  • Updates SQLite string-aggregate translation to allow ordered inputs and updates SQL generation to render ORDER BY inside the aggregate function parentheses.
  • Enables the Join_with_ordering SQLite functional test to assert server translation.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/EFCore.Sqlite.FunctionalTests/Query/Translations/StringTranslationsSqliteTest.cs Updates expected SQL for ordered string aggregation using group_concat(... ORDER BY ...).
src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteStringAggregateMethodTranslator.cs Emits the new aggregate expression when orderings are present instead of refusing translation.
src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlNullabilityProcessor.cs Adds nullability-processing support for the new custom SQL expression node.
src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs Adds SQL generation for aggregate ORDER BY inside function parentheses.
src/EFCore.Sqlite.Core/Query/Internal/SqlExpressions/SqliteAggregateFunctionExpression.cs New SQL expression type carrying aggregate function name, arguments, and orderings.

- Refuse translation when the SQLite version is below 3.44 (ServerVersion
  check), falling back to client evaluation as before
- Refuse ORDER BY combined with DISTINCT, which SQLite restricts to ordering
  by the distinct value itself
- Skip Join_with_ordering on SQLite versions below 3.44
@Aykuttonpc
Copy link
Copy Markdown
Contributor Author

Aykuttonpc commented Jun 1, 2026

Good catches all three addressed in b89fb2b: version-gated to 3.44 (falls back to client eval below that), DISTINCT + ORDER BY now refused, and the test is gated the same way.

@AndriySvyryd
Copy link
Copy Markdown
Member

Add a test for DISTINCT + ORDER BY only when ordering by the distinct column

SQLite's group_concat() accepts only a single argument when DISTINCT is used,
so it cannot be combined with the separator that string.Join/Concat always
supply ("DISTINCT aggregates must have exactly one argument"), independent of
ordering. Refuse translation for DISTINCT (the query then falls back to APPLY,
which SQLite doesn't support) rather than emit SQL that fails at execution time.

- Refuse translation whenever IsDistinct (previously only refused together with
  an ORDER BY)
- Add Join_with_distinct asserting it reports ApplyNotSupported
@Aykuttonpc
Copy link
Copy Markdown
Contributor Author

Looked into the distinct + ordering case. Two things block it. First, EF raises DistinctAfterOrderByWithoutRowLimitingOperatorWarning for Distinct() after OrderBy(), so it doesn't get through by default. Second, even past that, SQLite's group_concat only takes one argument with DISTINCT, so the separator from string.Join/Concat fails with DISTINCT aggregates must have exactly one argument. So distinct string aggregation can't be translated here regardless of ordering. I refuse it (falls back to APPLY, which SQLite doesn't support) and added a test for that. Could well be missing something here though, so glad to revisit if you had a different approach in mind.

Copy link
Copy Markdown
Member

@AndriySvyryd AndriySvyryd 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 your contribution!

@AndriySvyryd AndriySvyryd enabled auto-merge (squash) June 4, 2026 20:45
@Aykuttonpc
Copy link
Copy Markdown
Contributor Author

@Aykuttonpc please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@dotnet-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@dotnet-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@dotnet-policy-service agree company="Microsoft"

Contributor License Agreement

@dotnet-policy-service agree

@AndriySvyryd AndriySvyryd merged commit d87e0d3 into dotnet:main Jun 4, 2026
13 checks passed
@Aykuttonpc
Copy link
Copy Markdown
Contributor Author

Thanks for the review and merge!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support ORDER BY in SQLite aggregate functions

3 participants