Skip to content

*: add aggregate functions min_count and max_count#69642

Open
windtalker wants to merge 6 commits into
pingcap:masterfrom
windtalker:min_count_max_count
Open

*: add aggregate functions min_count and max_count#69642
windtalker wants to merge 6 commits into
pingcap:masterfrom
windtalker:min_count_max_count

Conversation

@windtalker

@windtalker windtalker commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: close #66124

Problem Summary:

Materialized view incremental refresh needs helper aggregate functions that can count how many rows match the current minimum or maximum value in a group.

What changed and how does it work?

This PR adds two aggregate functions:

  • max_count(expr) returns the number of non-NULL rows whose value equals the maximum non-NULL value of expr.
  • min_count(expr) returns the number of non-NULL rows whose value equals the minimum non-NULL value of expr.

The implementation includes:

  • Parser support for MAX_COUNT(expr), MAX_COUNT(ALL expr), MIN_COUNT(expr), and MIN_COUNT(ALL expr).
  • Expression aggregation descriptors, return-type inference, final/partial aggregation support, and cost factors.
  • Executor aggregate implementations for supported evaluation types, including partial result merge, spill serialization/deserialization, memory accounting, and sliding-window execution.
  • Planner handling so final aggregation keeps the original argument type for max/min-count partial extrema comparison.

DISTINCT is intentionally not supported for max_count or min_count; the parser rejects max_count(distinct ...) and min_count(distinct ...).

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No need to test
    • I checked and no code files have been changed.

Unit tests:

  • go test ./pkg/executor/aggfuncs -run 'Test(MaxMinCount|MergePartialResult4MaxMinCount|MemMaxMinCount)' -tags=intest,deadlock -count=1
  • ./tools/check/failpoint-go-test.sh pkg/expression/aggregation -run 'Test(MaxMinCount|BaseFunc_InferMaxMinCountRetType)' -count=1
  • ./tools/check/failpoint-go-test.sh pkg/planner/core -run 'TestBuildFinalModeAggregation(MaxMinCountSchema)?$' -count=1
  • go test . -run '^TestBuiltin$' -tags=intest,deadlock -count=1 in pkg/parser
  • make bazel_prepare
  • make bazel_lint_changed

Side effects

  • Performance regression: Consumes more CPU
  • Performance regression: Consumes more Memory
  • Breaking backward compatibility

Documentation

  • Affects user behaviors
  • Contains syntax changes
  • Contains variable changes
  • Contains experimental features
  • Changes MySQL compatibility

Release note

Please refer to Release Notes Language Style Guide to write a quality release note.

Add aggregate functions `MAX_COUNT()` and `MIN_COUNT()` to count occurrences of the maximum and minimum non-NULL values.

Summary by CodeRabbit

  • New Features
    • Added MAX_COUNT and MIN_COUNT aggregates end-to-end, including SQL parsing/validation, partial/final execution, collation-aware string handling, and sliding-window support across many data types (including vector float32).
    • Enabled TiFlash handling and optimized planning for these aggregates.
  • Bug Fixes
    • Added spill/restore serialization for vector float32 and expanded support for max/min-count window execution.
    • max_count/min_count row-based final aggregation is explicitly unsupported.
  • Tests
    • Added extensive unit, SQL/window, pushdown, merge, duplicate semantics, memory accounting, and negative-usage coverage.

@ti-chi-bot ti-chi-bot Bot added release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. sig/planner SIG: Planner labels Jul 3, 2026
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds MAX_COUNT and MIN_COUNT support from parsing through aggregation execution, including spill serialization, planner pushdown/MPP handling, and tests for SQL, planner, KV, and spill behavior.

Changes

MAX_COUNT/MIN_COUNT aggregate function implementation

