jackc/pgx

Is somehow possible to import multiple versions?

Closed this issue · 4 comments

Describe the bug
If two pgx/stdlib versions are imported, it leads to panic.

To Reproduce
Steps to reproduce the behavior:

package main

import (
        "fmt"

        _ "github.com/jackc/pgx/v4/stdlib"
        _ "github.com/jackc/pgx/v5/stdlib"
)

func main() {
        fmt.Println("Hello, 世界")
}

Expected behavior
Could import both at the same time, when Go modules allow it thanks to version suffix /v2.

Actual behavior
Panics.
It doesn't allow A/B release behind feature flag.

panic: sql: Register called twice for driver pgx

goroutine 1 [running]:
database/sql.Register({0x56796b, 0x3}, {0x5af900, 0xc0000a2228})
        /usr/lib/go/src/database/sql/sql.go:51 +0x13d
github.com/jackc/pgx/v5/stdlib.init.0()
        /home/prochac/go/pkg/mod/github.com/jackc/pgx/v5@v5.2.0/stdlib/sql.go:88 +0x85
exit status 2

Version
go version go1.19.5 linux/amd64

We can't unregister driver, but we can check if already registered
https://pkg.go.dev/database/sql#Drivers

The drivers could be registered with the major vesion in mind
"pgx/v4" and "pgx/v5" instead of "pgx"
The first imported could register "pgx" too. <-- this will keep backward compatibility

In my example, it could be

import (
        _ "github.com/jackc/pgx/v4/stdlib"
        _ "github.com/jackc/pgx/v5/stdlib"
)

sql.Open("pgx", "...") // v4
sql.Open("pgx/v4", "...") // v4
sql.Open("pgx/v5", "...") // v5

alternative, that can collide with goimports tool or others, though

import (
        _ "github.com/jackc/pgx/v5/stdlib"
        _ "github.com/jackc/pgx/v4/stdlib"
)

sql.Open("pgx", "...") // v5
sql.Open("pgx/v4", "...") // v4
sql.Open("pgx/v5", "...") // v5

Other way to explicitly choose the "default" driver

var _ = func() (_ bool) {
        sql.Register("pgx", &stdlib.Driver{})
        return
}

func main() {
        fmt.Println("Hello, 世界")
}
jackc commented

The drivers could be registered with the major vesion in mind
"pgx/v4" and "pgx/v5" instead of "pgx"
The first imported could register "pgx" too. <-- this will keep backward compatibility

I like this idea.

I will check if my premise is valid in some free time. I'm a bit worried about how the init funcs imports are implemented, and if they concurrent safe*.
If it will be ok, I would prepare PR, it should be quite simple :)

*) the safety is a crucial condition for calling sql.Drivers(). I'm a bit nervous about the internal mutex
https://cs.opensource.google/go/go/+/refs/tags/go1.20:src/database/sql/sql.go;drc=b46e44a399045d0177dd063dc192168f0b5b3f55;l=34

jackc commented

I expect it should be fine. From the Go spec (https://go.dev/ref/spec#Program_initialization_and_execution):

Package initialization—variable initialization and the invocation of init functions—happens in a single goroutine, sequentially, one package at a time.