sqlc-dev / sqlc

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

Enum field redeclared error in Go for enum value "-" #1310

Open ajlive opened 2 years ago

ajlive commented 2 years ago

Version

Other

What happened?

This is on version 1.11.0, main from today.

I have an enum field where one of the values is "-", eg, in this line from a create table:

`my_enum_field` enum('a','b','c','-') DEFAULT NULL,

Sqlc generates invalid Go code from this:

type MyEnumField string // line 368

const (
    MyEnumFieldA MyEnumField = "a"
    MyEnumFieldB MyEnumField = "b"
    MyEnumFieldC MyEnumField = "c"
    MyEnumField  MyEnumField = "-" // line 374
)

with the error (removed absolute path)

internal/db/models.go:374:2: MyEnumField redeclared in this block
    /.../internal/db/models.go:368:6: previous declaration

I'm not using this field in any of my queries. I can patch it well enough to get my program to run by changing line 374 to:

    MyEnumFieldHyphen MyEnumField = "-"

I was able to reproduce this error using the playground: https://play.sqlc.dev/p/f37da9bd1283a13d10c574e9c81adedd4474fa8fe4e93fd0a220e1310efa588a

Relevant log output

No response

Database schema

CREATE TABLE my_table (
  my_enum_field enum('a','b','c','-')
);

SQL queries

-- no query required to reproduce

Configuration

No response

Playground URL

https://play.sqlc.dev/p/f37da9bd1283a13d10c574e9c81adedd4474fa8fe4e93fd0a220e1310efa588a

What operating system are you using?

macOS

What database engines are you using?

MySQL

What type of code are you generating?

Go

JCraigWasTaken commented 1 year ago

This is occurring for me as well:

CREATE TABLE `equation` (
    `id` serial AUTO_INCREMENT PRIMARY KEY NOT NULL,
    `date` datetime NOT NULL,
    `varA` double NOT NULL,
    `operation` enum('+','-','*','/'),
    `varB` double NOT NULL,
    `result` double NOT NULL);

generates:

type EquationOperation string

const (
    EquationOperationValue0 EquationOperation = "+"
    EquationOperation       EquationOperation = "-"
    EquationOperationValue2 EquationOperation = "*"
    EquationOperationValue3 EquationOperation = "/"
)
ajlive commented 6 months ago

I was trying sqlc again and got this same error, and completely forgot I created this issue until I found it again by searching! I can report that this happens on Linux as well as macOS. Is there a workaround?

ajlive commented 6 months ago

The problem is occurring in buildEnums, where StructName is invoked: https://github.com/sqlc-dev/sqlc/blob/811b6b0351108fd64e445b94180d07f2539d0a06/internal/codegen/golang/result.go#L43-L48

EnumReplace replaces hyphen with underscore, so the name passed to StructName is my_table_my_enum_field__, with a double-underscore at the end.

Then in StructName there is this conversion from snake case, which splits on underscore, causing the enum value name to be the same as the enum type name: https://github.com/sqlc-dev/sqlc/blob/811b6b0351108fd64e445b94180d07f2539d0a06/internal/codegen/golang/struct.go#L34-L40

Looking at EnumReplace (specifically, the enumReplacer function it invokes), there are a few other characters affected: https://github.com/sqlc-dev/sqlc/blob/811b6b0351108fd64e445b94180d07f2539d0a06/internal/codegen/golang/enum.go#L31-L32

A naive fix would be to simply check for a string that's all underscores after calling EnumReplace:

diff --git a/internal/codegen/golang/result.go b/internal/codegen/golang/result.go
index 560e112af..81b0b9aa4 100644
--- a/internal/codegen/golang/result.go
+++ b/internal/codegen/golang/result.go
@@ -41,7 +41,14 @@ func buildEnums(req *plugin.GenerateRequest, options *opts.Options) []Enum {
            seen := make(map[string]struct{}, len(enum.Vals))
            for i, v := range enum.Vals {
                value := EnumReplace(v)
-               if _, found := seen[value]; found || value == "" {
+               isAllUnderscores := true
+               for _, r := range value {
+                   if r != '_' {
+                       isAllUnderscores = false
+                       break
+                   }
+               }
+               if _, found := seen[value]; found || value == "" || isAllUnderscores {
                    value = fmt.Sprintf("value_%d", i)
                }
                e.Constants = append(e.Constants, Constant{

It looks like I can work around this problem using overrides, and if I want control over naming this is what I'd do anyway, so a naive fix just to make sure there are no compilation errors seems fine.

ajlive commented 6 months ago

It looks like I can work around this problem using overrides, and if I want control over naming this is what I'd do anyway

Hmm. It appears this only works for postgres, and not mysql, since mysql doesn't have user-defined types. So I'm back to looking for a workaround.

ajlive commented 6 months ago

It appears this only works for postgres, and not mysql, since mysql doesn't have user-defined types.

I was confused: it is possible to override mysql enums, but the incorrect type causing the compiler error is still generated: it just is never used. It would be nice if you could configure sqlc not to generate this unused type, since I can easily imagine accidentally importing it instead of the override type, but it's vital in this case where the generated code is uncompilable. Maybe I'm just missing this option?

Here's an example of the unused type being generated with sqlc v1.26 in case this is unexpected behavior: overrides-test.zip