Skip to content

Commit fed9009

Browse files
author
Miguel Molina
authored
Merge pull request #48 from erizocosmico/managed-fks
Automanage foreign keys and relationships
2 parents ced6e8d + d42feb0 commit fed9009

26 files changed

+2089
-630
lines changed

batcher.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ func (r *batchQueryRunner) getRecordRelationships(ids []interface{}, rel Relatio
182182
return nil, err
183183
}
184184

185+
rec.setPersisted()
186+
rec.setWritable(true)
185187
indexedResults[val] = append(indexedResults[val], rec)
186188
}
187189

common_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ var ModelSchema = &BaseSchema{
149149
table: "model",
150150
id: f("id"),
151151
foreignKeys: ForeignKeys{
152-
"rel": NewSchemaField("model_id"),
153-
"rels": NewSchemaField("model_id"),
152+
"rel": NewForeignKey("model_id", false),
153+
"rels": NewForeignKey("model_id", false),
154154
},
155155
constructor: func() Record {
156156
return new(model)

generator/processor.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,12 @@ func (p *Processor) processModel(name string, s *types.Struct, t *types.Named) *
174174
m.Events = p.findEvents(t)
175175

176176
var base int
177-
if base, m.Fields = p.processFields(s, nil, true); base == -1 {
177+
var fields []*Field
178+
if base, fields = p.processFields(s, nil, true); base == -1 {
178179
return nil
179180
}
180181

182+
m.SetFields(fields)
181183
p.processBaseField(m, m.Fields[base])
182184
return m
183185
}

generator/template.go

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,12 @@ func (td *TemplateData) GenColumnAddresses(model *Model) string {
5353

5454
func (td *TemplateData) genFieldsColumnAddresses(buf *bytes.Buffer, fields []*Field) {
5555
for _, f := range fields {
56-
if f.Kind == Relationship {
57-
continue
58-
}
59-
6056
if f.Inline() {
6157
td.genFieldsColumnAddresses(buf, f.Fields)
62-
} else {
58+
} else if f.Kind == Relationship && f.IsInverse() {
59+
buf.WriteString(fmt.Sprintf("case \"%s\":\n", f.ForeignKey()))
60+
buf.WriteString(fmt.Sprintf("return kallax.VirtualColumn(\"%s\", r), nil\n", f.ForeignKey()))
61+
} else if f.Kind != Relationship {
6362
buf.WriteString(fmt.Sprintf("case \"%s\":\n", f.ColumnName()))
6463
buf.WriteString(fmt.Sprintf("return %s\n", f.Address()))
6564
}
@@ -76,13 +75,12 @@ func (td *TemplateData) GenColumnValues(model *Model) string {
7675

7776
func (td *TemplateData) genFieldsValues(buf *bytes.Buffer, fields []*Field) {
7877
for _, f := range fields {
79-
if f.Kind == Relationship {
80-
continue
81-
}
82-
8378
if f.Inline() {
8479
td.genFieldsValues(buf, f.Fields)
85-
} else {
80+
} else if f.Kind == Relationship && f.IsInverse() {
81+
buf.WriteString(fmt.Sprintf("case \"%s\":\n", f.ForeignKey()))
82+
buf.WriteString(fmt.Sprintf("return r.Model.VirtualColumn(col), nil\n"))
83+
} else if f.Kind != Relationship {
8684
buf.WriteString(fmt.Sprintf("case \"%s\":\n", f.ColumnName()))
8785
buf.WriteString(fmt.Sprintf("return %s\n", f.Value()))
8886
}
@@ -99,13 +97,11 @@ func (td *TemplateData) GenModelColumns(model *Model) string {
9997

10098
func (td *TemplateData) genFieldsColumns(buf *bytes.Buffer, fields []*Field) {
10199
for _, f := range fields {
102-
if f.Kind == Relationship {
103-
continue
104-
}
105-
106100
if f.Inline() {
107101
td.genFieldsColumns(buf, f.Fields)
108-
} else {
102+
} else if f.Kind == Relationship && f.IsInverse() {
103+
buf.WriteString(fmt.Sprintf("kallax.NewSchemaField(\"%s\"),\n", f.ForeignKey()))
104+
} else if f.Kind != Relationship {
109105
buf.WriteString(fmt.Sprintf("kallax.NewSchemaField(\"%s\"),\n", f.ColumnName()))
110106
}
111107
}

generator/template_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ case "url":
5757
return (*types.URL)(r.URL), nil
5858
case "url_no_ptr":
5959
return (*types.URL)(&r.UrlNoPtr), nil
60+
case "foo_id":
61+
return kallax.VirtualColumn("foo_id", r), nil
6062
`
6163

6264
const baseTpl = `
@@ -83,6 +85,7 @@ const baseTpl = `
8385
JSON JSON
8486
URL *url.URL
8587
UrlNoPtr url.URL
88+
RelInverse Rel ` + "`fk:\",inverse\"`" + `
8689
}
8790
`
8891

@@ -110,6 +113,8 @@ case "url":
110113
return (*types.URL)(r.URL), nil
111114
case "url_no_ptr":
112115
return (*types.URL)(&r.UrlNoPtr), nil
116+
case "foo_id":
117+
return r.Model.VirtualColumn(col), nil
113118
`
114119

115120
func (s *TemplateSuite) TestGenColumnValues() {
@@ -140,6 +145,7 @@ func (s *TemplateSuite) TestGenColumnValues() {
140145
JSON JSON
141146
URL *url.URL
142147
UrlNoPtr url.URL
148+
RelInverse Rel ` + "`fk:\",inverse\"`" + `
143149
}
144150
`)
145151

@@ -155,6 +161,7 @@ kallax.NewSchemaField("arr"),
155161
kallax.NewSchemaField("json"),
156162
kallax.NewSchemaField("url"),
157163
kallax.NewSchemaField("url_no_ptr"),
164+
kallax.NewSchemaField("foo_id"),
158165
`
159166

160167
func (s *TemplateSuite) TestGenModelColumns() {

generator/templates/model.tgo

Lines changed: 164 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ func (r *{{.Name}}) SetRelationship(field string, rel interface{}) error {
4141
if !ok {
4242
return fmt.Errorf("kallax: record of type %t can't be assigned to relationship {{.Name}}", rel)
4343
}
44-
r.{{.Name}} = {{if or (not .IsPtr) .IsOneToManyRelationship}}*{{end}}val
44+
{{if .IsPtr}}if !val.Model.ID.IsEmpty() {
45+
r.{{.Name}} = val
46+
}
47+
{{else}}r.{{.Name}} = *val{{end}}
4548
return nil
4649
{{else}}case "{{.Name}}":
4750
records, ok := rel.([]kallax.Record)
@@ -72,8 +75,45 @@ type {{.StoreName}} struct {
7275
// New{{.StoreName}} creates a new instance of {{.StoreName}}
7376
// using a SQL database.
7477
func New{{.StoreName}}(db *sql.DB) *{{.StoreName}} {
75-
return &{{.StoreName}}{kallax.NewStore(db, Schema.{{.Name}}.BaseSchema)}
78+
return &{{.StoreName}}{kallax.NewStore(db)}
79+
}
80+
81+
{{if .HasRelationships}}
82+
func (s *{{.StoreName}}) relationshipRecords(record *{{.Name}}) []kallax.RecordWithSchema {
83+
record.ClearVirtualColumns()
84+
var records []kallax.RecordWithSchema
85+
{{range .Relationships}}
86+
{{if .IsInverse}}
87+
if {{if .IsPtr}}record.{{.Name}} != nil{{else}}!record.{{.Name}}.ID.IsEmpty(){{end}} {
88+
record.AddVirtualColumn("{{.ForeignKey}}", record.{{.Name}}.ID)
89+
records = append(records, kallax.RecordWithSchema{
90+
Schema.{{.TypeSchemaName}}.BaseSchema,
91+
{{if not .IsPtr}}&{{end}}record.{{.Name}},
92+
})
93+
}
94+
{{else if .IsOneToManyRelationship}}
95+
for _, rec := range record.{{.Name}} {
96+
rec.ClearVirtualColumns()
97+
rec.AddVirtualColumn("{{.ForeignKey}}", record.ID)
98+
records = append(records, kallax.RecordWithSchema{
99+
Schema.{{.TypeSchemaName}}.BaseSchema,
100+
{{if not ($.IsPtrSlice .)}}&{{end}}rec,
101+
})
102+
}
103+
{{else}}
104+
if {{if .IsPtr}}record.{{.Name}} != nil{{else}}!record.{{.Name}}.ID.IsEmpty(){{end}} {
105+
record.{{.Name}}.ClearVirtualColumns()
106+
record.{{.Name}}.AddVirtualColumn("{{.ForeignKey}}", record.ID)
107+
records = append(records, kallax.RecordWithSchema{
108+
Schema.{{.TypeSchemaName}}.BaseSchema,
109+
{{if not .IsPtr}}&{{end}}record.{{.Name}},
110+
})
111+
}
112+
{{end}}
113+
{{end}}
114+
return records
76115
}
116+
{{end}}
77117

78118
// Insert inserts a {{.Name}} in the database. A non-persisted object is
79119
// required for this operation.
@@ -87,7 +127,25 @@ func (s *{{.StoreName}}) Insert(record *{{.Name}}) error {
87127
if err := record.BeforeInsert(); err != nil {
88128
}
89129
{{end}}
90-
return s.Store.Insert(record)
130+
{{if .HasRelationships}}
131+
records := s.relationshipRecords(record)
132+
if len(records) > 0 {
133+
return s.Store.Transaction(func(s *kallax.Store) error {
134+
if err := s.Insert(Schema.{{.Name}}.BaseSchema, record); err != nil {
135+
return err
136+
}
137+
138+
for _, r := range records {
139+
if _, err := s.Save(r.Schema, r.Record); err != nil {
140+
return err
141+
}
142+
}
143+
144+
return nil
145+
})
146+
}
147+
{{end}}
148+
return s.Store.Insert(Schema.{{.Name}}.BaseSchema, record)
91149
}
92150

93151
// Update updates the given record on the database. If the columns are given,
@@ -96,7 +154,7 @@ func (s *{{.StoreName}}) Insert(record *{{.Name}}) error {
96154
// in memory but not on the database.
97155
// Only writable records can be updated. Writable objects are those that have
98156
// been just inserted or retrieved using a query with no custom select fields.
99-
func (s *{{.StoreName}}) Update(record *{{.Name}}, cols ...kallax.SchemaField) (int64, error) {
157+
func (s *{{.StoreName}}) Update(record *{{.Name}}, cols ...kallax.SchemaField) (updated int64, err error) {
100158
{{if .Events.Has "BeforeSave"}}
101159
if err := record.BeforeSave(); err != nil {
102160
return 0, err
@@ -107,7 +165,26 @@ func (s *{{.StoreName}}) Update(record *{{.Name}}, cols ...kallax.SchemaField) (
107165
return 0, err
108166
}
109167
{{end}}
110-
return s.Store.Update(record, cols...)
168+
{{if .HasRelationships}}
169+
records := s.relationshipRecords(record)
170+
if len(records) > 0 {
171+
err = s.Store.Transaction(func(s *kallax.Store) error {
172+
updated, err = s.Update(Schema.{{.Name}}.BaseSchema, record, cols...)
173+
if err != nil {
174+
return err
175+
}
176+
177+
for _, r := range records {
178+
if _, err := s.Save(r.Schema, r.Record); err != nil {
179+
return err
180+
}
181+
}
182+
return nil
183+
})
184+
return updated, err
185+
}
186+
{{end}}
187+
return s.Store.Update(Schema.{{.Name}}.BaseSchema, record, cols...)
111188
}
112189

113190
// Save inserts the object if the record is not persisted, otherwise it updates
@@ -132,12 +209,21 @@ func (s *{{.StoreName}}) Save(record *{{.Name}}) (updated bool, err error) {
132209
}
133210
}
134211
{{end}}
135-
return s.Store.Save(record)
212+
if !record.IsPersisted() {
213+
return false, s.Insert(record)
214+
}
215+
216+
rowsUpdated, err := s.Update(record)
217+
if err != nil {
218+
return false, err
219+
}
220+
221+
return rowsUpdated > 0, nil
136222
}
137223

138224
// Delete removes the given record from the database.
139225
func (s *{{.StoreName}}) Delete(record *{{.Name}}) error {
140-
return s.Store.Delete(record)
226+
return s.Store.Delete(Schema.{{.Name}}.BaseSchema, record)
141227
}
142228

143229
// Find returns the set of results for the given query.
@@ -172,6 +258,7 @@ func (s *{{.StoreName}}) MustCount(q *{{.QueryName}}) int64 {
172258
// `sql.ErrNoRows` is returned if there are no results.
173259
func (s *{{.StoreName}}) FindOne(q *{{.QueryName}}) (*{{.Name}}, error) {
174260
q.Limit(1)
261+
q.Offset(0)
175262
rs, err := s.Find(q)
176263
if err != nil {
177264
return nil, err
@@ -206,7 +293,7 @@ func (s *{{.StoreName}}) MustFindOne(q *{{.QueryName}}) *{{.Name}} {
206293
// Reload refreshes the {{.Name}} with the data in the database and
207294
// makes it writable.
208295
func (s *{{.StoreName}}) Reload(record *{{.Name}}) error {
209-
return s.Store.Reload(record)
296+
return s.Store.Reload(Schema.{{.Name}}.BaseSchema, record)
210297
}
211298

212299
// Transaction executes the given callback in a transaction and rollbacks if
@@ -223,6 +310,75 @@ func (s *{{.StoreName}}) Transaction(callback func(*{{.StoreName}}) error) error
223310
})
224311
}
225312

313+
{{range .Relationships}}{{if .IsOneToManyRelationship}}
314+
// Remove{{.Name}} removes the given items of the {{.Name}} field of the
315+
// model. If no items are given, it removes all of them.
316+
// The items will also be removed from the passed record inside this method.
317+
func (s *{{.Model.StoreName}}) Remove{{.Name}}(record *{{.Model.Name}}, deleted ...{{if $.IsPtrSlice .}}*{{end}}{{$.GenTypeName .}}) error {
318+
var updated []{{if $.IsPtrSlice .}}*{{end}}{{$.GenTypeName .}}
319+
var clear bool
320+
if len(deleted) == 0 {
321+
clear = true
322+
deleted = record.{{.Name}}
323+
if len(deleted) == 0 {
324+
return nil
325+
}
326+
}
327+
328+
if len(deleted) > 1 {
329+
err := s.Store.Transaction(func(s *kallax.Store) error {
330+
for _, d := range deleted {
331+
if err := s.Delete(Schema.{{.TypeSchemaName}}.BaseSchema, {{if not ($.IsPtrSlice .)}}&{{end}}d); err != nil {
332+
return err
333+
}
334+
}
335+
return nil
336+
})
337+
338+
if err != nil {
339+
return err
340+
}
341+
342+
if clear {
343+
record.{{.Name}} = nil
344+
return nil
345+
}
346+
} else {
347+
if err := s.Store.Delete(Schema.{{.TypeSchemaName}}.BaseSchema, {{if not ($.IsPtrSlice .)}}&{{end}}deleted[0]); err != nil {
348+
return err
349+
}
350+
}
351+
352+
353+
for _, r := range record.{{.Name}} {
354+
var found bool
355+
for _, d := range deleted {
356+
if d.ID == r.ID {
357+
found = true
358+
break
359+
}
360+
}
361+
if !found {
362+
updated = append(updated, r)
363+
}
364+
}
365+
record.{{.Name}} = updated
366+
return nil
367+
}
368+
{{else if not .IsInverse}}
369+
// Remove{{.Name}} removes from the database the given relationship of the
370+
// model. It also resets the field {{.Name}} of the model.
371+
func (s *{{.Model.StoreName}}) Remove{{.Name}}(record *{{.Model.Name}}) error {
372+
err := s.Store.Delete(Schema.{{.TypeSchemaName}}.BaseSchema, {{if not .IsPtr}}&{{end}}record.{{.Name}})
373+
if err != nil {
374+
return err
375+
}
376+
377+
record.{{.Name}} = {{if .IsPtr}}nil{{else}}{{$.GenTypeName .}}{}{{end}}
378+
return nil
379+
}
380+
{{end}}{{end}}
381+
226382
{{template "query" .}}
227383

228384
{{template "resultset" .}}

generator/templates/resultset.tgo

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ func New{{.ResultSetName}}(rs kallax.ResultSet) *{{.ResultSetName}} {
1818
// The result set is closed automatically when there are no more items.
1919
func (rs *{{.ResultSetName}}) Next() bool {
2020
if !rs.ResultSet.Next() {
21-
rs.lastErr = rs.Close()
21+
rs.lastErr = rs.ResultSet.Close()
2222
return false
2323
}
2424

2525
var record kallax.Record
26-
record, rs.lastErr = rs.Get()
27-
if rs.lastErr == nil {
26+
record, rs.lastErr = rs.ResultSet.Get(Schema.{{.Name}}.BaseSchema)
27+
if rs.lastErr != nil {
2828
rs.last = nil
2929
} else {
3030
var ok bool

generator/templates/schema.tgo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var Schema = &schema{
2020
"{{.Alias}}",
2121
kallax.NewSchemaField("id"),
2222
kallax.ForeignKeys{
23-
{{range .Relationships}}"{{.Name}}": kallax.NewSchemaField("{{.ForeignKey}}"),
23+
{{range .Relationships}}"{{.Name}}": kallax.NewForeignKey("{{.ForeignKey}}", {{if .IsInverse}}true{{else}}false{{end}}),
2424
{{end}}
2525
},
2626
func() kallax.Record {

0 commit comments

Comments
 (0)