Skip to content

Add Cloud Spanner and BigQuery support #1505

Open

Description

@StevenACoffman

What do you want to change?

Just as sqlc supports PostgreSQL dialect, there is a similar binding for ZetaSQL dialect in https://github.com/goccy/go-zetasql

The ZetaSQL dialect is used by both GCP Cloud Spanner and BigQuery.

What database engines need to be changed?

ZetaSQL

What programming language backends need to be changed?

Go

Activity

  1. kyleconroy commented on Mar 24, 2022

    @kyleconroy
    Collaborator

    This is a great idea. Unlike a few other engines, ZetaSQL has a full parser that's easy to import. I'll need to play around with it to make sure that it builds on all our supported platforms.

  2. added and removed
    triageNew issues that hasn't been reviewed
    on Aug 28, 2022
  3. changed the title [-]Support GCP Cloud Spanner and BigQuery via ZetaSQL dialect[/-] [+]Cloud Spanner and BigQuery support[/+] on Sep 22, 2023
  4. kyleconroy commented on Sep 22, 2023

    @kyleconroy
    Collaborator

    I removed the accepted label as we aren't planning on working on this in the short term. Still excited about it!

  5. changed the title [-]Cloud Spanner and BigQuery support[/-] [+]Add Cloud Spanner and BigQuery support[/+] on Sep 22, 2023
  6. matdurand commented on Mar 20, 2025

    @matdurand

    @kyleconroy how hard would you say this is given we could reuse the ZetaSQL parser? I would really like to have Spanner support with SQLc. And would you accept a PR if I have some time to spare on this?

  7. matdurand commented on Mar 20, 2025

    @matdurand

    Hum, I see you're trying to remove CGO dependencies to be able to run on Windows and golang ZetaSQL is a CGO dependency, so this probably won't fly ...

  8. matdurand commented on Mar 21, 2025

    @matdurand

    Here is a potential non CGO replacement for the query parser. It's probably just for Spanner though, not BigQuery
    https://github.com/cloudspannerecosystem/memefish

  9. apstndb commented on Aug 7, 2025

    @apstndb

    ZetaSQL is not only non-CGO; it also cannot be used to parse Spanner GoogleSQL DDL because Spanner GoogleSQL DDL is not compatible with ZetaSQL DDL. For example, cloud-spanner-emulator uses ZetaSQL for query/DML parsing, but relies on its own DDL parser for DDL operations:
    https://github.com/GoogleCloudPlatform/cloud-spanner-emulator/tree/master/backend/schema/parser

    Currently, I believe memefish is the most feature-complete parser for Spanner GoogleSQL.
    In addition, memefish provides all the necessary functionality to implement a Spanner GoogleSQL engine for sqlc.

    FYI:
    I've created a proof-of-concept implementation for sqlc Spanner support by vibe-coding.
    If there is interest, I'm happy to reimplement it to fit your contribution policy.

    Disclaimer: I'm a contributor to memefish.

  10. bramoudith-sc commented on Oct 22, 2025

    @bramoudith-sc

    @apstndb would you mind creating a pull request?

  11. apstndb commented on Oct 22, 2025

    @apstndb

    @bramoudith-sc
    I think this project doesn't accept a PR of new engine currently.

  12. timtatt commented on Jun 13, 2026

    @timtatt

    Seeing if we can re-envigorate this thread - there have been a few changes to GoogleSQL support since this was raised.

    Usage

    I primarily use GoogleSQL for Spanner, though this would also be useful for BigQuery users. The main functionality of sqlc I utilise is the sqlc generate (not using sqlc cloud). At this stage, not making much use out of the sqlc analysis functionality.

    Would be awesome if this could be incrementally released starting with the generate functionality and following up with analysis integration.

    Useful Libraries

    Since the last update on this issue, GoogleSQL has consolidated with ZetaSQL

    GoogleSQL Documentation

    Happy to Help

    Please let me know how I can help with implementing this functionality, happy to submit a PR or doing some alpha testing!

  13. kyleconroy commented on Aug 18, 2026

    @kyleconroy
    Collaborator

    If you'd like to try out a very early version of BigQuery and Spanner support, you can build sqlc from main and use the new sqlc parse and sqlc analyze commands. I'll be wiring it up to code gen soon.

  14. apstndb commented on Aug 18, 2026

    @apstndb

    @kyleconroy

    Great work.

    In general, I think building an implementation that passes all tests based on the .test files in google/googlesql is a good approach.

    However, there seems to be one unresolved issue with this approach: Spanner GoogleSQL DDL is, in fact, not the same as GoogleSQL DDL.

    https://docs.cloud.google.com/spanner/docs/migrating-primary-keys?hl=en#migrate-bit-reversed-key-columns

    $ echo 'CREATE TABLE MyFirstTable (
      Id INT64 GENERATED BY DEFAULT AS IDENTITY (START COUNTER WITH 11000),
      Name STRING(MAX),
    ) PRIMARY KEY (Id)
    ' | mise x go:github.com/sqlc-dev/sqlc/cmd/sqlc@main -- sqlc parse --dialect googlesql
    Error: parse error: Syntax error: Expected keyword WITH but got identifier "COUNTER" [at 2:52]

    spanner_legacy_ddl.test does not track the current Spanner DDL syntax, so many constructs will need to be implemented by referring to other sources, such as the Cloud Spanner emulator.

  15. kyleconroy commented on Oct 14, 2026

    @kyleconroy
    Collaborator

    @apstndb I believe this should now be fixed, as I've expanded statement support in our parser. Please try out the code generation on main, which was added in #4621

  16. vvakame commented on Oct 26, 2026

    @vvakame
    Contributor

    Thanks for the Spanner support!

    I had Claude Code (an AI coding agent) prototype replacing our in-house Spanner query builder with sqlc, using main (3ad8b20). As part of that, it ran our production schema (99 tables, 160 indexes) and 426 real queries through sqlc. The schema parsed cleanly, and 425 of the 426 queries type-checked, including @{FORCE_INDEX=...}, THEN RETURN, IN UNNEST(@x), correlated subqueries and DML.

    The AI found two issues along the way. I'm reporting them here; the analysis below is its findings, which I've reviewed.

    All examples use this schema:

    CREATE TABLE authors (
      id   INT64 NOT NULL,
      name STRING(MAX) NOT NULL,
    ) PRIMARY KEY (id);
    
    CREATE TABLE books (
      author_id INT64 NOT NULL,
      id        INT64 NOT NULL,
      title     STRING(MAX) NOT NULL,
    ) PRIMARY KEY (author_id, id),
      INTERLEAVE IN PARENT authors ON DELETE CASCADE;

    1. ARRAY(SELECT AS STRUCT ...) is typed as an array of the first column

    -- name: ListAuthorsWithBooks :many
    SELECT
      a.id,
      a.name,
      ARRAY(
        SELECT AS STRUCT b.id, b.title
        FROM books AS b
        WHERE b.author_id = a.id
      ) AS books
    FROM authors AS a;

    generates

    type ListAuthorsWithBooksRow struct {
    	ID    int64
    	Name  string
    	Books []spanner.NullInt64
    }

    and the column's type_expr is array(int64) instead of array(struct(id: int64, title: string)). No error is reported, so the problem only surfaces at scan time. Fetching interleaved children this way is common in Spanner: 142 of our 426 queries use it.

    The cause appears to be:

    1. convertSelect (internal/engine/googlesql/convert.go) drops zjast.Select.SelectAs, so AS STRUCT never reaches the analyzer.
    2. typeSubLink (internal/core/analyzer/expr.go) types an ARRAY_SUBLINK from cols[0] only.

    TypeExpr can already express labeled struct fields, so a ~20 line local patch fixes the inference. The patch carries an AsStruct flag on ast.SelectStmt and builds array(struct(label: T, ...)) from all subquery columns. With it, type_expr comes out as expected:

    {"name": "array", "args": [{"type": {"name": "struct", "args": [
      {"label": "id",    "type": {"name": "int64"}},
      {"label": "title", "type": {"name": "string", "args": [{"ident": "max"}]}}
    ]}}]}

    googlesqlType in internal/codegen/golang has no struct case yet, so the built-in Go generator would still emit []any here. A plugin reading type_expr gets the correct type, though.

    2. Unaliased expressions are named ?column?, which leaks into the generated Go

    With engine: googlesql, even SELECT 1 fails to generate:

    -- name: One :one
    SELECT 1;
    
    -- name: SumBookIDs :one
    SELECT COALESCE(SUM(id), 0) FROM books WHERE author_id = @author_id;
    error generating code: source error: 35:6: illegal character U+003F '?' (and 12 more errors)
    
    	var ?column? int64
    	err := row.Scan(&?column?)
    	return ?column?, err

    The same queries with engine: postgresql generate column_1 and coalesce. The Go generator already has a column_N fallback for unnamed columns (columnName in internal/codegen/golang/result.go, covered by the yaml_overrides end-to-end test). It never applies here, because the core analyzer names the column ?column? (targetName in internal/core/analyzer/projection.go) rather than leaving it empty.

    When the expression isn't the only column, it happens to work: StructName replaces the ?s, so the struct field becomes Column. The single-column path only replaces $, so ?column? survives as the variable name.

    In our case this isn't a blocker. The two production queries with an unaliased COALESCE(SUM(...), 0) read results by position, which is the only reason they have no alias, and adding one is trivial.


    If patches for either would be welcome, we're happy to open PRs.

    For context, we use a process plugin that generates code for the native cloud.google.com/go/spanner client instead of database/sql, because our code relies on the client's transactions and mutations. The new type_expr field made that straightforward.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions