From 612e545ae29a3dc1d7cbdc3b22628a00f5346a45 Mon Sep 17 00:00:00 2001 From: Jack Ling <34231795+lingcoder@users.noreply.github.com> Date: Fri, 27 Feb 2026 16:12:58 +0800 Subject: [PATCH] fix(databse/gdb): use COUNT(1) if fields number is greater than 1 even when parameter `useFieldForCount` is true in AllAndCount/ScanAndCount (#4701) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fix bug where `AllAndCount(true)` with multiple fields generates invalid SQL `COUNT(field1, field2, ...)` causing syntax error. ## Root Cause When `useFieldForCount=true`, the COUNT query inherits the fields configuration from the model: ```go // Before (buggy code) if !useFieldForCount { countModel.fields = []any{Raw("1")} } // When useFieldForCount=true, fields remain as ["id", "nickname"] // Generates: SELECT COUNT(id, nickname) FROM table ❌ ``` ## Fix Always use `COUNT(1)` regardless of `useFieldForCount` parameter since COUNT() accepts only one argument: ```go // After (fixed code) // Always use COUNT(1) for counting, regardless of useFieldForCount. // COUNT() accepts only one argument, so we can't use multiple fields. countModel.fields = []any{Raw("1")} ``` Applied to both `AllAndCount()` and `ScanAndCount()` methods. ## Tests Added `Test_Issue4698` with 5 test cases: 1. AllAndCount(true) with multiple fields 2. AllAndCount(false) with multiple fields (baseline) 3. ScanAndCount with multiple fields 4. AllAndCount with single field 5. AllAndCount with WHERE condition All tests verify that COUNT generates valid SQL and returns correct count. ## Related Fixes #4698 Ref #4703 (discovered during pagination test development) --- .../drivers/mysql/mysql_z_unit_issue_test.go | 83 +++++++++++++++++++ .../sqlite/sqlite_z_unit_model_test.go | 20 ++--- .../sqlitecgo/sqlitecgo_z_unit_model_test.go | 31 ++++--- database/gdb/gdb_model_select.go | 26 ++++-- 4 files changed, 131 insertions(+), 29 deletions(-) diff --git a/contrib/drivers/mysql/mysql_z_unit_issue_test.go b/contrib/drivers/mysql/mysql_z_unit_issue_test.go index 265ac50f3..72c7185de 100644 --- a/contrib/drivers/mysql/mysql_z_unit_issue_test.go +++ b/contrib/drivers/mysql/mysql_z_unit_issue_test.go @@ -1983,3 +1983,86 @@ func Test_Issue4697(t *testing.T) { t.AssertNE(result[0]["nickname"], nil) }) } + +// https://github.com/gogf/gf/issues/4698 +func Test_Issue4698(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + // Test 1: AllAndCount with multiple fields should generate valid COUNT SQL + gtest.C(t, func(t *gtest.T) { + result, count, err := db.Model(table).Fields("id, nickname").AllAndCount(true) + t.AssertNil(err) + t.Assert(count, TableSize) + t.Assert(len(result), TableSize) + t.AssertNE(result[0]["id"], nil) + t.AssertNE(result[0]["nickname"], nil) + t.Assert(result[0]["passport"], nil) + }) + + // Test 2: AllAndCount(false) with multiple fields + gtest.C(t, func(t *gtest.T) { + result, count, err := db.Model(table).Fields("id, nickname").AllAndCount(false) + t.AssertNil(err) + t.Assert(count, TableSize) + t.Assert(len(result), TableSize) + }) + + // Test 3: ScanAndCount with multiple fields + gtest.C(t, func(t *gtest.T) { + type User struct { + Id int + Nickname string + } + var users []User + var total int + err := db.Model(table).Fields("id, nickname").ScanAndCount(&users, &total, true) + t.AssertNil(err) + t.Assert(total, TableSize) + t.Assert(len(users), TableSize) + t.AssertGT(users[0].Id, 0) + t.AssertNE(users[0].Nickname, "") + }) + + // Test 4: AllAndCount with single field and useFieldForCount=true + gtest.C(t, func(t *gtest.T) { + result, count, err := db.Model(table).Fields("id").AllAndCount(true) + t.AssertNil(err) + t.Assert(count, TableSize) + t.Assert(len(result), TableSize) + t.Assert(len(result[0]), 1) + }) + + // Test 5: AllAndCount with Where condition + gtest.C(t, func(t *gtest.T) { + result, count, err := db.Model(table).Fields("id, nickname").Where("id 0 { countModel = countModel.Cache(m.pageCacheOption[0]) @@ -341,9 +348,16 @@ func (m *Model) Scan(pointer any, where ...any) error { func (m *Model) ScanAndCount(pointer any, totalCount *int, useFieldForCount bool) (err error) { // support Fields with *, example: .Fields("a.*, b.name"). Count sql is select count(1) from xxx countModel := m.Clone() - // If useFieldForCount is false, set the fields to a constant value of 1 for counting - if !useFieldForCount { - countModel.fields = []any{Raw("1")} + // Decide how to build the COUNT() expression: + // - If caller explicitly wants to use the single field expression for counting, + // honor it (e.g. Fields("DISTINCT col") with useFieldForCount = true). + // - Otherwise, clear fields to let Count() use its default COUNT(1), + // avoiding invalid COUNT(field1, field2, ...) with multiple fields, + // or incorrect COUNT(DISTINCT 1) when Distinct() is set. + if useFieldForCount && len(m.fields) == 1 { + countModel.fields = m.fields + } else { + countModel.fields = nil } if len(m.pageCacheOption) > 0 { countModel = countModel.Cache(m.pageCacheOption[0])