Skip to content

Commit 33f2d5c

Browse files
paganotonizepatriknaemonokyrozeterareggieriser
authored
v5.3.4 (#644)
* fix: improve model ID field customization (#604) Updates places where `"id"` was hardcoded instead of using `model.IDField()`. * Ensure uninitialized map is initialized when unmarshaling json Add tests for this scenario * exclude migration_table_name from connection string * add test for OptionsString * Add support for pointer FKs when preloading a belongs_to association (#602) * feat: support context-aware tablenames (#614) This patch adds a feature which enables pop to pass down the connection context to the model's TableName() function by implementing TableName(ctx context.Context) string. The context can be used to dynamically generate tablenames which can be important for prefixed or generic tables and other use cases. * Bump pg deps (#616) * Reset to development * bumping pgx and pgconn versions Co-authored-by: Stanislas Michalak <[email protected]> Co-authored-by: Larry M Jordan <[email protected]> * Latest from master (#620) * Latest from development (#617) * fix: improve model ID field customization (#604) Updates places where `"id"` was hardcoded instead of using `model.IDField()`. * Ensure uninitialized map is initialized when unmarshaling json Add tests for this scenario * exclude migration_table_name from connection string * add test for OptionsString * Add support for pointer FKs when preloading a belongs_to association (#602) * feat: support context-aware tablenames (#614) This patch adds a feature which enables pop to pass down the connection context to the model's TableName() function by implementing TableName(ctx context.Context) string. The context can be used to dynamically generate tablenames which can be important for prefixed or generic tables and other use cases. * Bump pg deps (#616) * Reset to development * bumping pgx and pgconn versions Co-authored-by: Stanislas Michalak <[email protected]> Co-authored-by: Larry M Jordan <[email protected]> Co-authored-by: Patrik <[email protected]> Co-authored-by: Michael Montgomery <[email protected]> Co-authored-by: kyrozetera <[email protected]> Co-authored-by: Reggie Riser <[email protected]> Co-authored-by: hackerman <[email protected]> Co-authored-by: Stanislas Michalak <[email protected]> Co-authored-by: Larry M Jordan <[email protected]> * adding goreleaser syntaz (#619) Co-authored-by: Patrik <[email protected]> Co-authored-by: Michael Montgomery <[email protected]> Co-authored-by: kyrozetera <[email protected]> Co-authored-by: Reggie Riser <[email protected]> Co-authored-by: hackerman <[email protected]> Co-authored-by: Stanislas Michalak <[email protected]> Co-authored-by: Larry M Jordan <[email protected]> * Resolve issues in UPDATE and DELETE when using schemas (#618) * Resolve MySQL issues and improve test migrations * Bump CockroachDB to maintained and supported versions Version 2.1 has reached EoL in 2019 Signed-off-by: aeneasr <[email protected]> * Use `PaginatorPageKey` and `PaginatorPerPageKey` variables (#615) * update pagination_test * Pass Time structure into timestamp update functions. (#625) Closes #624 * Allow nullable JSONB and resolve MySQL regression (#639) * Allow passing args to `Order` (#630) * Added connection maximum idle time configuration (#635) This PR add the possibility to configure the connection maximum idle time (https://golang.org/pkg/database/sql/#DB.SetConnMaxIdleTime). Closes #632 BREAKING CHANGE: Requires Go 1.15 from now on. * Bump sqlite to 3.35.4 / 1.14.7 (#642) * Update pg, pgx, sqlx (#643) - `jackc/pgx` to version `v4.11.0`. - `jmoiron/sqlx` to version`v1.3.3` - `lib/pq` to version`v1.10.1` * Fix Inner has many associations when passing on multiple arguments (#633) * Fix Inner has many associations when passing on multiple arguments for inner fields * Fix broken tests * clean up extractFieldAndInnerFields function Co-authored-by: hackerman <[email protected]> Co-authored-by: Antonio Pagano <[email protected]> * Remove many to many TX condition for EagerPreload (#645) * Remove the need to use Tx when loading many to many associations * replace TX access to create a new tx.Store.Transaction() object * Added fix/tests for has_many with pointer foreign key (#647) Co-authored-by: Antonio Pagano <[email protected]> Co-authored-by: Patrik <[email protected]> Co-authored-by: Michael Montgomery <[email protected]> Co-authored-by: kyrozetera <[email protected]> Co-authored-by: Reggie Riser <[email protected]> Co-authored-by: hackerman <[email protected]> Co-authored-by: Stanislas Michalak <[email protected]> Co-authored-by: Larry M Jordan <[email protected]> Co-authored-by: Brian Buchholz <[email protected]> Co-authored-by: Mike Pontillo <[email protected]> Co-authored-by: Benjamin Blattberg <[email protected]> Co-authored-by: Jonathan Duck <[email protected]> Co-authored-by: Arthur Knoepflin <[email protected]>
1 parent 5a8f51c commit 33f2d5c

32 files changed

+592
-175
lines changed

.github/workflows/release.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
name: Release
1111
runs-on: ubuntu-latest
1212
container:
13-
image: bepsays/ci-goreleaser:1.13-4
13+
image: bepsays/ci-goreleaser:1.15.1
1414
steps:
1515
- name: Checkout Code
1616
uses: actions/checkout@master
@@ -24,4 +24,4 @@ jobs:
2424
GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }}
2525
with:
2626
version: latest
27-
args: release --rm-dist
27+
args: release --rm-dist

