Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
66 changes: 66 additions & 0 deletions gomod_test.go
Original file line number Diff line number Diff line change
@@ -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"))
}
}
50 changes: 15 additions & 35 deletions internal/x/expander/expander_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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.
Expand Down
Loading