Layer / File(s) Summary
Parser grammar and keywords
pkg/parser/...
Adds aggregate-function names, token mappings, grammar productions, and parser coverage.
Expression aggregation wiring
pkg/expression/aggregation/*
Wires the functions through descriptors, type inference, pushdown checks, protobuf conversion, evaluator construction, and tests.
Executor builder and implementations
pkg/executor/aggfuncs/*
Adds type-specific standard and sliding-window aggregation, partial-result handling, spill support, and build registration.
Planner and MPP handling
pkg/planner/core/*, pkg/kv/*
Updates aggregate costs, schema construction, MPP planning, task attachment, request support, and related tests.
Serialization support
pkg/util/serialization/*
Adds vector-float32 serialization and deserialization helpers.
Cross-layer validation
pkg/executor/aggfuncs/*_test.go, pkg/expression/aggregation/*_test.go, pkg/planner/core/*_test.go
Adds unit, SQL, spill, protobuf, pushdown, planner, and window-aggregation coverage.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Parser
  participant Aggregation
  participant Executor
  participant Planner
  Client->>Parser: Parse MAX_COUNT/MIN_COUNT
  Parser->>Aggregation: Build aggregate descriptor
  Aggregation->>Planner: Check pushdown and MPP properties
  Planner->>Executor: Select aggregation implementation
  Executor-->>Client: Return extrema occurrence count
Loading

Suggested labels: ok-to-test, approved

Suggested reviewers: qw4990, 0xpoe, connor1996

Poem

A rabbit counts peaks in the data stream,
Maxima and minima now gleam.
Through windows and spills, the numbers align,
Two little counters hop down the line.
🐇🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issue #66124 also requests sum_int, but this PR only implements min_count and max_count. Add the missing sum_int helper or split that requirement into a separate linked issue/PR and update the issue references accordingly.
Docstring Coverage ⚠️ Warning Docstring coverage is 29.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding max_count and min_count aggregate functions.
Description check ✅ Passed The description includes the issue number, problem summary, change details, tests, side effects, documentation, and release note.
Out of Scope Changes check ✅ Passed The changes stay focused on parser, executor, planner, serialization, and tests needed for max_count/min_count support.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0.57703% with 1723 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.0279%. Comparing base (b8d04e1) to head (67ba029).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@               Coverage Diff                @@
##             master     #69642        +/-   ##
================================================
- Coverage   76.3194%   74.0279%   -2.2915%     
================================================
  Files          2041       2056        +15     
  Lines        560092     578379     +18287     
================================================
+ Hits         427459     428162       +703     
- Misses       131732     149867     +18135     
+ Partials        901        350       -551     
Flag Coverage Δ
integration 40.7542% <0.5770%> (+1.0489%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
dumpling 60.4471% <ø> (ø)
parser ∅ <ø> (∅)
br 47.4060% <ø> (-15.3154%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/expression/aggregation/descriptor.go (1)

202-217: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle max_count/min_count as two-argument partial aggregates in pkg/expression/aggregation/descriptor.go

maxMinCountFunction.GetPartialResult returns [count, value], but Split() only wires one Column in the default branch. In parallel HashAgg, the final phase then treats partial rows as single values and loses the real count. Add a dedicated 2-arg branch like AVG for AggFuncMaxCount/AggFuncMinCount.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/expression/aggregation/descriptor.go` around lines 202 - 217, The default
branch in descriptor assembly is wiring only one Column for max_count/min_count,
but maxMinCountFunction.GetPartialResult produces two partial fields ([count,
value]); update the Split logic to add a dedicated two-argument partial
aggregate path for ast.AggFuncMaxCount and ast.AggFuncMinCount, similar to the
AVG handling, and make sure finalAggDesc.Args preserves both partial inputs
instead of collapsing them into a single value.
🧹 Nitpick comments (5)
pkg/expression/aggregation/base_func.go (1)

128-133: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: merge case with AggFuncCount.

ast.AggFuncMaxCount, ast.AggFuncMinCount could be merged into the same case as ast.AggFuncCount on line 129 since both call a.typeInfer4Count() with no other logic difference.

♻️ Proposed simplification
-	case ast.AggFuncCount:
-		a.typeInfer4Count()
-	case ast.AggFuncMaxCount, ast.AggFuncMinCount:
-		a.typeInfer4Count()
+	case ast.AggFuncCount, ast.AggFuncMaxCount, ast.AggFuncMinCount:
+		a.typeInfer4Count()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/expression/aggregation/base_func.go` around lines 128 - 133, Merge the
redundant aggregation switch branches in baseFunc’s type inference logic: in the
switch over a.Name inside the type inference method, combine ast.AggFuncMaxCount
and ast.AggFuncMinCount with ast.AggFuncCount since they all call
a.typeInfer4Count() and have no unique behavior, keeping the existing
ast.AggFuncApproxCountDistinct branch unchanged.
pkg/planner/core/plan_test.go (1)

682-721: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding a MIN_COUNT case for symmetry.

Test only exercises ast.AggFuncMaxCount; ast.AggFuncMinCount shares the same code path in BuildFinalModeAggregation (line 791 checks both), so this isn't strictly required, but a quick parallel case would strengthen coverage of the min branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/planner/core/plan_test.go` around lines 682 - 721, The test only covers
ast.AggFuncMaxCount, but BuildFinalModeAggregation handles ast.AggFuncMinCount
on the same path as well. Extend TestBuildFinalModeAggregationMaxMinCountSchema
with a parallel MinCount case using the same setup and assertions, so both max
and min branches are covered with matching schema and argument type checks.
pkg/executor/aggfuncs/spill_helper_test.go (1)

476-524: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Test doesn't cover the zero-value/zero-size branch or buffer-capacity growth.

Two gaps in this new round-trip test:

  1. Neither expectData entry has a truly zero-value VectorFloat32 (SerializedSize() == 0), so the special-case branch added in SerializeVectorFloat32 (pkg/util/serialization/serialization_util.go) is never exercised here.
  2. Unlike the JSON test above it (which asserts bufSizeChecker.checkBufferCapacity(serializeHelper) after each serialize call), this test never validates buffer capacity — despite using a large 1024-float vector seemingly intended to stress buffer growth.
🧪 Suggested additions
 	expectData := []partialResult4MaxMinVectorFloat32{
 		{val: types.MustCreateVectorFloat32([]float32{1, 2, 3}), isNull: false},
 		{val: types.MustCreateVectorFloat32(largeVecData), isNull: true},
+		{val: types.VectorFloat32{}, isNull: true},
 	}
...
 	for _, pr := range serializedPartialResults {
 		serializedData := serializeHelper.serializePartialResult4MaxMinVectorFloat32(*(*partialResult4MaxMinVectorFloat32)(pr))
 		chk.AppendBytes(0, serializedData)
+		require.True(t, bufSizeChecker.checkBufferCapacity(serializeHelper))
 	}

Do you want me to draft the full patch, including initializing bufSizeChecker consistently with the JSON test above?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/executor/aggfuncs/spill_helper_test.go` around lines 476 - 524, The
round-trip test in TestPartialResult4MaxMinVectorFloat32 is missing coverage for
the zero-size VectorFloat32 serialization path and for serialize buffer growth.
Update the test data to include a truly zero-value
partialResult4MaxMinVectorFloat32 so SerializeVectorFloat32’s SerializedSize()
== 0 branch is exercised, and add the same buffer-capacity assertions used by
the nearby JSON test by initializing bufSizeChecker and checking it after each
serializePartialResult4MaxMinVectorFloat32 call in SerializeHelper.
pkg/util/serialization/serialization_util.go (1)

133-139: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the zero-value serialization branch. VectorFloat32{} has nil data, so ZeroCopySerialize() would emit an empty slice; substituting types.ZeroVectorFloat32 preserves the canonical 4-byte empty-vector encoding.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/util/serialization/serialization_util.go` around lines 133 - 139,
Document and preserve the zero-value branch in SerializeVectorFloat32: when
value.SerializedSize() is 0, keep using types.ZeroVectorFloat32 with
serializeBuffer instead of value.ZeroCopySerialize(), since VectorFloat32{} has
nil data and must serialize to the canonical empty-vector encoding. Make the
intent explicit in the SerializeVectorFloat32 function so future changes don’t
remove this special case.

Source: Coding guidelines

pkg/executor/aggfuncs/func_max_min_count.go (1)

82-152: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Sliding-window deque boxes every value into any, causing per-row heap allocations.

maxMinCountDequeItem.item is typed any, and enqueue is called once per row in UpdatePartialResult/Slide for every sliding-window variant (int64, uint64, float32/64, MyDecimal, types.Time, types.Duration). Boxing these values into an interface allocates on the heap for most values, adding GC pressure on what is effectively a per-row hot path for window function evaluation over large windows.

A generic minMaxCountDeque[T any] (or per-type typed slices) would avoid the boxing while keeping the same eviction logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/executor/aggfuncs/func_max_min_count.go` around lines 82 - 152, The deque
hot path in minMaxCountDeque is boxing every value into any, which causes
avoidable per-row heap allocations during sliding-window updates. Refactor
maxMinCountDequeItem and minMaxCountDeque into a generic typed deque (or
equivalent per-type storage) so enqueue, dequeue, and frontCount operate on
concrete value types instead of interface values. Keep the same eviction/index
tracking behavior, and update the callers in UpdatePartialResult/Slide to use
the typed deque for the max/min/count variants.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@pkg/expression/aggregation/descriptor.go`:
- Around line 202-217: The default branch in descriptor assembly is wiring only
one Column for max_count/min_count, but maxMinCountFunction.GetPartialResult
produces two partial fields ([count, value]); update the Split logic to add a
dedicated two-argument partial aggregate path for ast.AggFuncMaxCount and
ast.AggFuncMinCount, similar to the AVG handling, and make sure
finalAggDesc.Args preserves both partial inputs instead of collapsing them into
a single value.

---

Nitpick comments:
In `@pkg/executor/aggfuncs/func_max_min_count.go`:
- Around line 82-152: The deque hot path in minMaxCountDeque is boxing every
value into any, which causes avoidable per-row heap allocations during
sliding-window updates. Refactor maxMinCountDequeItem and minMaxCountDeque into
a generic typed deque (or equivalent per-type storage) so enqueue, dequeue, and
frontCount operate on concrete value types instead of interface values. Keep the
same eviction/index tracking behavior, and update the callers in
UpdatePartialResult/Slide to use the typed deque for the max/min/count variants.

In `@pkg/executor/aggfuncs/spill_helper_test.go`:
- Around line 476-524: The round-trip test in
TestPartialResult4MaxMinVectorFloat32 is missing coverage for the zero-size
VectorFloat32 serialization path and for serialize buffer growth. Update the
test data to include a truly zero-value partialResult4MaxMinVectorFloat32 so
SerializeVectorFloat32’s SerializedSize() == 0 branch is exercised, and add the
same buffer-capacity assertions used by the nearby JSON test by initializing
bufSizeChecker and checking it after each
serializePartialResult4MaxMinVectorFloat32 call in SerializeHelper.

In `@pkg/expression/aggregation/base_func.go`:
- Around line 128-133: Merge the redundant aggregation switch branches in
baseFunc’s type inference logic: in the switch over a.Name inside the type
inference method, combine ast.AggFuncMaxCount and ast.AggFuncMinCount with
ast.AggFuncCount since they all call a.typeInfer4Count() and have no unique
behavior, keeping the existing ast.AggFuncApproxCountDistinct branch unchanged.

In `@pkg/planner/core/plan_test.go`:
- Around line 682-721: The test only covers ast.AggFuncMaxCount, but
BuildFinalModeAggregation handles ast.AggFuncMinCount on the same path as well.
Extend TestBuildFinalModeAggregationMaxMinCountSchema with a parallel MinCount
case using the same setup and assertions, so both max and min branches are
covered with matching schema and argument type checks.

In `@pkg/util/serialization/serialization_util.go`:
- Around line 133-139: Document and preserve the zero-value branch in
SerializeVectorFloat32: when value.SerializedSize() is 0, keep using
types.ZeroVectorFloat32 with serializeBuffer instead of
value.ZeroCopySerialize(), since VectorFloat32{} has nil data and must serialize
to the canonical empty-vector encoding. Make the intent explicit in the
SerializeVectorFloat32 function so future changes don’t remove this special
case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1d9907ac-ae77-427d-94ec-68ed04d508d6

📥 Commits

Reviewing files that changed from the base of the PR and between 8be4bd0 and 898557d.

📒 Files selected for processing (27)
  • pkg/executor/aggfuncs/BUILD.bazel
  • pkg/executor/aggfuncs/aggfuncs.go
  • pkg/executor/aggfuncs/builder.go
  • pkg/executor/aggfuncs/func_max_min.go
  • pkg/executor/aggfuncs/func_max_min_count.go
  • pkg/executor/aggfuncs/func_max_min_count_test.go
  • pkg/executor/aggfuncs/spill_deserialize_helper.go
  • pkg/executor/aggfuncs/spill_helper_test.go
  • pkg/executor/aggfuncs/spill_serialize_helper.go
  • pkg/expression/aggregation/BUILD.bazel
  • pkg/expression/aggregation/aggregation.go
  • pkg/expression/aggregation/aggregation_test.go
  • pkg/expression/aggregation/base_func.go
  • pkg/expression/aggregation/base_func_test.go
  • pkg/expression/aggregation/descriptor.go
  • pkg/expression/aggregation/max_min_count.go
  • pkg/expression/aggregation/window_func.go
  • pkg/parser/ast/functions.go
  • pkg/parser/misc.go
  • pkg/parser/parser.go
  • pkg/parser/parser.y
  • pkg/parser/parser_test.go
  • pkg/planner/core/cost/factors_thresholds.go
  • pkg/planner/core/operator/physicalop/base_physical_agg.go
  • pkg/planner/core/plan_test.go
  • pkg/util/serialization/deserialization_util.go
  • pkg/util/serialization/serialization_util.go

@windtalker windtalker force-pushed the min_count_max_count branch from 898557d to 2839561 Compare July 7, 2026 09:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
pkg/kv/checker.go (1)

53-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider documenting the TiKV restriction for MAX_COUNT/MIN_COUNT.

This list marks MaxCount/MinCount as generically "supported" expr types, but actual TiKV push-down is disallowed elsewhere (per CheckAggPushDown), same as GroupConcat. The existing comment only calls out GroupConcat's TiFlash-only restriction; a similar note for MaxCount/MinCount would help future readers avoid assuming TiKV push-down works here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/kv/checker.go` around lines 53 - 57, Update the aggregate-function
comment in checker.go so it also documents that tipb.ExprType_MaxCount and
tipb.ExprType_MinCount are not valid for TiKV push-down, similar to the existing
TiFlash-only note for tipb.ExprType_GroupConcat. Keep the explanation near the
switch case in checker.CheckExpr, and make it clear that CheckAggPushDown
enforces this TiKV restriction even though these expr types appear in the
supported list.
pkg/expression/aggregation/aggregation.go (1)

90-105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting shared MaxCount/MinCount construction logic.

The MaxCount and MinCount branches are identical except for ast.AggFuncMaxCount/ast.AggFuncMinCount and isMax: true/false. A small helper (e.g. newMaxMinCountFunc(args, mode, isMax bool, ctx) ...) would remove the duplication and reduce the chance the two branches drift apart if cmpArgIdx logic is tweaked later.

♻️ Proposed refactor
-	case tipb.ExprType_MaxCount:
-		aggF := newAggFunc(ast.AggFuncMaxCount, args, false)
-		aggF.Mode = AggFunctionMode(*expr.AggFuncMode)
-		cmpArgIdx := 0
-		if (aggF.Mode == FinalMode || aggF.Mode == Partial2Mode) && len(args) > 1 {
-			cmpArgIdx = 1
-		}
-		return &maxMinCountFunction{aggFunction: aggF, isMax: true, ctor: collate.GetCollator(args[cmpArgIdx].GetType(ctx.GetEvalCtx()).GetCollate())}, aggF.AggFuncDesc, nil
-	case tipb.ExprType_MinCount:
-		aggF := newAggFunc(ast.AggFuncMinCount, args, false)
-		aggF.Mode = AggFunctionMode(*expr.AggFuncMode)
-		cmpArgIdx := 0
-		if (aggF.Mode == FinalMode || aggF.Mode == Partial2Mode) && len(args) > 1 {
-			cmpArgIdx = 1
-		}
-		return &maxMinCountFunction{aggFunction: aggF, isMax: false, ctor: collate.GetCollator(args[cmpArgIdx].GetType(ctx.GetEvalCtx()).GetCollate())}, aggF.AggFuncDesc, nil
+	case tipb.ExprType_MaxCount, tipb.ExprType_MinCount:
+		isMax := expr.Tp == tipb.ExprType_MaxCount
+		name := ast.AggFuncMinCount
+		if isMax {
+			name = ast.AggFuncMaxCount
+		}
+		aggF := newAggFunc(name, args, false)
+		aggF.Mode = AggFunctionMode(*expr.AggFuncMode)
+		cmpArgIdx := 0
+		if (aggF.Mode == FinalMode || aggF.Mode == Partial2Mode) && len(args) > 1 {
+			cmpArgIdx = 1
+		}
+		return &maxMinCountFunction{aggFunction: aggF, isMax: isMax, ctor: collate.GetCollator(args[cmpArgIdx].GetType(ctx.GetEvalCtx()).GetCollate())}, aggF.AggFuncDesc, nil
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/expression/aggregation/aggregation.go` around lines 90 - 105, The
MaxCount and MinCount cases in aggregation.go duplicate the same construction
and cmpArgIdx selection logic, so refactor them into a shared helper to keep the
behavior in one place. Add a helper around the newAggFunc, AggFunctionMode
assignment, cmpArgIdx choice, collate.GetCollator lookup, and
maxMinCountFunction creation, with only ast.AggFuncMaxCount vs
ast.AggFuncMinCount and isMax differing. Update the tipb.ExprType_MaxCount and
tipb.ExprType_MinCount branches to call that helper so future changes to the
comparison-argument logic stay consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/expression/aggregation/aggregation.go`:
- Around line 90-105: The MaxCount and MinCount cases in aggregation.go
duplicate the same construction and cmpArgIdx selection logic, so refactor them
into a shared helper to keep the behavior in one place. Add a helper around the
newAggFunc, AggFunctionMode assignment, cmpArgIdx choice, collate.GetCollator
lookup, and maxMinCountFunction creation, with only ast.AggFuncMaxCount vs
ast.AggFuncMinCount and isMax differing. Update the tipb.ExprType_MaxCount and
tipb.ExprType_MinCount branches to call that helper so future changes to the
comparison-argument logic stay consistent.

In `@pkg/kv/checker.go`:
- Around line 53-57: Update the aggregate-function comment in checker.go so it
also documents that tipb.ExprType_MaxCount and tipb.ExprType_MinCount are not
valid for TiKV push-down, similar to the existing TiFlash-only note for
tipb.ExprType_GroupConcat. Keep the explanation near the switch case in
checker.CheckExpr, and make it clear that CheckAggPushDown enforces this TiKV
restriction even though these expr types appear in the supported list.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3a032b35-8cd3-4718-8f06-6817eeeda42c

📥 Commits

Reviewing files that changed from the base of the PR and between 898557d and 2839561.

📒 Files selected for processing (35)
  • pkg/executor/aggfuncs/BUILD.bazel
  • pkg/executor/aggfuncs/aggfuncs.go
  • pkg/executor/aggfuncs/builder.go
  • pkg/executor/aggfuncs/func_max_min.go
  • pkg/executor/aggfuncs/func_max_min_count.go
  • pkg/executor/aggfuncs/func_max_min_count_test.go
  • pkg/executor/aggfuncs/spill_deserialize_helper.go
  • pkg/executor/aggfuncs/spill_helper_test.go
  • pkg/executor/aggfuncs/spill_serialize_helper.go
  • pkg/expression/aggregation/BUILD.bazel
  • pkg/expression/aggregation/agg_to_pb.go
  • pkg/expression/aggregation/agg_to_pb_test.go
  • pkg/expression/aggregation/aggregation.go
  • pkg/expression/aggregation/aggregation_test.go
  • pkg/expression/aggregation/base_func.go
  • pkg/expression/aggregation/base_func_test.go
  • pkg/expression/aggregation/descriptor.go
  • pkg/expression/aggregation/max_min_count.go
  • pkg/expression/aggregation/window_func.go
  • pkg/kv/checker.go
  • pkg/kv/checker_test.go
  • pkg/parser/ast/functions.go
  • pkg/parser/misc.go
  • pkg/parser/parser.go
  • pkg/parser/parser.y
  • pkg/parser/parser_test.go
  • pkg/planner/core/cost/factors_thresholds.go
  • pkg/planner/core/operator/physicalop/BUILD.bazel
  • pkg/planner/core/operator/physicalop/base_physical_agg.go
  • pkg/planner/core/operator/physicalop/physical_hash_agg.go
  • pkg/planner/core/operator/physicalop/physical_utils_test.go
  • pkg/planner/core/plan_test.go
  • pkg/planner/core/task.go
  • pkg/util/serialization/deserialization_util.go
  • pkg/util/serialization/serialization_util.go
✅ Files skipped from review due to trivial changes (1)
  • pkg/parser/ast/functions.go
🚧 Files skipped from review as they are similar to previous changes (21)
  • pkg/executor/aggfuncs/BUILD.bazel
  • pkg/util/serialization/deserialization_util.go
  • pkg/planner/core/cost/factors_thresholds.go
  • pkg/parser/misc.go
  • pkg/expression/aggregation/BUILD.bazel
  • pkg/planner/core/plan_test.go
  • pkg/expression/aggregation/descriptor.go
  • pkg/executor/aggfuncs/spill_deserialize_helper.go
  • pkg/executor/aggfuncs/aggfuncs.go
  • pkg/parser/parser_test.go
  • pkg/expression/aggregation/base_func_test.go
  • pkg/util/serialization/serialization_util.go
  • pkg/parser/parser.y
  • pkg/executor/aggfuncs/builder.go
  • pkg/expression/aggregation/max_min_count.go
  • pkg/executor/aggfuncs/spill_helper_test.go
  • pkg/executor/aggfuncs/spill_serialize_helper.go
  • pkg/executor/aggfuncs/func_max_min.go
  • pkg/expression/aggregation/base_func.go
  • pkg/executor/aggfuncs/func_max_min_count_test.go
  • pkg/executor/aggfuncs/func_max_min_count.go

@windtalker windtalker force-pushed the min_count_max_count branch from 2839561 to 75c73ae Compare July 8, 2026 03:41

@gengliqi gengliqi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@ti-chi-bot ti-chi-bot Bot added the needs-1-more-lgtm Indicates a PR needs 1 more LGTM. label Jul 9, 2026
@windtalker windtalker force-pushed the min_count_max_count branch from 75c73ae to cd59864 Compare July 12, 2026 10:59
@windtalker

Copy link
Copy Markdown
Contributor Author

/test check-dev2

@AilinKid

Copy link
Copy Markdown
Contributor

pkg/expression/aggregation/descriptor.goSplit() default branch only wires one Column for max_count / min_count

maxMinCountFunction.GetPartialResult() returns two values: [count, extrema_value]. In final/partial2 mode, maxMinCountFunction.Update() depends on len(Args) > 1 to read them correctly — Args[0] for the accumulated count and Args[1] for the partial extrema.

But in Split(), the default branch only allocates a single arg:

default:
    args := make([]expression.Expression, 0, 1)   // ← only one
    ...
    args = append(args, &expression.Column{
        Index:   ordinal[0],
        RetType: argRetTp,
    })
    finalAggDesc.Args = args

This means if Split() were called for max_count / min_count, the final descriptor would have just one Args element, and Update() would silently fall into the single-phase path (treating each partial row as raw input with count=1), losing the accumulated counts.

However, I searched the entire pkg/ tree (2992 .go files) and found that AggFuncDesc.Split() has zero production callers — it is only invoked from test files:

  • pkg/executor/aggfuncs/aggfunc_test.go (lines 272, 393)
  • and the new func_max_min_count_test.go in this PR (TestMergePartialResult4MaxMinCount)

The actual two-phase split in production goes through BuildFinalModeAggregation() (in base_physical_agg.go), which correctly wires both columns via NeedCount() + NeedValue().

Suggestion: I would still lean toward adding a dedicated branch here (like the existing ast.AggFuncAvg case) for defensive correctness in case Split() gets used in the future. Alternatively, if the consensus is that Split() is effectively dead code, I would at least recommend adding a comment noting that max_count/min_count are not handled in this path — otherwise a future reader tracing GetPartialResult → [count, value] will be confused by the single-arg wiring here.

@AilinKid

Copy link
Copy Markdown
Contributor

/test check-dev2

@AilinKid AilinKid left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

/hold pls feel free to unhold when the comment above is scanned

@ti-chi-bot ti-chi-bot Bot added do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. lgtm and removed needs-1-more-lgtm Indicates a PR needs 1 more LGTM. labels Jul 13, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

[LGTM Timeline notifier]

Timeline:

  • 2026-07-09 09:35:16.841229569 +0000 UTC m=+274302.877324625: ☑️ agreed by gengliqi.
  • 2026-07-13 08:06:27.452985618 +0000 UTC m=+614573.489080664: ☑️ agreed by AilinKid.

@windtalker windtalker force-pushed the min_count_max_count branch from cd59864 to 27f1dd6 Compare July 14, 2026 02:45
@windtalker

Copy link
Copy Markdown
Contributor Author

pkg/expression/aggregation/descriptor.goSplit() default branch only wires one Column for max_count / min_count

maxMinCountFunction.GetPartialResult() returns two values: [count, extrema_value]. In final/partial2 mode, maxMinCountFunction.Update() depends on len(Args) > 1 to read them correctly — Args[0] for the accumulated count and Args[1] for the partial extrema.

But in Split(), the default branch only allocates a single arg:

default:
    args := make([]expression.Expression, 0, 1)   // ← only one
    ...
    args = append(args, &expression.Column{
        Index:   ordinal[0],
        RetType: argRetTp,
    })
    finalAggDesc.Args = args

This means if Split() were called for max_count / min_count, the final descriptor would have just one Args element, and Update() would silently fall into the single-phase path (treating each partial row as raw input with count=1), losing the accumulated counts.

However, I searched the entire pkg/ tree (2992 .go files) and found that AggFuncDesc.Split() has zero production callers — it is only invoked from test files:

  • pkg/executor/aggfuncs/aggfunc_test.go (lines 272, 393)
  • and the new func_max_min_count_test.go in this PR (TestMergePartialResult4MaxMinCount)

The actual two-phase split in production goes through BuildFinalModeAggregation() (in base_physical_agg.go), which correctly wires both columns via NeedCount() + NeedValue().

Suggestion: I would still lean toward adding a dedicated branch here (like the existing ast.AggFuncAvg case) for defensive correctness in case Split() gets used in the future. Alternatively, if the consensus is that Split() is effectively dead code, I would at least recommend adding a comment noting that max_count/min_count are not handled in this path — otherwise a future reader tracing GetPartialResult → [count, value] will be confused by the single-arg wiring here.

Good catch. Semantically, the row-based two-phase shape of max_count / min_count should indeed be [count, extrema_value].

One clarification: AggFuncDesc.Split() is currently used by TiDB executor's internal parallel HashAgg path, not as the general plan-level row-based split. In that path, the final worker does not consume partial rows through Update() / UpdatePartialResult(). It merges internal
aggfuncs.PartialResult objects through MergePartialResult(), where both the extrema value and count are already stored in the partial result struct. So the one-column descriptor here is a single partial-result slot used for building the final AggFunc and for spill/restore, not the
row-based [count, value] schema.

For the real row-based final/partial2 shape, max_count / min_count would need two args (count, extrema_value). That path is currently intentionally unsupported in the executor: FinalMode / Partial2Mode with more than one arg is wrapped by
unsupportedRowBasedFinalMaxMinCount.

I agree the current code is easy to misread, so I added a comment in AggFuncDesc.Split() to clarify that this branch is only for executor-internal parallel HashAgg and should not be interpreted as the row-based two-phase shape.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
pkg/executor/aggfuncs/func_max_min_count_test.go (1)

336-359: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Sliding-window coverage is int-only.

TestMaxMinCountSlidingWindow only exercises mysql.TypeLonglong. Given the string sliding variant uses a distinct collate field not exercised here (see companion comment in func_max_min_count.go), consider adding a string/decimal/time case to this test to catch collation or comparator wiring issues in the sliding path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/executor/aggfuncs/func_max_min_count_test.go` around lines 336 - 359,
Extend TestMaxMinCountSlidingWindow beyond integer inputs by adding
sliding-window cases for string, decimal, or time values, especially one that
exercises collation-sensitive comparison through the max_count/min_count paths.
Keep the existing integer assertions and verify expected results for each added
type so comparator and collate wiring are covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/executor/aggfuncs/func_max_min_count.go`:
- Around line 26-29: Remove the misleading DefPartialResult4MaxMinCountSize
constant and its compatibility comment from the constant block, after confirming
no external references exist; retain DefPartialResult4MaxMinCountIntSize as the
sole size constant.
- Around line 92-176: The sliding deque’s dynamic backing storage is not
accounted for after allocation. Update allocPartialResult4MaxMinCountSliding and
the deque mutation paths used by UpdatePartialResult and Slide to charge memory
for initial capacity and any growth of d.items (including reallocations), and
return the corresponding delta instead of always returning zero; apply the same
accounting fix to the analogous implementation in func_max_min.go.

---

Nitpick comments:
In `@pkg/executor/aggfuncs/func_max_min_count_test.go`:
- Around line 336-359: Extend TestMaxMinCountSlidingWindow beyond integer inputs
by adding sliding-window cases for string, decimal, or time values, especially
one that exercises collation-sensitive comparison through the
max_count/min_count paths. Keep the existing integer assertions and verify
expected results for each added type so comparator and collate wiring are
covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: be928e81-8ddd-4d43-9f7d-ba9c2724886f

📥 Commits

Reviewing files that changed from the base of the PR and between cd59864 and 27f1dd6.

📒 Files selected for processing (35)
  • pkg/executor/aggfuncs/BUILD.bazel
  • pkg/executor/aggfuncs/aggfuncs.go
  • pkg/executor/aggfuncs/builder.go
  • pkg/executor/aggfuncs/func_max_min.go
  • pkg/executor/aggfuncs/func_max_min_count.go
  • pkg/executor/aggfuncs/func_max_min_count_test.go
  • pkg/executor/aggfuncs/spill_deserialize_helper.go
  • pkg/executor/aggfuncs/spill_helper_test.go
  • pkg/executor/aggfuncs/spill_serialize_helper.go
  • pkg/expression/aggregation/BUILD.bazel
  • pkg/expression/aggregation/agg_to_pb.go
  • pkg/expression/aggregation/agg_to_pb_test.go
  • pkg/expression/aggregation/aggregation.go
  • pkg/expression/aggregation/aggregation_test.go
  • pkg/expression/aggregation/base_func.go
  • pkg/expression/aggregation/base_func_test.go
  • pkg/expression/aggregation/descriptor.go
  • pkg/expression/aggregation/max_min_count.go
  • pkg/expression/aggregation/window_func.go
  • pkg/kv/checker.go
  • pkg/kv/checker_test.go
  • pkg/parser/ast/functions.go
  • pkg/parser/misc.go
  • pkg/parser/parser.go
  • pkg/parser/parser.y
  • pkg/parser/parser_test.go
  • pkg/planner/core/cost/factors_thresholds.go
  • pkg/planner/core/operator/physicalop/BUILD.bazel
  • pkg/planner/core/operator/physicalop/base_physical_agg.go
  • pkg/planner/core/operator/physicalop/physical_hash_agg.go
  • pkg/planner/core/operator/physicalop/physical_utils_test.go
  • pkg/planner/core/plan_test.go
  • pkg/planner/core/task.go
  • pkg/util/serialization/deserialization_util.go
  • pkg/util/serialization/serialization_util.go
🚧 Files skipped from review as they are similar to previous changes (25)
  • pkg/expression/aggregation/BUILD.bazel
  • pkg/util/serialization/deserialization_util.go
  • pkg/planner/core/task.go
  • pkg/util/serialization/serialization_util.go
  • pkg/executor/aggfuncs/BUILD.bazel
  • pkg/executor/aggfuncs/spill_helper_test.go
  • pkg/planner/core/cost/factors_thresholds.go
  • pkg/expression/aggregation/base_func_test.go
  • pkg/kv/checker_test.go
  • pkg/expression/aggregation/window_func.go
  • pkg/planner/core/operator/physicalop/physical_utils_test.go
  • pkg/executor/aggfuncs/func_max_min.go
  • pkg/expression/aggregation/agg_to_pb.go
  • pkg/parser/ast/functions.go
  • pkg/parser/parser.y
  • pkg/planner/core/operator/physicalop/base_physical_agg.go
  • pkg/kv/checker.go
  • pkg/parser/parser_test.go
  • pkg/expression/aggregation/aggregation.go
  • pkg/expression/aggregation/descriptor.go
  • pkg/planner/core/plan_test.go
  • pkg/planner/core/operator/physicalop/physical_hash_agg.go
  • pkg/expression/aggregation/base_func.go
  • pkg/expression/aggregation/aggregation_test.go
  • pkg/executor/aggfuncs/spill_deserialize_helper.go

Comment on lines +26 to +29
const (
// DefPartialResult4MaxMinCountSize is kept for compatibility.
DefPartialResult4MaxMinCountSize = int64(unsafe.Sizeof(partialResult4MaxMinCountInt{}))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Misleading "compatibility" constant in a brand-new file.

DefPartialResult4MaxMinCountSize duplicates DefPartialResult4MaxMinCountIntSize and the comment claims it's "kept for compatibility," but this is a new file with no prior version to be compatible with. This will confuse future readers into thinking there's a legacy contract to preserve.

🧹 Proposed fix
 const (
-	// DefPartialResult4MaxMinCountSize is kept for compatibility.
-	DefPartialResult4MaxMinCountSize = int64(unsafe.Sizeof(partialResult4MaxMinCountInt{}))
-
 	// DefPartialResult4MaxMinCountIntSize is the size of partialResult4MaxMinCountInt.
 	DefPartialResult4MaxMinCountIntSize = int64(unsafe.Sizeof(partialResult4MaxMinCountInt{}))

Confirm nothing outside this file references the unsuffixed name before removing:

#!/bin/bash
rg -n '\bDefPartialResult4MaxMinCountSize\b'

Based on coding guidelines: "Comments SHOULD explain non-obvious intent... SHOULD NOT restate what the code already makes clear" — this comment does worse than restate, it actively misleads.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/executor/aggfuncs/func_max_min_count.go` around lines 26 - 29, Remove the
misleading DefPartialResult4MaxMinCountSize constant and its compatibility
comment from the constant block, after confirming no external references exist;
retain DefPartialResult4MaxMinCountIntSize as the sole size constant.

Source: Coding guidelines

Comment on lines +92 to +176
type maxMinCountDequeItem struct {
item any
idxs []uint64
}

type minMaxCountDeque struct {
items []maxMinCountDequeItem
isMax bool
cmpFunc func(i, j any) int
}

func newMinMaxCountDeque(isMax bool, cmpFunc func(i, j any) int) *minMaxCountDeque {
return &minMaxCountDeque{
items: make([]maxMinCountDequeItem, 0, 64),
isMax: isMax,
cmpFunc: cmpFunc,
}
}

func (d *minMaxCountDeque) reset() {
d.items = d.items[:0]
}

func (d *minMaxCountDeque) isEmpty() bool {
return len(d.items) == 0
}

func (d *minMaxCountDeque) enqueue(idx uint64, item any) {
for !d.isEmpty() {
backIdx := len(d.items) - 1
cmp := d.cmpFunc(item, d.items[backIdx].item)
if (d.isMax && cmp > 0) || (!d.isMax && cmp < 0) {
d.items = d.items[:backIdx]
continue
}
if cmp == 0 {
d.items[backIdx].idxs = append(d.items[backIdx].idxs, idx)
return
}
break
}
d.items = append(d.items, maxMinCountDequeItem{
item: item,
idxs: []uint64{idx},
})
}

func (d *minMaxCountDeque) dequeue(boundary uint64) {
for !d.isEmpty() {
front := &d.items[0]
firstAlive := 0
for firstAlive < len(front.idxs) && front.idxs[firstAlive] <= boundary {
firstAlive++
}
if firstAlive == 0 {
return
}
front.idxs = front.idxs[firstAlive:]
if len(front.idxs) > 0 {
return
}
d.items = d.items[1:]
}
}

func (d *minMaxCountDeque) frontCount() int {
if d.isEmpty() {
return 0
}
return len(d.items[0].idxs)
}

type partialResult4MaxMinCountSliding struct {
deque *minMaxCountDeque
count int64
isNull bool
}

func allocPartialResult4MaxMinCountSliding(isMax bool, cmp func(i, j any) int) (PartialResult, int64) {
p := &partialResult4MaxMinCountSliding{
deque: newMinMaxCountDeque(isMax, cmp),
isNull: true,
}
return PartialResult(p), DefPartialResult4MaxMinCountSlidingSize + DefMaxMinCountDequeSize
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File list ==\n'
git ls-files 'pkg/executor/aggfuncs/*max*min*count*' 'pkg/executor/aggfuncs/*max*min*' | sed -n '1,120p'

printf '\n== Outline: func_max_min_count.go ==\n'
ast-grep outline pkg/executor/aggfuncs/func_max_min_count.go --view expanded || true

printf '\n== Outline: func_max_min.go ==\n'
ast-grep outline pkg/executor/aggfuncs/func_max_min.go --view expanded || true

printf '\n== Search for deque/memory accounting references ==\n'
rg -n "DefMaxMinCountDequeSize|DefPartialResult4MaxMinCountSlidingSize|memDelta|UpdatePartialResult|Slide|newMinMaxCountDeque|make\\(\\[\\]maxMinCountDequeItem|maxMinCountDeque" pkg/executor/aggfuncs/func_max_min_count.go pkg/executor/aggfuncs/func_max_min.go

printf '\n== Relevant slices: func_max_min_count.go ==\n'
sed -n '1,260p' pkg/executor/aggfuncs/func_max_min_count.go

printf '\n== Relevant slices: func_max_min.go ==\n'
sed -n '1,320p' pkg/executor/aggfuncs/func_max_min.go

Repository: pingcap/tidb

Length of output: 50368


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== func_max_min_count.go: deque + sliding methods ==\n'
sed -n '87,260p' pkg/executor/aggfuncs/func_max_min_count.go

printf '\n== func_max_min.go: deque + sliding methods for int/uint/float32/float64/decimal/string ==\n'
sed -n '166,380p' pkg/executor/aggfuncs/func_max_min.go
sed -n '490,540p' pkg/executor/aggfuncs/func_max_min.go
sed -n '650,700p' pkg/executor/aggfuncs/func_max_min.go
sed -n '808,860p' pkg/executor/aggfuncs/func_max_min.go
sed -n '992,1045p' pkg/executor/aggfuncs/func_max_min.go
sed -n '1158,1210p' pkg/executor/aggfuncs/func_max_min.go

printf '\n== Search for any memory accounting helpers mentioning deque growth or sampling ==\n'
rg -n "sampling|Sample|track.*mem|memory.*track|MemDelta|MemTracker|unsafe.Sizeof\\(Pair|unsafe.Sizeof\\(maxMinCountDequeItem|DefMaxMinCountDequeSize|DefMaxMinDequeSize" pkg/executor/aggfuncs pkg/util pkg/executor | sed -n '1,200p'

Repository: pingcap/tidb

Length of output: 38892


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== func_max_min.go: constructor + deque internals ==\n'
sed -n '1,140p' pkg/executor/aggfuncs/func_max_min.go

printf '\n== func_max_min_count.go: constructor + any constants around allocation ==\n'
sed -n '1,120p' pkg/executor/aggfuncs/func_max_min_count.go

Repository: pingcap/tidb

Length of output: 8808


Sliding deque memory growth is still untracked. allocPartialResult4MaxMinCountSliding only charges the struct plus DefMaxMinCountDequeSize, but newMinMaxCountDeque preallocates 64 entries and enqueue can keep growing d.items as the frame expands. UpdatePartialResult and Slide both return 0 after mutating the deque, so large partitions can bypass the memory tracker; func_max_min.go has the same gap.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/executor/aggfuncs/func_max_min_count.go` around lines 92 - 176, The
sliding deque’s dynamic backing storage is not accounted for after allocation.
Update allocPartialResult4MaxMinCountSliding and the deque mutation paths used
by UpdatePartialResult and Slide to charge memory for initial capacity and any
growth of d.items (including reallocations), and return the corresponding delta
instead of always returning zero; apply the same accounting fix to the analogous
implementation in func_max_min.go.

@ti-chi-bot

ti-chi-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: AilinKid, gengliqi
Once this PR has been reviewed and has the lgtm label, please assign bornchanger, likidu for approval. For more information see the Code Review Process.
Please ensure that each of them provides their approval before proceeding.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

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

Labels

do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. lgtm release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/planner SIG: Planner size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add some helper functions for incremental refresh materialized view

3 participants