Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go Generated Nullable Types Are Not Serializable #897

Open
2 tasks done
barrownicholas opened this issue Feb 22, 2025 · 1 comment · May be fixed by #898
Open
2 tasks done

Go Generated Nullable Types Are Not Serializable #897

barrownicholas opened this issue Feb 22, 2025 · 1 comment · May be fixed by #898
Labels
bug Something isn't working

Comments

@barrownicholas
Copy link

Bug report

  • I confirm this is a bug with Supabase, not with my own application.
  • I confirm I have searched the Docs, GitHub Discussions, and Discord.

Describe the bug

These lines define the nullable types:
https://github.com/supabase/postgres-meta/blob/b85bf01fe51789588b87b4552d593b5510632bf9/src/server/templates/go.ts#L277C1-L288C2

The problem is, Go cannot automatically Decode into an object that uses the sql nullable variables. The modern approach is to use pointers, which can be nullable.

To Reproduce

In the below example, I have copied (a) the generated struct using supabase cli, (b) a sample response from a supabase rest call, and (c) the standard approach to decoding into a go object. As you can see, the error returns:

json: cannot unmarshal string into Go struct field PublicUsersSelect.date_of_birth of type sql.NullString

Run Online: https://go.dev/play/p/hOkZo4xt0bo

package main

import (
	"database/sql"
	"encoding/json"
	"log"
	"strings"
)

// generated by supabase; copied here for demo
type PublicUsersSelect struct {
	ActiveOrganizationId sql.NullString `json:"active_organization_id"`
	Avatar               sql.NullString `json:"avatar"`
	DateOfBirth          sql.NullString `json:"date_of_birth"`
	FirstName            string         `json:"first_name"`
	FullName             string         `json:"full_name"`
	Id                   string         `json:"id"`
	IsSetup              bool           `json:"is_setup"`
	LastName             string         `json:"last_name"`
}

func main() {
	// usually from api; hard-coded here for demo
	res := `{"id":"35527ac4-dfd0-4743-89ab-a3ea226e4466","first_name":"Nicholas","last_name":"Barrow","full_name":"Nicholas Barrow","date_of_birth":"2002-02-11","avatar":null,"active_organization_id":"c7b65312-45b9-4076-a8c5-f9054fef435c","is_setup":true}`

	var user PublicUsersSelect
	err := json.NewDecoder(strings.NewReader(res)).Decode(&user)
	if err != nil {
		log.Println(err)
	}
}

Expected behavior

The modern approach is to use pointers, which can be nullable. Another approach is to use custom wrappers with initializers, but this seems more cumbersome.

@barrownicholas barrownicholas added the bug Something isn't working label Feb 22, 2025
@barrownicholas barrownicholas linked a pull request Feb 22, 2025 that will close this issue
@barrownicholas
Copy link
Author

barrownicholas commented Feb 22, 2025

I just tested this using a quick replacement script (i.e., by just replacing all the values exactly as this #898 would) and it works perfectly, including with type-safety.

Edit: here is the script I used, to test, if you want to try:

package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"strings"
)

var replacements = map[string]string{
	"sql.NullString":  "*string",
	"sql.NullBool":    "*bool",
	"sql.NullInt16":   "*int16",
	"sql.NullInt32":   "*int32",
	"sql.NullInt64":   "*int64",
	"sql.NullFloat32": "*float32",
	"sql.NullFloat64": "*float64",
}

func main() {
	args := os.Args[1:]
	if len(args) == 0 {
		log.Fatal("missing arg: filename")
	} else if len(args) != 1 {
		log.Fatalf("too many args (expected 1, got %v): %v", len(args), args)
	}

	filename := args[0]
	file, err := os.Open(filename)
	if err != nil {
		log.Fatalf("error opening file: %v", err)
	}
	defer file.Close()

	// read the file line by line
	scanner := bufio.NewScanner(file)
	var lines []string
	for scanner.Scan() {
		line := scanner.Text()
		for existingType, newType := range replacements {
			line = strings.ReplaceAll(line, "package database", "package db")
			line = strings.ReplaceAll(line, `import "database/sql"`, "")
			line = strings.ReplaceAll(line, existingType, newType)
		}
		lines = append(lines, line)
	}
	if err := scanner.Err(); err != nil {
		log.Fatalf("error reading file: %v", err)
	}

	// write back to the same file (overwrite)
	err = os.WriteFile(filename, []byte(strings.Join(lines, "\n")+"\n"), 0666)
	if err != nil {
		log.Fatalf("error writing file: %v", err)
	}

	fmt.Println("file successfully updated:", filename)

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant