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

v6.0.2 #704

Merged
merged 48 commits into from
Apr 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
f36afb5
fix: improve model ID field customization (#604)
zepatrik Oct 29, 2020
4d0d826
Ensure uninitialized map is initialized when unmarshaling json
naemono Dec 15, 2020
81e5f2d
exclude migration_table_name from connection string
kyrozetera Dec 23, 2020
0e3d2e2
add test for OptionsString
kyrozetera Dec 23, 2020
b2918a3
Add support for pointer FKs when preloading a belongs_to association …
reggieriser Jan 5, 2021
0fb7635
feat: support context-aware tablenames (#614)
aeneasr Jan 18, 2021
119c0d2
Bump pg deps (#616)
paganotoni Jan 27, 2021
f252caa
Latest from master (#620)
paganotoni Jan 28, 2021
ad18e4d
Resolve issues in UPDATE and DELETE when using schemas (#618)
aeneasr Feb 6, 2021
e314840
Use `PaginatorPageKey` and `PaginatorPerPageKey` variables (#615)
bhb603 Feb 6, 2021
46cfd45
Pass Time structure into timestamp update functions. (#625)
mpontillo Mar 8, 2021
fbf43b4
Allow nullable JSONB and resolve MySQL regression (#639)
Apr 13, 2021
1b7b5ef
Allow passing args to `Order` (#630)
duckbrain Apr 13, 2021
83cf49c
Added connection maximum idle time configuration (#635)
ArthurKnoep Apr 14, 2021
51f7117
Bump sqlite to 3.35.4 / 1.14.7 (#642)
aeneasr Apr 27, 2021
cf0a600
Update pg, pgx, sqlx (#643)
paganotoni May 5, 2021
e947d1b
merging master
paganotoni May 5, 2021
96f8e48
Fix Inner has many associations when passing on multiple arguments (#…
larrymjordan May 5, 2021
aeb9b56
Remove many to many TX condition for EagerPreload (#645)
larrymjordan May 6, 2021
bef765a
Added fix/tests for has_many with pointer foreign key (#647)
reggieriser May 6, 2021
bb07a37
Export WhereID, Alias, WhereNamedID (#637)
aeneasr Jun 8, 2021
d279000
fix: log model values everywhere (#656)
zepatrik Jun 28, 2021
0ecad25
Add delete to query builder (#658)
zepatrik Jul 16, 2021
bb7527e
Sort down migrations (#657)
zepatrik Aug 10, 2021
e383983
Preserve eager information when validating models (#664) (#665)
karlhaas Oct 3, 2021
45bb170
Migrate from packr to fs (#667)
fasmat Nov 23, 2021
d2bb309
merging master
paganotoni Nov 23, 2021
86d67a1
Task merging master (#669)
paganotoni Nov 23, 2021
a61a3fd
Merge branch 'development' of https://github.com/gobuffalo/pop into d…
paganotoni Nov 23, 2021
b72810f
Replace removed command
aeneasr Dec 3, 2021
94d331f
Resolve `EagerPreload` panic caused for pointer references
aeneasr Dec 8, 2021
d4a4f55
Resolve `EagerPreload` panic caused by NullUUID
aeneasr Dec 8, 2021
321cc6a
Support pointers in n+1 `Eager` loading
aeneasr Dec 9, 2021
cd4d78c
Improve error message of associations.ForStruct
aeneasr Dec 9, 2021
8817167
Add test cases for `IsZeroOfUnderlyingType`
aeneasr Dec 9, 2021
e867f2c
Resolve an obscure bug where empty structs got loaded for NULL foreig…
aeneasr Dec 9, 2021
7c217b9
Resolve association regression in finders.go
aeneasr Dec 10, 2021
3ba6312
Use dedicated migrations for preloading regression test
aeneasr Dec 10, 2021
134d8a6
Fix code regression
aeneasr Dec 10, 2021
08f34ff
Fix test code regressions
aeneasr Dec 10, 2021
bf0ffb2
Fix sql migration order
aeneasr Dec 10, 2021
20bb080
Resolve order issue in test
aeneasr Dec 10, 2021
d148b95
Ignore order when testing for nil values in test
aeneasr Dec 10, 2021
f7abee9
Pass Context during exec in create. (#688)
martinei Feb 14, 2022
32f6994
feat: support embedded struct fields (#691)
zepatrik Feb 22, 2022
2ff8b3f
test: use `T.TempDir` to create temporary test directory
Juneezee Apr 10, 2022
0656433
task: merging master into development
paganotoni Apr 16, 2022
d6d7437
task: adding next version number
paganotoni Apr 16, 2022
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
4 changes: 4 additions & 0 deletions associations/association.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,5 +160,9 @@ func fieldIsNil(f reflect.Value) bool {

// IsZeroOfUnderlyingType will check if the value of anything is the equal to the Zero value of that type.
func IsZeroOfUnderlyingType(x interface{}) bool {
if x == nil {
return true
}

return reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface())
}
37 changes: 37 additions & 0 deletions associations/association_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package associations

import (
"database/sql"
"fmt"
"github.com/gobuffalo/nulls"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
"testing"
)

func Test_IsZeroOfUnderlyingType(t *testing.T) {
for k, tc := range []struct {
in interface{}
zero bool
}{
{in: nil, zero: true},
{in: 0, zero: true},
{in: 1, zero: false},
{in: false, zero: true},
{in: "", zero: true},
{in: interface{}(nil), zero: true},
{in: uuid.NullUUID{}, zero: true},
{in: uuid.UUID{}, zero: true},
{in: uuid.NullUUID{Valid: true}, zero: false},
{in: nulls.Int{}, zero: true},
{in: nulls.String{}, zero: true},
{in: nulls.Bool{}, zero: true},
{in: nulls.Float64{}, zero: true},
{in: sql.NullString{}, zero: true},
{in: sql.NullString{Valid: true}, zero: false},
} {
t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) {
assert.EqualValues(t, tc.zero, IsZeroOfUnderlyingType(tc.in))
})
}
}
13 changes: 11 additions & 2 deletions associations/associations_for_struct.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package associations

import (
"errors"
"fmt"
"reflect"
"regexp"
Expand Down Expand Up @@ -30,7 +29,7 @@ var associationBuilders = map[string]associationBuilder{}
func ForStruct(s interface{}, fields ...string) (Associations, error) {
t, v := getModelDefinition(s)
if t.Kind() != reflect.Struct {
return nil, errors.New("could not get struct associations: not a struct")
return nil, fmt.Errorf("could not get struct associations: not a struct but %T", s)
}
fields = trimFields(fields)
associations := Associations{}
Expand Down Expand Up @@ -73,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
21 changes: 14 additions & 7 deletions associations/belongs_to_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,22 @@ func (b *belongsToAssociation) BeforeInterface() interface{} {

func (b *belongsToAssociation) BeforeSetup() error {
ownerID := reflect.Indirect(reflect.ValueOf(b.ownerModel.Interface())).FieldByName("ID")
if b.ownerID.CanSet() {
if n := nulls.New(b.ownerID.Interface()); n != nil {
b.ownerID.Set(reflect.ValueOf(n.Parse(ownerID.Interface())))
} else if b.ownerID.Kind() == reflect.Ptr {
b.ownerID.Set(ownerID.Addr())
toSet := b.ownerID
switch b.ownerID.Type().Name() {
case "NullUUID":
b.ownerID.FieldByName("Valid").Set(reflect.ValueOf(true))
toSet = b.ownerID.FieldByName("UUID")
}

if toSet.CanSet() {
if n := nulls.New(toSet.Interface()); n != nil {
toSet.Set(reflect.ValueOf(n.Parse(ownerID.Interface())))
} else if toSet.Kind() == reflect.Ptr {
toSet.Set(ownerID.Addr())
} else {
b.ownerID.Set(ownerID)
toSet.Set(ownerID)
}
return nil
}
return fmt.Errorf("could not set '%s' to '%s'", ownerID, b.ownerID)
return fmt.Errorf("could not set '%s' to '%s'", ownerID, toSet)
}
19 changes: 19 additions & 0 deletions associations/belongs_to_association_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ type barBelongsTo struct {
Foo fooBelongsTo `belongs_to:"foo"`
}

type barBelongsToNullable struct {
FooID uuid.NullUUID `db:"foo_id"`
Foo *fooBelongsTo `belongs_to:"foo"`
}

func Test_Belongs_To_Association(t *testing.T) {
a := require.New(t)

Expand Down Expand Up @@ -50,3 +55,17 @@ func Test_Belongs_To_Association(t *testing.T) {
a.Equal(nil, before[index].BeforeInterface())
}
}

func Test_Belongs_To_Nullable_Association(t *testing.T) {
a := require.New(t)
id, _ := uuid.NewV1()

bar := barBelongsToNullable{Foo: &fooBelongsTo{id}}
as, err := associations.ForStruct(&bar, "Foo")
a.NoError(err)

before := as.AssociationsBeforeCreatable()
for index := range before {
a.Equal(nil, before[index].BeforeSetup())
}
}
50 changes: 32 additions & 18 deletions columns/columns_for_struct.go
Original file line number Diff line number Diff line change
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
}
35 changes: 33 additions & 2 deletions dialect_cockroach.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package pop

import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"sync"

Expand Down Expand Up @@ -200,13 +202,42 @@ func (p *cockroach) FizzTranslator() fizz.Translator {
}

func (p *cockroach) DumpSchema(w io.Writer) error {
cmd := exec.Command("cockroach", "dump", p.Details().Database, "--dump-mode=schema")
cmd := exec.Command("cockroach", "sql", "-e", "SHOW CREATE ALL TABLES", "-d", p.Details().Database, "--format", "raw")

c := p.ConnectionDetails
if defaults.String(c.Options["sslmode"], "disable") == "disable" || strings.Contains(c.RawOptions, "sslmode=disable") {
cmd.Args = append(cmd.Args, "--insecure")
}
return genericDumpSchema(p.Details(), cmd, w)
return cockroachDumpSchema(p.Details(), cmd, w)
}

func cockroachDumpSchema(deets *ConnectionDetails, cmd *exec.Cmd, w io.Writer) error {
log(logging.SQL, strings.Join(cmd.Args, " "))

var bb bytes.Buffer

cmd.Stdout = &bb
cmd.Stderr = os.Stderr

err := cmd.Run()
if err != nil {
return err
}

// --format raw returns comments prefixed with # which is invalid, so we make it a valid SQL comment.
result := regexp.MustCompile("(?m)^#").ReplaceAll(bb.Bytes(), []byte("-- #"))

if _, err := w.Write(result); err != nil {
return err
}

x := bytes.TrimSpace(result)
if len(x) == 0 {
return fmt.Errorf("unable to dump schema for %s", deets.Database)
}

log(logging.Info, "dumped schema for %s", deets.Database)
return nil
}

func (p *cockroach) LoadSchema(r io.Reader) error {
Expand Down
2 changes: 1 addition & 1 deletion dialect_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func genericCreate(s store, model *Model, cols columns.Columns, quoter quotable)
if err != nil {
return err
}
_, err = stmt.Exec(model.Value)
_, err = stmt.ExecContext(model.ctx, model.Value)
if err != nil {
if closeErr := stmt.Close(); closeErr != nil {
return fmt.Errorf("failed to close prepared statement: %s: %w", closeErr, err)
Expand Down
3 changes: 2 additions & 1 deletion dialect_sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,11 @@ func (m *sqlite) CreateDB() error {
if err != nil {
return fmt.Errorf("could not create SQLite database '%s': %w", m.ConnectionDetails.Database, err)
}
_, err = os.Create(m.ConnectionDetails.Database)
f, err := os.Create(m.ConnectionDetails.Database)
if err != nil {
return fmt.Errorf("could not create SQLite database '%s': %w", m.ConnectionDetails.Database, err)
}
_ = f.Close()

log(logging.Info, "created database '%s'", m.ConnectionDetails.Database)
return nil
Expand Down
10 changes: 2 additions & 8 deletions dialect_sqlite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ package pop

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"

Expand Down Expand Up @@ -133,10 +131,8 @@ func Test_ConnectionDetails_Finalize_SQLite_OverrideOptions_Synonym_Path(t *test

func Test_ConnectionDetails_FinalizeOSPath(t *testing.T) {
r := require.New(t)
d, err := ioutil.TempDir("", "")
r.NoError(err)
d := t.TempDir()
p := filepath.Join(d, "testdb.sqlite")
defer os.RemoveAll(p)
cd := &ConnectionDetails{
Dialect: "sqlite",
Database: p,
Expand All @@ -148,10 +144,8 @@ func Test_ConnectionDetails_FinalizeOSPath(t *testing.T) {

func TestSqlite_CreateDB(t *testing.T) {
r := require.New(t)
dir, err := ioutil.TempDir("", "")
r.NoError(err)
dir := t.TempDir()
p := filepath.Join(dir, "testdb.sqlite")
defer os.RemoveAll(p)
cd := &ConnectionDetails{
Dialect: "sqlite",
Database: p,
Expand Down
27 changes: 27 additions & 0 deletions executors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
9 changes: 8 additions & 1 deletion finders.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,14 @@ func (q *Query) eagerDefaultAssociations(model interface{}) error {
v = reflect.Indirect(reflect.ValueOf(model)).FieldByName(inner.Name)
innerQuery := Q(query.Connection)
innerQuery.eagerFields = inner.Fields
err = innerQuery.eagerAssociations(v.Addr().Interface())

switch v.Kind() {
case reflect.Ptr:
err = innerQuery.eagerAssociations(v.Interface())
default:
err = innerQuery.eagerAssociations(v.Addr().Interface())
}

if err != nil {
return err
}
Expand Down
Loading