diff --git a/go.mod b/go.mod index 6b496e9ca0..717579dc97 100644 --- a/go.mod +++ b/go.mod @@ -59,5 +59,3 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect ) - -replace github.com/go-sql-driver/mysql => github.com/sqlc-dev/mysql v0.0.0-20251129233104-d81e1cac6db2 diff --git a/go.sum b/go.sum index 89cd769738..8b4b405ef7 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/cel-go v0.28.0 h1:KjSWstCpz/MN5t4a8gnGJNIYUsJRpdi/r97xWDphIQc= @@ -83,8 +85,6 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/sqlc-dev/doubleclick v1.0.0 h1:2/OApfQ2eLgcfa/Fqs8WSMA6atH0G8j9hHbQIgMfAXI= github.com/sqlc-dev/doubleclick v1.0.0/go.mod h1:ODHRroSrk/rr5neRHlWMSRijqOak8YmNaO3VAZCNl5Y= -github.com/sqlc-dev/mysql v0.0.0-20251129233104-d81e1cac6db2 h1:kmCAKKtOgK6EXXQX9oPdEASIhgor7TCpWxD8NtcqVcU= -github.com/sqlc-dev/mysql v0.0.0-20251129233104-d81e1cac6db2/go.mod h1:TrDMWzjNTKvJeK2GC8uspG+PWyPLiY9QKvwdWpAdlZE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/gomod_test.go b/gomod_test.go new file mode 100644 index 0000000000..fd00b12e6f --- /dev/null +++ b/gomod_test.go @@ -0,0 +1,66 @@ +package sqlc + +import ( + "fmt" + "os" + "strings" + "testing" +) + +// TestGoModHasNoReplaceDirectives guards against regressions of +// https://github.com/sqlc-dev/sqlc/issues/4397. +// +// When go.mod contains a replace directive, the Go toolchain refuses to run +// `go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest` (and the equivalent +// `go run ...@latest`): +// +// go: github.com/sqlc-dev/sqlc/cmd/sqlc@latest (in github.com/sqlc-dev/sqlc@v...): +// The go.mod file for the module providing named packages contains one or +// more replace directives. It must not contain directives that would cause +// it to be interpreted differently than if it were the main module. +// +// https://docs.sqlc.dev/en/latest/overview/install.html tells users to run +// exactly that command, so any replace directive slipping into go.mod breaks +// the advertised installation path for the next release. +func TestGoModHasNoReplaceDirectives(t *testing.T) { + data, err := os.ReadFile("go.mod") + if err != nil { + t.Fatalf("read go.mod: %v", err) + } + + var ( + inBlock bool + offenders []string + ) + for i, raw := range strings.Split(string(data), "\n") { + line := strings.TrimSpace(raw) + if idx := strings.Index(line, "//"); idx >= 0 { + line = strings.TrimSpace(line[:idx]) + } + + if inBlock { + if line == ")" { + inBlock = false + continue + } + if line != "" { + offenders = append(offenders, fmt.Sprintf(" go.mod:%d: %s", i+1, raw)) + } + continue + } + + switch { + case line == "replace (": + inBlock = true + case strings.HasPrefix(line, "replace "): + offenders = append(offenders, fmt.Sprintf(" go.mod:%d: %s", i+1, raw)) + } + } + + if len(offenders) > 0 { + t.Fatalf("go.mod must not contain replace directives; "+ + "they break `go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest`.\n"+ + "See https://github.com/sqlc-dev/sqlc/issues/4397\n%s", + strings.Join(offenders, "\n")) + } +} diff --git a/internal/x/expander/expander_test.go b/internal/x/expander/expander_test.go index 98cf22981b..ee3b6cef4c 100644 --- a/internal/x/expander/expander_test.go +++ b/internal/x/expander/expander_test.go @@ -3,12 +3,10 @@ package expander import ( "context" "database/sql" - "database/sql/driver" - "fmt" "os" "testing" - "github.com/go-sql-driver/mysql" + _ "github.com/go-sql-driver/mysql" "github.com/jackc/pgx/v5/pgxpool" "github.com/ncruces/go-sqlite3" @@ -44,46 +42,28 @@ func (g *PostgreSQLColumnGetter) GetColumnNames(ctx context.Context, query strin return columns, nil } -// MySQLColumnGetter implements ColumnGetter for MySQL using the forked driver's StmtMetadata. +// MySQLColumnGetter implements ColumnGetter for MySQL. Column names are read +// from the result set metadata returned by executing the query; the test +// tables are empty, so no real rows are transferred. +// +// An earlier implementation pulled column metadata straight out of a prepared +// statement via a forked mysql driver exposing StmtMetadata. That fork +// required a `replace` directive in go.mod, which broke `go install +// github.com/sqlc-dev/sqlc/cmd/sqlc@latest` (see +// https://github.com/sqlc-dev/sqlc/issues/4397). Reading columns from sql.Rows +// works with the upstream driver and keeps the test covering the same +// behavior. type MySQLColumnGetter struct { db *sql.DB } func (g *MySQLColumnGetter) GetColumnNames(ctx context.Context, query string) ([]string, error) { - conn, err := g.db.Conn(ctx) + rows, err := g.db.QueryContext(ctx, query) if err != nil { return nil, err } - defer conn.Close() - - var columns []string - err = conn.Raw(func(driverConn any) error { - preparer, ok := driverConn.(driver.ConnPrepareContext) - if !ok { - return fmt.Errorf("driver connection does not support PrepareContext") - } - - stmt, err := preparer.PrepareContext(ctx, query) - if err != nil { - return err - } - defer stmt.Close() - - meta, ok := stmt.(mysql.StmtMetadata) - if !ok { - return fmt.Errorf("prepared statement does not implement StmtMetadata") - } - - for _, col := range meta.ColumnMetadata() { - columns = append(columns, col.Name) - } - return nil - }) - if err != nil { - return nil, err - } - - return columns, nil + defer rows.Close() + return rows.Columns() } // SQLiteColumnGetter implements ColumnGetter for SQLite using the native ncruces/go-sqlite3 API.