Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
46 changes: 46 additions & 0 deletions internal/dbtest/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ func TestDB(t *testing.T) {
{testSelectMap},
{testSelectMapSlice},
{testSelectStruct},
{testSelectStructNilPtr},
{testSelectNestedStructValue},
{testSelectNestedStructPtr},
{testSelectStructSlice},
Expand Down Expand Up @@ -444,6 +445,51 @@ func testSelectStruct(t *testing.T, db *bun.DB) {
require.Contains(t, err.Error(), "Model does not have column")
}

func testSelectStructNilPtr(t *testing.T, db *bun.DB) {
type Model struct {
Num int
Str string
}

// Scan a row into a nil **Model: the inner pointer should be allocated and populated.
var model *Model
err := db.NewSelect().
ColumnExpr("10 AS num, 'hello' AS str").
Scan(ctx, &model)
require.NoError(t, err)
require.NotNil(t, model)
require.Equal(t, 10, model.Num)
require.Equal(t, "hello", model.Str)

// No rows + nil **Model: should NOT return ErrNoRows; inner pointer stays nil.
var empty *Model
err = db.NewSelect().
TableExpr("(SELECT 42 AS num) AS t").
Where("1 = 2").
Scan(ctx, &empty)
require.NoError(t, err)
require.Nil(t, empty)

// Regression: a non-nil *Model (existing behaviour) must still return ErrNoRows.
nonNil := new(Model)
err = db.NewSelect().
TableExpr("(SELECT 42 AS num) AS t").
Where("1 = 2").
Scan(ctx, nonNil)
require.Equal(t, sql.ErrNoRows, err)

// Scan a row into a non-nil **Model: the existing inner pointer is reused.
existing := &Model{}
preserved := existing
err = db.NewSelect().
ColumnExpr("7 AS num, 'world' AS str").
Scan(ctx, &existing)
require.NoError(t, err)
require.Same(t, preserved, existing)
require.Equal(t, 7, existing.Num)
require.Equal(t, "world", existing.Str)
}

func testSelectNestedStructValue(t *testing.T, db *bun.DB) {
type Model struct {
Num int
Expand Down
7 changes: 5 additions & 2 deletions model.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ func _newModel(db *DB, dest any, scan bool) (Model, error) {
return newMapModel(db, mapPtr), nil
case reflect.Struct:
return newStructTableModelValue(db, dest, v), nil
case reflect.Pointer:
if v.Type().Elem().Kind() == reflect.Struct {
return newStructTableModelValue(db, dest, v), nil
}
case reflect.Slice:
switch elemType := sliceElemType(v); elemType.Kind() {
case reflect.Struct:
Expand Down Expand Up @@ -200,8 +204,7 @@ func validMap(typ reflect.Type) error {

func isSingleRowModel(m Model) bool {
switch m.(type) {
case *mapModel,
*structTableModel,
case *structTableModel,
Comment thread
CyJaySong marked this conversation as resolved.
Outdated
*scanModel:
return true
default:
Expand Down
3 changes: 3 additions & 0 deletions query_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,9 @@ func (q *baseQuery) _scan(
}

if numRow == 0 && hasDest && isSingleRowModel(model) {
Comment thread
CyJaySong marked this conversation as resolved.
if nm, ok := model.(*structTableModel); ok && nm.isNil() {
Comment thread
CyJaySong marked this conversation as resolved.
Outdated
return driver.RowsAffected(numRow), nil
}
return nil, sql.ErrNoRows
}
return driver.RowsAffected(numRow), nil
Expand Down
Loading