diff --git a/associations/associations_for_struct.go b/associations/associations_for_struct.go index 544e8786..bdd7e180 100644 --- a/associations/associations_for_struct.go +++ b/associations/associations_for_struct.go @@ -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 diff --git a/columns/columns_for_struct.go b/columns/columns_for_struct.go index a20cd426..5e09f69f 100644 --- a/columns/columns_for_struct.go +++ b/columns/columns_for_struct.go @@ -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 } diff --git a/executors_test.go b/executors_test.go index 3202bfad..d428e364 100644 --- a/executors_test.go +++ b/executors_test.go @@ -533,6 +533,33 @@ func Test_Create_Non_PK_ID(t *testing.T) { }) } +func Test_Embedded_Struct(t *testing.T) { + 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") diff --git a/pop_test.go b/pop_test.go index 0166e9d0..629ea35a 100644 --- a/pop_test.go +++ b/pop_test.go @@ -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"` +} diff --git a/test.sh b/test.sh index 4ea9a5f0..d99c6744 100755 --- a/test.sh +++ b/test.sh @@ -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 { diff --git a/testdata/migrations/20220214113659_embedded_struct.down.fizz b/testdata/migrations/20220214113659_embedded_struct.down.fizz new file mode 100644 index 00000000..ec1651dd --- /dev/null +++ b/testdata/migrations/20220214113659_embedded_struct.down.fizz @@ -0,0 +1 @@ +drop_table("embedding_structs") diff --git a/testdata/migrations/20220214113659_embedded_struct.up.fizz b/testdata/migrations/20220214113659_embedded_struct.up.fizz new file mode 100644 index 00000000..90473246 --- /dev/null +++ b/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() +} \ No newline at end of file