diff --git a/internal/sqlbuilder/select.go b/internal/sqlbuilder/select.go index 0e59ac78..b98f7df7 100644 --- a/internal/sqlbuilder/select.go +++ b/internal/sqlbuilder/select.go @@ -424,9 +424,12 @@ func (sel *selector) As(alias string) db.Selector { }) } -func (sel *selector) statement() *exql.Statement { - sq, _ := sel.build() - return sq.statement() +func (sel *selector) statement() (*exql.Statement, error) { + sq, err := sel.build() + if err != nil { + return nil, err + } + return sq.statement(), nil } func (sel *selector) QueryRow() (*sql.Row, error) { @@ -502,7 +505,11 @@ func (sel *selector) build() (*selectorQuery, error) { } func (sel *selector) Compile() (string, error) { - return sel.statement().Compile(sel.template()) + s, err := sel.statement() + if err != nil { + return "", err + } + return s.Compile(sel.template()) } func (sel *selector) Prev() immutable.Immutable { diff --git a/internal/sqlbuilder/select_nil_test.go b/internal/sqlbuilder/select_nil_test.go new file mode 100644 index 00000000..d5df2249 --- /dev/null +++ b/internal/sqlbuilder/select_nil_test.go @@ -0,0 +1,43 @@ +package sqlbuilder + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAsWithoutFromDoesNotNilPanic(t *testing.T) { + b := &sqlBuilder{t: newTemplateWithUtils(&testTemplate)} + + // Valid path still works. + assert.Contains(t, b.SelectFrom("artist").As("a").String(), "artist") + + // As without From used to nil-deref inside statement(); now surface a real error. + defer func() { + r := recover() + if r == nil { + t.Fatal("expected panic with error message, got none") + } + msg, ok := r.(string) + if !ok { + t.Fatalf("expected string panic, got %T %v", r, r) + } + if strings.Contains(msg, "nil pointer") || strings.Contains(msg, "invalid memory") { + t.Fatalf("still nil-pointer style panic: %s", msg) + } + if !strings.Contains(msg, "As()") { + t.Fatalf("unexpected error message: %s", msg) + } + }() + _ = b.Select("id").As("alias").String() +} + +func TestSelectorStatementReturnsBuildError(t *testing.T) { + b := &sqlBuilder{t: newTemplateWithUtils(&testTemplate)} + sel := b.Select("id").As("alias").(*selector) + st, err := sel.statement() + assert.Nil(t, st) + assert.Error(t, err) + assert.Contains(t, err.Error(), "As()") +}