Fix COUNT over an empty table (#468) and add multi-client insertAll e2e coverage#471
Merged
Conversation
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>
This was referenced May 19, 2026
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.
Summary
Resolves #468 and adds multi-client CRUD coverage that acts as a regression guard for #470.
#468 —
COUNT(*)over an empty table returnedNULLSince v0.7.0 switched the SQL backend to
googlesqlite,SELECT COUNT(*)— and everyCOUNT/COUNTIFform — over an empty table returned SQLNULLinstead of the typedINT640, 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'sCreateAggregateFunction, whoseiter.Seqwrapper never invokes the aggregate body for a group with zero input rows, soCOUNTnever emitted its0. 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.
TestIssue468CountStarOverEmptyTableexercisesCOUNT(*),COUNT(col),COUNTIFand a no-row-matching filter (all0) plusSUM(staysNULL) over an empty table through the public client.#470 — multi-client insertAll coverage
The e2e conformance corpus only exercised read-only
SELECTqueries, so no test covered streaming rows in throughtabledata.insertAlland reading them back — the path #470 reported as regressed.A case may now carry a
setupblock naming a target table and the rows to stream into it. The harness preloads everysetuptable into the emulator project (schema only, no rows), modelling an emulator started with--data-from-yaml; each client runner then streamssetup.rowsin throughinsertAllbefore running the query. All six runners (Python, Ruby, PHP, Node.js, Java,bqCLI) gained this handling, and a newinsert_all_then_selectcase 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_selectcase passes on all six clients, and a rawinsertAll+query against the publishedghcr.io/goccy/bigquery-emulator:0.7.1image with the reporter's exact YAML (dummy-data-project/dummy_services_sensitive/test_table, emptydata:) also returns the streamed row. The new coverage is kept as a permanent regression guard regardless.Testing
go test ./server/...— green, includingTestIssue468CountStarOverEmptyTable.go test ./test/e2e/...— all six clients green, includinginsert_all_then_select.🤖 Generated with Claude Code