.github/workflows/tests.yml

+11-11
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- name: Set up Go
2020
uses: actions/setup-go@v1
2121
with:
22-
go-version: 1.13
22+
go-version: 1.15
2323
id: go
2424
- name: Checkout Code
2525
uses: actions/checkout@v1
@@ -64,7 +64,7 @@ jobs:
6464
- name: Set up Go
6565
uses: actions/setup-go@v1
6666
with:
67-
go-version: 1.13
67+
go-version: 1.15
6868
id: go
6969
- name: Checkout Code
7070
uses: actions/checkout@v1
@@ -97,7 +97,7 @@ jobs:
9797
- name: Set up Go
9898
uses: actions/setup-go@v1
9999
with:
100-
go-version: 1.13
100+
go-version: 1.15
101101
id: go
102102
- name: Checkout Code
103103
uses: actions/checkout@v1
@@ -110,12 +110,12 @@ jobs:
110110
run: |
111111
mkdir -p crdb/certs
112112
pushd crdb
113-
wget -qO- https://binaries.cockroachdb.com/cockroach-v2.1.0.linux-amd64.tgz | tar zxv
114-
mv cockroach-v2.1.0.linux-amd64/cockroach .
113+
wget -qO- https://binaries.cockroachdb.com/cockroach-v20.2.4.linux-amd64.tgz | tar zxv
114+
mv cockroach-v20.2.4.linux-amd64/cockroach .
115115
./cockroach cert create-ca --certs-dir certs --ca-key key
116116
./cockroach cert create-client root --certs-dir certs --ca-key key
117117
./cockroach cert create-node localhost 127.0.0.1 `hostname -s` `hostname -f` --certs-dir certs --ca-key key
118-
./cockroach start --certs-dir certs --listen-addr localhost --port 26259 --http-port 8089 --background
118+
./cockroach start-single-node --certs-dir certs --listen-addr localhost --port 26259 --http-port 8089 --background
119119
popd
120120
- name: Build and run soda
121121
env:
@@ -139,7 +139,7 @@ jobs:
139139
- name: Set up Go
140140
uses: actions/setup-go@v1
141141
with:
142-
go-version: 1.13
142+
go-version: 1.15
143143
id: go
144144
- name: Checkout Code
145145
uses: actions/checkout@v1
@@ -152,9 +152,9 @@ jobs:
152152
run: |
153153
mkdir -p crdb
154154
pushd crdb
155-
wget -qO- https://binaries.cockroachdb.com/cockroach-v2.1.0.linux-amd64.tgz | tar zxv
156-
mv cockroach-v2.1.0.linux-amd64/cockroach .
157-
./cockroach start --insecure --background
155+
wget -qO- https://binaries.cockroachdb.com/cockroach-v20.2.4.linux-amd64.tgz | tar zxv
156+
mv cockroach-v20.2.4.linux-amd64/cockroach .
157+
./cockroach start-single-node --insecure --background
158158
popd
159159
- name: Build and run soda
160160
env:
@@ -181,7 +181,7 @@ jobs:
181181
- name: Set up Go
182182
uses: actions/setup-go@v1
183183
with:
184-
go-version: 1.13
184+
go-version: 1.15
185185
id: go
186186
- name: Checkout Code
187187
uses: actions/checkout@v1

