Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support embedded struct fields #691

Merged
merged 1 commit into from Feb 22, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions associations/associations_for_struct.go
Expand Up @@ -72,6 +72,16 @@ func ForStruct(s interface{}, fields ...string) (Associations, error) {
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)

// inline embedded field
if f.Anonymous {
innerAssociations, err := ForStruct(v.Field(i).Interface(), fields...)
if err != nil {
return nil, err
}
associations = append(associations, innerAssociations...)
continue
}

// ignores those fields not included in fields list.
if len(fields) > 0 && fieldIgnoredIn(fields, f.Name) {
continue
Expand Down
50 changes: 32 additions & 18 deletions columns/columns_for_struct.go
Expand Up @@ -31,33 +31,47 @@ func ForStructWithAlias(s interface{}, tableName, tableAlias, idField string) (c
}
}

fieldCount := st.NumField()
// recursive functions to also find and add embedded struct fields
var findColumns func(st reflect.Type)
findColumns = func(t reflect.Type) {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}

for i := 0; i < fieldCount; i++ {
field := st.Field(i)
fc := t.NumField()
for i := 0; i < fc; i++ {
field := t.Field(i)

popTags := TagsFor(field)
tag := popTags.Find("db")
if field.Anonymous {
findColumns(field.Type)
continue
}

if !tag.Ignored() && !tag.Empty() {
col := tag.Value
popTags := TagsFor(field)
tag := popTags.Find("db")

// add writable or readable.
tag := popTags.Find("rw")
if !tag.Empty() {
col = col + "," + tag.Value
}
if !tag.Ignored() && !tag.Empty() {
col := tag.Value

cs := columns.Add(col)
// add writable or readable.
tag := popTags.Find("rw")
if !tag.Empty() {
col = col + "," + tag.Value
}

// add select clause.
tag = popTags.Find("select")
if !tag.Empty() {
c := cs[0]
c.SetSelectSQL(tag.Value)
cs := columns.Add(col)

// add select clause.
tag = popTags.Find("select")
if !tag.Empty() {
c := cs[0]
c.SetSelectSQL(tag.Value)
}
}
}
}

findColumns(st)

return columns
}
27 changes: 27 additions & 0 deletions executors_test.go
Expand Up @@ -533,6 +533,33 @@ func Test_Create_Non_PK_ID(t *testing.T) {
})
}

func Test_Embedded_Struct(t *testing.T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this test to go through the whole CRUD flow, should therefore cover most parts.

if PDB == nil {
t.Skip("skipping integration tests")
}
transaction(func(tx *Connection) {
r := require.New(t)

entry := &EmbeddingStruct{
InnerStruct: InnerStruct{},
AdditionalField: "I am also important!",
}
r.NoError(tx.Create(entry))

var actual EmbeddingStruct
r.NoError(tx.Find(&actual, entry.ID))
r.Equal(entry.AdditionalField, actual.AdditionalField)

entry.AdditionalField = entry.AdditionalField + " updated"
r.NoError(tx.Update(entry))

r.NoError(tx.Find(&actual, entry.ID))
r.Equal(entry.AdditionalField, actual.AdditionalField)

r.NoError(tx.Destroy(entry))
})
}

func Test_Eager_Create_Has_Many(t *testing.T) {
if PDB == nil {
t.Skip("skipping integration tests")
Expand Down
11 changes: 11 additions & 0 deletions pop_test.go
Expand Up @@ -476,3 +476,14 @@ type NonStandardID struct {
ID int `db:"pk"`
OutfacingID string `db:"id"`
}

type InnerStruct struct {
ID int `db:"id"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}

type EmbeddingStruct struct {
InnerStruct
AdditionalField string `db:"additional_field"`
}
2 changes: 1 addition & 1 deletion test.sh
Expand Up @@ -58,7 +58,7 @@ function test {
./tsoda create -e $SODA_DIALECT -c ./database.yml -p ./testdata/migrations
./tsoda migrate -e $SODA_DIALECT -c ./database.yml -p ./testdata/migrations
echo "Test..."
go test -race -tags sqlite $VERBOSE ./... -count=1
go test -race -tags sqlite $VERBOSE -count=1 ./...
}

function debug_test {
Expand Down
@@ -0,0 +1 @@
drop_table("embedding_structs")
7 changes: 7 additions & 0 deletions testdata/migrations/20220214113659_embedded_struct.up.fizz
@@ -0,0 +1,7 @@
create_table("embedding_structs") {
t.Column("id", "int", { "primary": true })

t.Column("additional_field", "string", {})

t.Timestamps()
}