You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
Reacted by Steve Coffman, Burak Karakan, Shogo Iwano, Yuki Ito, John Wang, Masahiro Wakame, Dan Gurney, Bill Sorensen, ginokent, Sho Minagawa and 5 more
@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?
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 ...
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.
Reacted by nnaka2992, Alexandre Thenorio, Fredrik Averpil, Tim Tattersall, bramoudith-sc and Masahiro Wakame
Reacted by Alexandre Thenorio, Fredrik Averpil and Masahiro Wakame
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
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.
$ 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.
@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
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:
CREATETABLEauthors (
id INT64 NOT NULL,
name STRING(MAX) NOT NULL,
) PRIMARY KEY (id);
CREATETABLEbooks (
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 :manySELECTa.id,
a.name,
ARRAY(
SELECTAS STRUCT b.id, b.titleFROM books AS b
WHEREb.author_id=a.id
) AS books
FROM authors AS a;
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:
convertSelect (internal/engine/googlesql/convert.go) drops zjast.Select.SelectAs, so AS STRUCT never reaches the analyzer.
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:
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 :oneSELECT1;
-- name: SumBookIDs :oneSELECT 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? int64err:=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.
Activity
kyleconroy commentedon Mar 24, 2022
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.
[-]Support GCP Cloud Spanner and BigQuery via ZetaSQL dialect[/-][+]Cloud Spanner and BigQuery support[/+]kyleconroy commentedon Sep 22, 2023
I removed the accepted label as we aren't planning on working on this in the short term. Still excited about it!
[-]Cloud Spanner and BigQuery support[/-][+]Add Cloud Spanner and BigQuery support[/+]matdurand commentedon Mar 20, 2025
@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?
matdurand commentedon Mar 20, 2025
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 ...
matdurand commentedon Mar 21, 2025
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
feat: Add Cloud Spanner engine PoC for sqlc
apstndb commentedon Aug 7, 2025
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.
bramoudith-sc commentedon Oct 22, 2025
@apstndb would you mind creating a pull request?
apstndb commentedon Oct 22, 2025
@bramoudith-sc
I think this project doesn't accept a PR of new engine currently.
timtatt commentedon Jun 13, 2026
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
sqlcI 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!
kyleconroy commentedon Aug 18, 2026
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.
apstndb commentedon Aug 18, 2026
@kyleconroy
Great work.
In general, I think building an implementation that passes all tests based on the
.testfiles ingoogle/googlesqlis 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
spanner_legacy_ddl.testdoes 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.kyleconroy commentedon Oct 14, 2026
@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
vvakame commentedon Oct 26, 2026
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:
1.
ARRAY(SELECT AS STRUCT ...)is typed as an array of the first columngenerates
and the column's
type_exprisarray(int64)instead ofarray(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:
convertSelect(internal/engine/googlesql/convert.go) dropszjast.Select.SelectAs, soAS STRUCTnever reaches the analyzer.typeSubLink(internal/core/analyzer/expr.go) types anARRAY_SUBLINKfromcols[0]only.TypeExprcan already express labeled struct fields, so a ~20 line local patch fixes the inference. The patch carries anAsStructflag onast.SelectStmtand buildsarray(struct(label: T, ...))from all subquery columns. With it,type_exprcomes out as expected:{"name": "array", "args": [{"type": {"name": "struct", "args": [ {"label": "id", "type": {"name": "int64"}}, {"label": "title", "type": {"name": "string", "args": [{"ident": "max"}]}} ]}}]}googlesqlTypeininternal/codegen/golanghas nostructcase yet, so the built-in Go generator would still emit[]anyhere. A plugin readingtype_exprgets the correct type, though.2. Unaliased expressions are named
?column?, which leaks into the generated GoWith
engine: googlesql, evenSELECT 1fails to generate:The same queries with
engine: postgresqlgeneratecolumn_1andcoalesce. The Go generator already has acolumn_Nfallback for unnamed columns (columnNameininternal/codegen/golang/result.go, covered by theyaml_overridesend-to-end test). It never applies here, because the core analyzer names the column?column?(targetNameininternal/core/analyzer/projection.go) rather than leaving it empty.When the expression isn't the only column, it happens to work:
StructNamereplaces the?s, so the struct field becomesColumn. 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/spannerclient instead ofdatabase/sql, because our code relies on the client's transactions and mutations. The newtype_exprfield made that straightforward.