associations/association.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func (a *associationComposite) InnerAssociations() InnerAssociations {
4343
// association for Song.
4444
type InnerAssociation struct {
4545
Name string
46-
Fields string
46+
Fields []string
4747
}
4848

4949
// InnerAssociations is a group of InnerAssociation.

associations/associations_for_struct.go

+29-10
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,39 @@ func ForStruct(s interface{}, fields ...string) (Associations, error) {
3434
}
3535
fields = trimFields(fields)
3636
associations := Associations{}
37-
innerAssociations := InnerAssociations{}
37+
fieldsWithInnerAssociation := map[string]InnerAssociations{}
3838

3939
// validate if fields contains a non existing field in struct.
4040
// and verify is it has inner associations.
4141
for i := range fields {
42-
var innerField, field string
42+
var innerField string
4343

4444
if !validAssociationExpRegexp.MatchString(fields[i]) {
4545
return associations, fmt.Errorf("association '%s' does not match the format %s", fields[i], "'<field>' or '<field>.<nested-field>'")
4646
}
4747

48-
if strings.Contains(fields[i], ".") {
49-
dotIndex := strings.Index(fields[i], ".")
50-
field = fields[i][:dotIndex]
51-
innerField = fields[i][dotIndex+1:]
52-
fields[i] = field
53-
}
48+
fields[i], innerField = extractFieldAndInnerFields(fields[i])
49+
5450
if _, ok := t.FieldByName(fields[i]); !ok {
5551
return associations, fmt.Errorf("field %s does not exist in model %s", fields[i], t.Name())
5652
}
5753

5854
if innerField != "" {
59-
innerAssociations = append(innerAssociations, InnerAssociation{fields[i], innerField})
55+
var found bool
56+
innerF, _ := extractFieldAndInnerFields(innerField)
57+
58+
for j := range fieldsWithInnerAssociation[fields[i]] {
59+
f, _ := extractFieldAndInnerFields(fieldsWithInnerAssociation[fields[i]][j].Fields[0])
60+
if innerF == f {
61+
fieldsWithInnerAssociation[fields[i]][j].Fields = append(fieldsWithInnerAssociation[fields[i]][j].Fields, innerField)
62+
found = true
63+
break
64+
}
65+
}
66+
67+
if !found {
68+
fieldsWithInnerAssociation[fields[i]] = append(fieldsWithInnerAssociation[fields[i]], InnerAssociation{fields[i], []string{innerField}})
69+
}
6070
}
6171
}
6272

@@ -79,7 +89,7 @@ func ForStruct(s interface{}, fields ...string) (Associations, error) {
7989
modelType: t,
8090
modelValue: v,
8191
popTags: tags,
82-
innerAssociations: innerAssociations,
92+
innerAssociations: fieldsWithInnerAssociation[f.Name],
8393
}
8494

8595
a, err := builder(params)
@@ -121,3 +131,12 @@ func fieldIgnoredIn(fields []string, field string) bool {
121131
}
122132
return true
123133
}
134+
135+
func extractFieldAndInnerFields(field string) (string, string) {
136+
if !strings.Contains(field, ".") {
137+
return field, ""
138+
}
139+
140+
dotIndex := strings.Index(field, ".")
141+
return field[:dotIndex], field[dotIndex+1:]
142+
}

connection.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import (
55
"sync/atomic"
66
"time"
77

8+
"github.com/pkg/errors"
9+
810
"github.com/gobuffalo/pop/v5/internal/defaults"
911
"github.com/gobuffalo/pop/v5/internal/randx"
10-
"github.com/pkg/errors"
1112
)
1213

