Skip to content

Fix COUNT over an empty table (#468) and add multi-client insertAll e2e coverage#471

Merged
goccy merged 2 commits into
mainfrom
fix/v071-bug-triage
May 19, 2026
Merged

Fix COUNT over an empty table (#468) and add multi-client insertAll e2e coverage#471
goccy merged 2 commits into
mainfrom
fix/v071-bug-triage

Conversation

@goccy

@goccy goccy commented May 19, 2026

Copy link
Copy Markdown
Owner

Summary

Resolves #468 and adds multi-client CRUD coverage that acts as a regression guard for #470.

#468COUNT(*) over an empty table returned NULL

Since v0.7.0 switched the SQL backend to googlesqlite, SELECT COUNT(*) — and every COUNT/COUNTIF form — over an empty table returned SQL NULL instead of the typed INT64 0, breaking every client that scans the column into a non-pointer integer.

The root cause was in googlesqlite: aggregates were registered through ncruces/go-sqlite3's CreateAggregateFunction, whose iter.Seq wrapper never invokes the aggregate body for a group with zero input rows, so COUNT never emitted its 0. Fixed in googlesqlite v0.2.2 (goccy/googlesqlite#11); this PR bumps the dependency to pick up that release.

The emulator forwards the backend's value unchanged, so no emulator code change is needed. TestIssue468CountStarOverEmptyTable exercises COUNT(*), COUNT(col), COUNTIF and a no-row-matching filter (all 0) plus SUM (stays NULL) over an empty table through the public client.

#470 — multi-client insertAll coverage

The e2e conformance corpus only exercised read-only SELECT queries, so no test covered streaming rows in through tabledata.insertAll and reading them back — the path #470 reported as regressed.

A case may now carry a setup block naming a target table and the rows to stream into it. The harness preloads every setup table into the emulator project (schema only, no rows), modelling an emulator started with --data-from-yaml; each client runner then streams setup.rows in through insertAll before running the query. All six runners (Python, Ruby, PHP, Node.js, Java, bq CLI) gained this handling, and a new insert_all_then_select case asserts the streamed rows are visible to the query that follows.

On #470 reproduction: the regression could not be reproduced. The new insert_all_then_select case passes on all six clients, and a raw insertAll+query against the published ghcr.io/goccy/bigquery-emulator:0.7.1 image with the reporter's exact YAML (dummy-data-project / dummy_services_sensitive / test_table, empty data:) also returns the streamed row. The new coverage is kept as a permanent regression guard regardless.

Testing

  • go test ./server/... — green, including TestIssue468CountStarOverEmptyTable.
  • go test ./test/e2e/... — all six clients green, including insert_all_then_select.

🤖 Generated with Claude Code

goccy and others added 2 commits May 20, 2026 01:01
Since v0.7.0 switched the SQL backend to googlesqlite, `SELECT COUNT(*)`
(and every COUNT/COUNTIF form) over an empty table returned SQL NULL
instead of the typed INT64 0, breaking every client that scans the
column into a non-pointer integer.

The root cause was in googlesqlite: aggregates were registered through
ncruces/go-sqlite3's CreateAggregateFunction, whose iter.Seq wrapper
never invokes the aggregate body for a group with zero input rows, so
COUNT never emitted its 0. Fixed in googlesqlite v0.2.2
(goccy/googlesqlite#11); this commit picks up that release.

The emulator forwards the backend's value unchanged, so no emulator
code change is needed. TestIssue468CountStarOverEmptyTable is a
regression test exercising COUNT(*), COUNT(col), COUNTIF and a
no-row-matching filter (all 0) plus SUM (stays NULL) over an empty
table through the public client.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The conformance corpus only exercised read-only SELECT queries, so no
test covered streaming rows in through tabledata.insertAll and reading
them back — the path issue #470 reported as regressed.

A case may now carry a `setup` block naming a target table and the rows
to stream into it. The harness preloads every `setup` table into the
emulator project (schema only, no rows), modelling an emulator started
with --data-from-yaml; each client runner then streams `setup.rows` in
through insertAll before running the query. All six runners (Python,
Ruby, PHP, Node.js, Java, bq CLI) gained this handling, and a new
`insert_all_then_select` case asserts the streamed rows are visible to
the query that follows.

This makes the suite a regression guard for #470 across every client.
Note: #470 itself could not be reproduced — the new case passes on all
six clients, and a raw insertAll+query against the published v0.7.1
image with the reporter's exact YAML also returns the streamed row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@goccy goccy merged commit e8dc89b into main May 19, 2026
12 checks passed
@goccy goccy deleted the fix/v071-bug-triage branch May 19, 2026 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant