Repository navigation
fix(mysql): type an aggregate under group by by its argument - #120
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
There was a problem hiding this comment.
ℹ️ No engine issues found — one docs sentence overstates the rule.
Reviewed changes
I reviewed the whole PR: the new grouped-aggregate probe in the MySQL engine, its use from prove and window_flags, the new and re-blessed oracle cases, and the docs entry.
grouped_aggregate/grouped_probe— For a plainGROUP BYwith no ROLLUP and no wildcard, these select eachsum/avg/min/maxargument from the sameFROMandWHERE. The probe dropsGROUP BY,HAVING,DISTINCT,ORDER BY,LIMIT, locks and named windows, and keeps theWITH.json_arrayagg/json_objectaggare marked never null.grouped_flags— The probe is described against the reduced and materialized query, so a strictWHEREreads its outer joins as inner. Each probe column maps back to its original position, and theWHERE'stestedproof counts too.window_flagsreuses it, sosum(max(x)) over ()followsx.- Derived tables and scalar subqueries — A grouped derived table's claim goes through
through_derived's filler rewrite and a fresh describe, so an outer join over it keeps the server's nullable flag. Scalar subqueries stay nullable (scalar_subquery.sql). - Oracle — There is a new
mysql_grouped_aggregatescase. The?was dropped inmysql_booleans,mysql_server_flagsandmysql_window_aggregates; each dropped?is aNOT NULLargument under a plainGROUP BY. - Docs — The MySQL page gains an Aggregates rule with a captured example, replacing the old sentence that covered only
json_arrayagg.
claude-opus-5-5 | 𝕏
Contributor
There was a problem hiding this comment.
✅ No new issues found. The docs sentence now matches the engine.
Reviewed changes
I reviewed the one commit since the last review, a docs fix for the earlier comment.
- Named the bit functions in the Aggregates rule.
site/content/docs/mysql.mdxnow namesbit_and,bit_orandbit_xorwithcountas the aggregates that are notNULLover no rows. This matchesoracle/cases/mysql_server_flags/expected.txt, whereaggregates_no_rowstypesd,eandfas non-null.
claude-opus-5-5 | 𝕏
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

MySQL typed
sum,avg,min,max,json_arrayaggandjson_objectaggunder aGROUP BYas nullable, because the server flags them nullable whatever their argument. Every group of a plainGROUP BYholds at least one row, sosum,avg,minandmaxare as NULL as their argument there, and the two JSON aggregates are never NULL. Postgres already follows this rule.sum(p.id),avg(p.id),min(p.title)withgroup by p.user_idsum(distinct p.id), andmax(p.id)underselect distinctor afterhaving count(*) > 1max(p.note)withwhere p.note is not null, andmax(p.title)overusers u left join posts preduced bywhere p.id > 0json_objectagg(p.title, p.note)withgroup bysum(max(p.id)) over ()in a grouped querymax(p.published_at)(a nullable column), andmax(p.title)over an unreducedleft joinsum(p.id)with nogroup by, and withgroup by … with rollupvar_samp,stddev_pop,group_concatThe rule. For a plain SELECT with a
GROUP BYand noROLLUP,grouped_probe(engine/mysql/src/mysql_nulls.rs) writes a statement that selects each such aggregate's argument from the sameFROMandWHERE, with noGROUP BY,HAVING,DISTINCT,ORDER BYorLIMIT. Those are the rows the aggregate reads before they are grouped.grouped_flags(engine/mysql/src/mysql.rs) prepares it, and the aggregate is non-null where the argument's NOT NULL flag is set, or where theWHEREproves a bare column. The outer joins a strictWHEREreduces are read reduced, as everywhere else. This is the same probe shape as the window rule of #115, which now also reads a grouped aggregate passed as a window's argument.Where it makes no claim:
No
GROUP BY: an empty table gives one row of NULLs.WITH ROLLUP: left to the server's flags, as Postgres leaves a grouping set. Measured on 8.4, a ROLLUP over no rows returns no row, and its summary rows aggregate the rows themselves, so this could be extended later.Only a whole select-list item: an expression over an aggregate (
sum(x) + 1) keeps the server's flag.Variance family and
group_concat: they keep the server's flag;var_sampandstddev_sampare NULL over one row.Set operations and wildcards: a select list with a wildcard, and a set operation, are not read.
New oracle case
oracle/cases/mysql_grouped_aggregates(20 statements, blessed on MySQL 8.4, emptyunchecked.txt), with users without posts, posts without a date or a note, and every edge value.Other cases'
expected.txtlines that lost a?, each read:mysql_booleans:expressions_over_flagsmax(done)over aBOOLEAN NOT NULL.mysql_server_flags:aggregates_groupedsum(small),max(name),avg(small), andmax(day)over aDATE NOT NULLthat holds zero dates.min(maybe_n)andgroup_concatstay nullable, and itssetup.sqlcomment names the rule.mysql_window_aggregates:grouped_sum.Docs: the MySQL page's Nullability rules gain an Aggregates entry (captured
my_null_aggregates.sql). It replaces the sentence that said onlyjson_arrayaggis non-null under agroup by.Checks:
cargo build --workspace --all-targetswith no warning,cargo testof every crate, the whole oracle with--strict-coverage(145 cases, 0 failed),engine/build.sh(only the MySQL artifact changes),nub run typecheck,nub run testwith both databases (258 passed, 0 skipped),nub run build, and the site's capture and build.