8000 fix(query-generator): resolve malformed SQL and cursor pagination across List RPCs by pachopepe · Pull Request #983 · google/xmc · GitHub
[go: up one dir, main page]

Skip to content

fix(query-generator): resolve malformed SQL and cursor pagination across List RPCs - #983

Merged
pachopepe merged 9 commits into
mainfrom
fix/list-page-with-page-size
Sep 28, 2026
Merged

pachopepe merged 9 commits into
mainfrom
fix/list-page-with-page-size

Conversation

@pachopepe
@pachopepe pachopepe commented Sep 25, 2026 •
Copy link
Copy Markdown
Collaborator

Summary

Fixes server-side Spanner SQL syntax errors (Syntax error: Unexpected keyword ORDER caused by emitting WHERE ORDER BY) and cursor pagination defects when listing resources with page_size, page_token, and optional filter or order_by parameters.

Motivation & Context

When paginating ListExperiments (or ListXidEntries, ListWorkUnits, ListArtifacts, ListStatusMessages) with page_size and no filter, GenerateQuery defaulted ORDER BY to primary key columns (such as bit_reversed_xid), while cursor extractors only populated user-facing fields (such as xid). Consequently, orderedPtFields evaluated to empty while len(ptFields) > 0 triggered the WHERE keyword emission, producing invalid SQL (SELECT ... FROM Experiments e WHERE ORDER BY e.BitReversedXid LIMIT @limit). Additionally, JSON serialization of time.Time cursor values in EncodeAfterCursorPageToken decoded back as string in DecodeAfterCursorPageToken, causing Spanner TIMESTAMP parameter type mismatches when ordering by created, updated, or completed.

Key Changes

  • Query Generator (internal/query_generator/querytools): Extracted resolveOrderedPageTokenFields to match cursor fields against pageTokenFields (respecting equalityFilters from baseFilter), guard WHERE emission on len(orderedPtFields) > 0, and return InvalidArgument when any required sort field is missing from ptFields.
  • Cursor Timestamp & Number Deserialization (internal/query_generator/liststatement): Updated DecodeAfterCursorPageToken to parse numeric cursor values via json.Number and coerce RFC3339 timestamp strings back into time.Time for Spanner TIMESTAMP bind parameters.
  • Schema & Cursor Extractors (internal/storage/spanner/...): Added bit_reversed_xid mappings to experimentschema, artifactschema, and statusmessageschema, populated bit_reversed_xid (spannerutils.ReverseBits(xid)) in ExtractExperimentCursor, ExtractXidCursor, ExtractWorkUnitCursor, ExtractArtifactCursor, and ExtractStatusMessageCursor, and configured OrderByFieldsWithSortDirection on DashboardChartsWithCharts to order and tie-break by chart_id.
  • Validation & XidService Pagination (internal/validator, cmd/xid_service/server): Added normalizePageSize in ListXidEntries (page_size == 0 defaults to defaultXidPageSize = 10, matching ExperimentStateServer), documented the 1024-character maxPageTokenLen limit in ValidatePageToken, and rejected whitespace-only filters in ValidateFilter.

Assumptions

  • None — requirements were fully specified.

Verification & Testing

  • Commands Executed: bazel test //internal/query_generator/... //internal/validator/... //internal/storage/spanner/... //cmd/experiment_state_server/... //cmd/xid_service/... & bazel coverage --combined_report=lcov //internal/query_generator/... //internal/validator/...
  • Coverage & Test Status: All 35 affected Bazel test suites pass (including multi-page Spanner emulator pagination tests for ListExperiments, ListXidEntries, ListWorkUnits, and ListArtifacts), bazel coverage passes across all 6 query generator and validator targets, .githooks/pre-commit passes with 0 findings, and end-to-end pagination was verified on a live GKE development environment.

Related Issues / Bugs

Fixes bug 564895895

TAG=agy
CONV=3a77ad46-f4de-418d-a279-ab38b9667efd

…oss List RPCs

Fix server-side Spanner SQL syntax errors ("Syntax error: Unexpected keyword ORDER"
from malformed "WHERE  ORDER BY") and cursor pagination defects across all List*
endpoints:

1. Extract validateAndOrderPageTokenFields in internal/query_generator/querytools
   to filter page-token cursor fields against non-constant pageTokenFields
   (excluding equalityFilters) and return InvalidArgument whenever any required
   ORDER BY cursor key is missing or nil.
2. Parse RFC3339 string timestamps back into time.Time in DecodeAfterCursorPageToken
   so Spanner parameter bindings for TIMESTAMP columns (created, updated, completed)
   preserve type fidelity after JSON round-tripping.
3. Add bit_reversed_xid to Experiments, Artifacts, and StatusMessages query schema
   FieldToTableMappings and include bit_reversed_xid in cursor extractors across
   Experiments, XidEntries, WorkUnits, Artifacts, and StatusMessages.
4. Normalize page_size == 0 to DefaultPageSize (50) in ListXidEntries and reject
   whitespace-only filter strings in ValidateFilter.

Fixes bug 564895895

TAG=agy
CONV=3a77ad46-f4de-418d-a279-ab38b9667efd
@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request enhances the Spanner query generator and cursor-based pagination to support nullable columns, ensuring correct SQL generation (such as using IS NULL and IS NOT NULL predicates) and preserving int64 types during page token decoding. It also integrates bit_reversed_xid into various cursor extraction helpers and increases the maximum page token length. Feedback on these changes highlights a potential issue with hardcoding "work_unit_id": nil in ExtractStatusMessageCursor which could break pagination for status messages belonging to a work unit, as well as a mismatch between the documentation and implementation of nextPageTokenPrefix regarding nullability checks.

… prefix helper

Comment link: #983 (comment)

Comment link: #983 (comment)

TAG=agy

CONV=3a77ad46-f4de-418d-a279-ab38b9667efd
@pachopepe
pachopepe marked this pull request as ready for review September 25, 2026 09:57
AntonTyb
AntonTyb previously approved these changes Sep 25, 2026
@pachopepe
pachopepe enabled auto-merge September 25, 2026 15:31
@pachopepe
pachopepe disabled auto-merge September 25, 2026 15:31
andrzejfudro
andrzejfudro previously approved these changes Sep 28, 2026
Comment thread internal/validator/parameters.go Resolved
…ation

- Document rationale for 1024-character maxPageTokenLen in internal/validator/parameters.go
- Remove redundant xid-to-bit_reversed_xid derivation from normalizeCursorNumbers in internal/query_generator/liststatement
- Return InvalidArgument in resolveOrderedPageTokenFields when any required sort field is missing from ptFields
- Set OrderByFieldsWithSortDirection on DashboardChartsWithCharts to order and tie-break by chart_id

Comment link: #983 (comment)
Comment link: #983 (comment)
Comment link: #983 (comment)

TAG=agy
CONV=24574bbf-71a0-47f1-8939-622fd31d7bbe
@pachopepe
pachopepe dismissed stale reviews from andrzejfudro and AntonTyb via 86c4e1e September 28, 2026 12:12
@pachopepe
pachopepe added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit aa6665d Sep 28, 2026
11 checks passed
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.

3 participants

0