Cross Check/ Advise on a simple implementation that uses an Enum type #1559
-
Hi guys, I was wondering what is the best way to implement INSERT and SELECT using an CREATE TYPE order_status AS ENUM
('UNKNOWN', 'CREATED', 'PROCESSING', 'COMPLETED');
CREATE TABLE IF NOT EXISTS orders
(
order_id integer NOT NULL,
order_status order_status NOT NULL DEFAULT 'UNKNOWN'::order_status,
CONSTRAINT test_pkey PRIMARY KEY (id)
) I've created this but I just wanted if I'm using correctly the package main
import (
"context"
"database/sql/driver"
"fmt"
"log"
"pgclient"
)
type Order struct {
Id int64 `sql:"order_id"`
Status Status `sql:"order_status"`
}
type Status int64
const (
StatusUnknown Status = iota
StatusCreated
StatusProcessing
StatusCompleted
)
// deserialize from DB
func (s *Status) Scan(value interface{}) error {
*s = toStatus(value.(string))
return nil
}
// serialize to DB
func (s Status) Value() (driver.Value, error) {
return s.String(), nil
}
func toStatus(s string) Status {
switch s {
case "UNKNOWN":
return StatusUnknown
case "CREATED":
return StatusCreated
case "PROCESSING":
return StatusProcessing
case "COMPLETED":
return StatusCompleted
}
return StatusUnknown
}
func (s Status) String() string {
switch s {
case StatusCreated:
return "CREATED"
case StatusProcessing:
return "PROCESSING"
case StatusCompleted:
return "COMPLETED"
}
return "unknown"
}
func main() {
// insert test
orderToDB := &Order{
Id: 1,
Status: StatusCreated,
}
_, err := pgclient.GetSession().Exec(context.Background(), "INSERT INTO test (order_id, order_status) VALUES($1,$2)", orderToDB.Id, orderToDB.Status)
if err != nil {
log.Fatalf("insert error: %v\n", err)
}
// select test
var orderFromDB Order
err = pgclient.GetSession().QueryRow(context.Background(), "SELECT order_id, order_status FROM test WHERE order_id = 1").Scan(&orderFromDB.Id, &orderFromDB.Status)
if err != nil {
log.Fatalf("select error: %v\n", err)
}
fmt.Printf("%+v", orderFromDB) // {Id:1 Status:CREATED}
} package pgclient
import (
"context"
"fmt"
"github.com/BigBoulard/.../conf"
"github.com/BigBoulard/.../log"
"github.com/jackc/pgx/v5/pgxpool"
)
var dbpool *pgxpool.Pool
func GetSession() *pgxpool.Pool {
return dbpool
}
func init() {
conf.LoadEnv()
connStr := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s",
conf.DB.User,
conf.DB.Pass,
conf.DB.Host,
conf.DB.DBName,
conf.DB.SSLMode,
)
var err error
dbpool, err = pgxpool.New(context.Background(), connStr)
if err != nil {
log.Fatal(err, "pgclient", "init",
fmt.Sprintf("Can't connect to host %s db %s", conf.DB.Host, conf.DB.DBName),
)
panic(err)
}
fmt.Println("Connected to database.")
} Thank you so much |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 5 replies
-
Unless you really need to map the enum string to an integer I would do something like this: config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
dt, err := conn.LoadType(ctx, "order_status")
if err != nil {
return err
}
conn.TypeMap().RegisterType(dt)
return nil
} This will maintain an internal registry of enum values. There will only be one copy of any string (per connection). You can use normal strings or you can still make your own type with a string backing. |
Beta Was this translation helpful? Give feedback.
-
@BigBoulard Using a custom string type instead of an enum doesn't make sense in my opinion. I want to use real enums to enable flexibility to modify, in this case, the order status workflow. Using real enums provides a logical ordering for each stage. Otherwise, we are just using strings again, like we have been doing since php with very confusing status names in old codebases. I have been using enums with underlying uint type by assigning the default starting stage to type Status uint
const (
StatusCreated Status = 2
StatusProcessing = 512
StatusComplete = 1024
StatusUnknown = 4096
) Now, if in the future a stage such as a status of unpaid orders is necessary, we can just add The issue with mapping this to postgres still remains. I solved this by just using INTEGER as type in PG, since limiting the available options in postgres is not necessary, in my opinion. I just make sure to correctly write my insert/update queries. And in case an invalid status is saved to the db, reading it into a struct (i am using github.com/georgysavva/scany/v2/pgxscan for that) will produce an error. And this should only occur in testing anyway and if not, it would be easy to fix with a db edit and a bugfix. |
Beta Was this translation helpful? Give feedback.
Unless you really need to map the enum string to an integer I would do something like this:
This will maintain an internal registry of enum values. There will only be one copy of any string (per connection).
You can use normal strings or you can still make your own type with a string backing.