Skip to content

Add a :type annotation to share a row type between queries - #4630

Open
egimbernat wants to merge 1 commit into
sqlc-dev:mainfrom
egimbernat:query-type-annotation
Open

egimbernat wants to merge 1 commit into
sqlc-dev:mainfrom
egimbernat:query-type-annotation

Conversation

@egimbernat

Copy link
Copy Markdown

Queries that select the same columns each get their own row struct (GetBookRow, ListBooksByAuthorRow, …) even when the structs are identical, so callers end up converting between them. This adds an optional :type <TypeName> after the command:

-- name: GetBook :one :type BookWithAuthor
SELECT books.id, books.title, authors.name AS author_name
FROM books JOIN authors ON authors.id = books.author_id
WHERE books.id = $1;

-- name: ListBooksByAuthor :many :type BookWithAuthor
SELECT books.id, books.title, authors.name AS author_name
FROM books JOIN authors ON authors.id = books.author_id
WHERE books.author_id = $1;

Both methods return BookWithAuthor, which is emitted once. It works with sqlc.embed as well.

Compatibility is checked strictly, as #3595 asks: generation fails when two queries naming the same type would give different fields (name, Go type or struct tag, in order), and the error names the first differing column:

query ListBooks: :type BookWithAuthor does not match query GetBook: column 3 is AuthorID int64, want AuthorName string

It also fails if the name is already a model's or the query returns a single column, since there is no struct to name. An explicit :type wins over the existing reuse of a table's model when the columns happen to match one.

Changes

  • metadata.ParseQueryNameAndType now returns Metadata (with the new TypeName) instead of two strings, so its three callers no longer rebuild it from parts.
  • plugin.Query gets type_name = 9 (regenerated with buf generate), so plugins for other languages can honour the annotation. The two JSON plugin goldens gain the field.
  • Go codegen: rowTypes in internal/codegen/golang/result.go.
  • Docs: new howto/row_types.md, plus a note in reference/query-annotations.md.
  • Tests: endtoend case query_type_annotation (plain columns, aliases, sqlc.embed, and a query whose columns match a model), and four failing cases with stderr.txt: mismatched columns, a model's name, a single column, and malformed annotations.

go test --tags=examples ./... passes locally against PostgreSQL 16 and MySQL 9.

Fixes #781
Fixes #3595
Related: #2252, which asks to return an existing model; this names a shared type instead.

A query that returns more than one column gets a struct named after it, so
queries selecting the same columns return different types. `:type <TypeName>`
after the command names the row type instead, and queries that use the same
name return one struct. Generation fails if their columns give different
fields, if the name is a model's, or if the query returns a single column.

Plugins receive the name as Query.type_name. ParseQueryNameAndType now
returns the Metadata, so its callers no longer rebuild it from parts.

Fixes sqlc-dev#781
Fixes sqlc-dev#3595

Co-authored-by: ali <ali.dehkharghani@megadevs.de>
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.

Allow the specification of shared row datatypes Override query return type

1 participant