sqlc-dev / sqlc

Generate type-safe code from SQL
https://sqlc.dev
MIT License
12.1k stars 774 forks source link

Struct Overrides (proposal) #1176

Open nkev opened 2 years ago

nkev commented 2 years ago

I can see quite a few unresolved issues related to the customization of sqlc generated structs.

I'm in a similar position where I have several custom struct tags and embedded structs in my product model that flattens out to a single Postgres table, e.g.:

package models

//Product is a Postgres table, parts of which are imported from the remote ERP and another remote website.
//All fields of ERPProduct and WebProduct are contained in the Postgres table
type Product struct {
    ERPProduct   //note: embedded ERPProduct.ProductCode is primary key!
    WebProduct
    Culture      string  `json:"culture" pg:"culture"` 
    IsEnabled    bool   `json:"isEnabled,omitempty" pg:"is_enabled"`
}

//ERPProduct represents an ERP product record from the remote SQL Server
type ERPProduct struct {
    ProductCode  string   `json:"productCode" db:"ProductCode" pg:"product_code"` //db tag is for ERP  SQL Server
    Category     string   `json:"categoryCode,omitempty" db:"Category" pg:"category"`
    //...other columns...
}

//Import will connect to the ERP and import an ERPProduct from an SQL Server
func (p *ERPProduct) Import() (error) {
   //code to import and populate p using sqlx
   return nil
}

//WebProduct represents a web product 
type WebProduct struct {
    WebCode         string  `json:"webcode,omitempty" pg:"web_code"`
    WebDescription  string  `json:"webDescription,omitempty" pg:"web_description"`
    Slug            string  `json:"slug,omitempty" pg:"slug"`
    //...other columns...
}

//Import will connect to another remote website and import a WebProduct via an API
func (p *WebProduct) Import() (error) {
   //code to import and populate p 
   return nil
}

As you can see, there are too many issues with this product model to port across to sqlc.

However, I have an idea that might solve all of those issues in one foul swoop.. :)

We can currently override fields like this:

version: "1"
packages: [...]
overrides:
  - go_type: "github.com/gofrs/uuid.UUID"
    db_type: "uuid"

Could we not do the same for structs? i.e. override the generation of the 'product' struct with my custom product struct like this:

version: "1"
packages: [...]
overrides:
  - go_struct: "github.com/me/myproj/models.Product"
    db_struct: "Product"

The flattenned out 'product' definition would still be required in the sqlc schema file (only for validation in sqlc generated queries):

create table products
(
    product_code text primary key,
    category     text,
    ...
    webcode      text,
    ...
    culture  text,
    is_enabled   boolean default true  not null
);

sqlc would then generate the struct of the 'product' schema but comment it out with an 'overriden by..' comment above it.

sqlc would then import github.com/me/myproj/models in the query.sql.go file and use the Product struct in there.

Any product related query errors thereafter will be the responsibility of the developer...

kyleconroy commented 2 years ago

See a similar issue here: https://github.com/kyleconroy/sqlc/issues/755

nkev commented 2 years ago

Yes, I saw that and others too related to custom tags. I'm sure there will also be other issues in future related to customization of structs. For example, in my scenario above I thought about omitting product in the schema file so the struct does not get generated by sqlc. But then I realized the product table gets referenced in many queries so this means I cannot use sqlc at all, even though I would so love to.

To be fair, sqlc cannot possibly cater for all struct use cases, so I think allowing user overriden structs is the most flexible solution, don't you agree?