1314
// Connections contains all available connections
@@ -117,6 +118,9 @@ func (c *Connection) Open() error {
117118
if details.ConnMaxLifetime > 0 {
118119
db.SetConnMaxLifetime(details.ConnMaxLifetime)
119120
}
121+
if details.ConnMaxIdleTime > 0 {
122+
db.SetConnMaxIdleTime(details.ConnMaxIdleTime)
123+
}
120124
if details.Unsafe {
121125
db = db.Unsafe()
122126
}

connection_details.go

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ type ConnectionDetails struct {
4444
IdlePool int
4545
// Defaults to 0 "unlimited". See https://golang.org/pkg/database/sql/#DB.SetConnMaxLifetime
4646
ConnMaxLifetime time.Duration
47+
// Defaults to 0 "unlimited". See https://golang.org/pkg/database/sql/#DB.SetConnMaxIdleTime
48+
ConnMaxIdleTime time.Duration
4749
// Defaults to `false`. See https://godoc.org/github.com/jmoiron/sqlx#DB.Unsafe
4850
Unsafe bool
4951
Options map[string]string

dialect_cockroach.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func (p *cockroach) Update(s store, model *Model, cols columns.Columns) error {
105105
}
106106

107107
func (p *cockroach) Destroy(s store, model *Model) error {
108-
stmt := p.TranslateSQL(fmt.Sprintf("DELETE FROM %s WHERE %s", p.Quote(model.TableName()), model.whereID()))
108+
stmt := p.TranslateSQL(fmt.Sprintf("DELETE FROM %s AS %s WHERE %s", p.Quote(model.TableName()), model.alias(), model.whereID()))
109109
_, err := genericExec(s, stmt, model.ID())
110110
return err
111111
}

dialect_common.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func genericCreate(s store, model *Model, cols columns.Columns, quoter quotable)
9999
}
100100

101101
func genericUpdate(s store, model *Model, cols columns.Columns, quoter quotable) error {
102-
stmt := fmt.Sprintf("UPDATE %s SET %s WHERE %s", quoter.Quote(model.TableName()), cols.Writeable().QuotedUpdateString(quoter), model.whereNamedID())
102+
stmt := fmt.Sprintf("UPDATE %s AS %s SET %s WHERE %s", quoter.Quote(model.TableName()), model.alias(), cols.Writeable().QuotedUpdateString(quoter), model.whereNamedID())
103103
log(logging.SQL, stmt, model.ID())
104104
_, err := s.NamedExec(stmt, model.Value)
105105
if err != nil {
@@ -109,7 +109,7 @@ func genericUpdate(s store, model *Model, cols columns.Columns, quoter quotable)
109109
}
110110

111111
func genericDestroy(s store, model *Model, quoter quotable) error {
112-
stmt := fmt.Sprintf("DELETE FROM %s WHERE %s", quoter.Quote(model.TableName()), model.whereID())
112+
stmt := fmt.Sprintf("DELETE FROM %s AS %s WHERE %s", quoter.Quote(model.TableName()), model.alias(), model.whereID())
113113
_, err := genericExec(s, stmt, model.ID())
114114
if err != nil {
115115
return err

dialect_mysql.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ func (m *mysql) Update(s store, model *Model, cols columns.Columns) error {
9191
}
9292

9393
func (m *mysql) Destroy(s store, model *Model) error {
94-
return errors.Wrap(genericDestroy(s, model, m), "mysql destroy")
94+
stmt := fmt.Sprintf("DELETE FROM %s WHERE %s = ?", m.Quote(model.TableName()), model.IDField())
95+
_, err := genericExec(s, stmt, model.ID())
96+
return errors.Wrap(err, "mysql destroy")
9597
}
9698

9799
func (m *mysql) SelectOne(s store, model *Model, query Query) error {
@@ -155,9 +157,10 @@ func (m *mysql) FizzTranslator() fizz.Translator {
155157

156158
func (m *mysql) DumpSchema(w io.Writer) error {
157159
deets := m.Details()
158-
cmd := exec.Command("mysqldump", "-d", "-h", deets.Host, "-P", deets.Port, "-u", deets.User, fmt.Sprintf("--password=%s", deets.Password), deets.Database)
160+
// Github CI is currently using mysql:5.7 but the mysqldump version doesn't seem to match
161+
cmd := exec.Command("mysqldump", "--column-statistics=0", "-d", "-h", deets.Host, "-P", deets.Port, "-u", deets.User, fmt.Sprintf("--password=%s", deets.Password), deets.Database)
159162
if deets.Port == "socket" {
160-
cmd = exec.Command("mysqldump", "-d", "-S", deets.Host, "-u", deets.User, fmt.Sprintf("--password=%s", deets.Password), deets.Database)
163+
cmd = exec.Command("mysqldump", "--column-statistics=0", "-d", "-S", deets.Host, "-u", deets.User, fmt.Sprintf("--password=%s", deets.Password), deets.Database)
161164
}
162165
return genericDumpSchema(deets, cmd, w)
163166
}

dialect_postgresql.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func (p *postgresql) Update(s store, model *Model, cols columns.Columns) error {
9292
}
9393

9494
func (p *postgresql) Destroy(s store, model *Model) error {
95-
stmt := p.TranslateSQL(fmt.Sprintf("DELETE FROM %s WHERE %s", p.Quote(model.TableName()), model.whereID()))
95+
stmt := p.TranslateSQL(fmt.Sprintf("DELETE FROM %s AS %s WHERE %s", p.Quote(model.TableName()), model.alias(), model.whereID()))
9696
_, err := genericExec(s, stmt, model.ID())
9797
if err != nil {
9898
return err

docker-compose.yml

+2-3
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,9 @@ services:
2424
volumes:
2525
- ./sqldumps:/docker-entrypoint-initdb.d
2626
cockroach:
27-
image: cockroachdb/cockroach:v2.1.0
27+
image: cockroachdb/cockroach:v20.2.4
2828
ports:
2929
- "26257:26257"
30-
- "8080:8080"
3130
volumes:
3231
- "./cockroach-data/roach1:/cockroach/cockroach-data"
33-
command: start --insecure
32+
command: start-single-node --insecure

executors.go

+8-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package pop
22

33
import (
44
"reflect"
5+
"time"
56

67
"github.com/gobuffalo/pop/v5/associations"
78
"github.com/gobuffalo/pop/v5/columns"
@@ -234,8 +235,9 @@ func (c *Connection) Create(model interface{}, excludeColumns ...string) error {
234235
cols.Remove(excludeColumns...)
235236
}
236237

237-
m.touchCreatedAt()
238-
m.touchUpdatedAt()
238+
now := nowFunc().Truncate(time.Microsecond)
239+
m.setUpdatedAt(now)
240+
m.setCreatedAt(now)
239241

240242
if err = c.Dialect.Create(c.Store, m, cols); err != nil {
241243
return err
@@ -357,7 +359,8 @@ func (c *Connection) Update(model interface{}, excludeColumns ...string) error {
357359
cols.Remove(excludeColumns...)
358360
}
359361

360-
m.touchUpdatedAt()
362+
now := nowFunc().Truncate(time.Microsecond)
363+
m.setUpdatedAt(now)
361364

362365
if err = c.Dialect.Update(c.Store, m, cols); err != nil {
363366
return err
@@ -401,7 +404,8 @@ func (c *Connection) UpdateColumns(model interface{}, columnNames ...string) err
401404
}
402405
cols.Remove("id", "created_at")
403406

404-
m.touchUpdatedAt()
407+
now := nowFunc().Truncate(time.Microsecond)
408+
m.setUpdatedAt(now)
405409

406410
if err = c.Dialect.Update(c.Store, m, cols); err != nil {
407411
return err

finders.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,16 @@ func (q *Query) eagerDefaultAssociations(model interface{}) error {
273273
return err
274274
}
275275

276+
if err == sql.ErrNoRows {
277+
continue
278+
}
279+
276280
// load all inner associations.
277281
innerAssociations := association.InnerAssociations()
278282
for _, inner := range innerAssociations {
279283
v = reflect.Indirect(reflect.ValueOf(model)).FieldByName(inner.Name)
280284
innerQuery := Q(query.Connection)
281-
innerQuery.eagerFields = []string{inner.Fields}
285+
innerQuery.eagerFields = inner.Fields
282286
err = innerQuery.eagerAssociations(v.Addr().Interface())
283287
if err != nil {
284288
return err

finders_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -908,3 +908,43 @@ func Test_FindManyToMany(t *testing.T) {
908908
r.NoError(err)
909909
})
910910
}
911+
912+
func Test_FindMultipleInnerHasMany(t *testing.T) {
913+
if PDB == nil {
914+
t.Skip("skipping integration tests")
915+
}
916+
transaction(func(tx *Connection) {
917+
r := require.New(t)
918+
919+
user := User{Name: nulls.NewString("Mark")}
920+
err := tx.Create(&user)
921+
r.NoError(err)
922+
923+
book := Book{Title: "Pop Book", Isbn: "PB1", UserID: nulls.NewInt(user.ID)}
924+
err = tx.Create(&book)
925+
r.NoError(err)
926+
927+
writer := Writer{Name: "Jhon", BookID: book.ID}
928+
err = tx.Create(&writer)
929+
r.NoError(err)
930+
931+
friend := Friend{FirstName: "Frank", LastName: "Kafka", WriterID: writer.ID}
932+
err = tx.Create(&friend)
933+
r.NoError(err)
934+
935+
address := Address{Street: "St 27", HouseNumber: 27, WriterID: writer.ID}
936+
err = tx.Create(&address)
937+
r.NoError(err)
938+
939+
u := User{}
940+
err = tx.Eager("Books.Writers.Addresses", "Books.Writers.Friends").Find(&u, user.ID)
941+
r.NoError(err)
942+
943+
r.Len(u.Books, 1)
944+
r.Len(u.Books[0].Writers, 1)
945+
r.Len(u.Books[0].Writers[0].Addresses, 1)
946+
r.Equal(u.Books[0].Writers[0].Addresses[0].HouseNumber, 27)
947+
r.Len(u.Books[0].Writers[0].Friends, 1)
948+
r.Equal(u.Books[0].Writers[0].Friends[0].FirstName, "Frank")
949+
})
950+
}

0 commit comments

Comments
 (0)