Skip to content

Commit a7eb23f

Browse files
authored
Merge pull request #187 from erizocosmico/feature/pk-in-model
allow definition of primary keys directly on kallax.Model tags
2 parents 7e544e6 + 880d2b0 commit a7eb23f

File tree

7 files changed

+235
-74
lines changed

7 files changed

+235
-74
lines changed

generator/common_test.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import (
1010
parseutil "gopkg.in/src-d/go-parse-utils.v1"
1111
)
1212

13-
func mkField(name, typ string, fields ...*Field) *Field {
14-
f := NewField(name, typ, reflect.StructTag(""))
13+
func mkField(name, typ, tag string, fields ...*Field) *Field {
14+
f := NewField(name, typ, reflect.StructTag(tag))
1515
f.SetFields(fields)
1616
return f
1717
}
@@ -46,13 +46,9 @@ func withNode(f *Field, name string, typ types.Type) *Field {
4646
return f
4747
}
4848

49-
func withTag(f *Field, tag string) *Field {
50-
f.Tag = reflect.StructTag(tag)
51-
return f
52-
}
53-
5449
func inline(f *Field) *Field {
55-
return withTag(f, `kallax:",inline"`)
50+
f.Tag = reflect.StructTag(`kallax:",inline"`)
51+
return f
5652
}
5753

5854
func processorFixture(source string) (*Processor, error) {

generator/processor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,11 @@ func (p *Processor) processModel(name string, s *types.Struct, t *types.Named) (
201201
return nil, nil
202202
}
203203

204+
p.processBaseField(m, fields[base])
204205
if err := m.SetFields(fields); err != nil {
205206
return nil, err
206207
}
207208

208-
p.processBaseField(m, fields[base])
209209
return m, nil
210210
}
211211

generator/processor_test.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -384,19 +384,23 @@ func (s *ProcessorSuite) TestIsEmbedded() {
384384
type Bar struct {
385385
kallax.Model
386386
ID int64 ` + "`pk:\"autoincr\"`" + `
387-
Bar string
387+
Baz string
388388
}
389389
390390
type Struct struct {
391-
Bar Bar
391+
Qux Bar
392+
}
393+
394+
type Struct2 struct {
395+
Mux string
392396
}
393397
394398
type Foo struct {
395399
kallax.Model
396400
ID int64 ` + "`pk:\"autoincr\"`" + `
397401
A Bar
398402
B *Bar
399-
Bar
403+
Struct2
400404
*Struct
401405
C struct {
402406
D int
@@ -405,21 +409,16 @@ func (s *ProcessorSuite) TestIsEmbedded() {
405409
`
406410
pkg := s.processFixture(src)
407411
m := findModel(pkg, "Foo")
408-
cases := []struct {
409-
field string
410-
embedded bool
411-
}{
412-
{"Model", true},
413-
{"A", false},
414-
{"B", false},
415-
{"Bar", true},
416-
{"Struct", true},
417-
{"C", false},
412+
expected := []string{
413+
"ID", "Model", "A", "B", "Mux", "Qux", "C",
418414
}
419415

420-
for _, c := range cases {
421-
s.Equal(c.embedded, findField(m, c.field).IsEmbedded, c.field)
416+
var names []string
417+
for _, f := range m.Fields {
418+
names = append(names, f.Name)
422419
}
420+
421+
s.Equal(expected, names)
423422
}
424423

425424
func TestProcessor(t *testing.T) {

generator/template.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -512,14 +512,16 @@ func addTemplate(base *template.Template, name string, filename string) *templat
512512
return template.Must(base.New(name).Parse(text))
513513
}
514514

515-
var base *template.Template = makeTemplate("base", "templates/base.tgo")
516-
var schema *template.Template = addTemplate(base, "schema", "templates/schema.tgo")
517-
var model *template.Template = addTemplate(base, "model", "templates/model.tgo")
518-
var query *template.Template = addTemplate(model, "query", "templates/query.tgo")
519-
var resultset *template.Template = addTemplate(model, "resultset", "templates/resultset.tgo")
515+
var (
516+
base = makeTemplate("base", "templates/base.tgo")
517+
schema = addTemplate(base, "schema", "templates/schema.tgo")
518+
model = addTemplate(base, "model", "templates/model.tgo")
519+
query = addTemplate(model, "query", "templates/query.tgo")
520+
resultset = addTemplate(model, "resultset", "templates/resultset.tgo")
521+
)
520522

521523
// Base is the default Template instance with all templates preloaded.
522-
var Base *Template = &Template{template: base}
524+
var Base = &Template{template: base}
523525

524526
const (
525527
// tplFindByCollection is the template of the FindBy autogenerated for
@@ -709,10 +711,9 @@ func shortName(pkg *types.Package, typ types.Type) string {
709711

710712
if specialName, ok := specialTypeShortName(typ); ok {
711713
return prefix + specialName
712-
} else {
713-
shortName := typeString(typ, pkg)
714-
return prefix + strings.Replace(shortName, "*", "", -1)
715714
}
715+
shortName := typeString(typ, pkg)
716+
return prefix + strings.Replace(shortName, "*", "", -1)
716717
}
717718

718719
// isEqualizable returns true if the autogenerated FindBy will use an equal query

generator/types.go

Lines changed: 122 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -470,15 +470,20 @@ func (m *Model) CtorRetVars() string {
470470
return strings.Join(ret, ", ")
471471
}
472472

473-
// SetFields sets all the children fields and their model to the current model.
473+
// SetFields sets all the children fields and their model to the current
474+
// model.
474475
// It also finds the primary key and sets it in the model.
475476
// It will return an error if more than one primary key is found.
477+
// SetFields always sets the primary key as the first field of the model.
478+
// So, all models can expect to have the primary key in the position 0 of
479+
// their field slice. This is because the Store will expect the ID in that
480+
// position.
476481
func (m *Model) SetFields(fields []*Field) error {
477482
var fs []*Field
478483
var id *Field
479-
for _, f := range fields {
484+
for _, f := range flattenFields(fields) {
480485
f.Model = m
481-
if f.IsPrimaryKey() {
486+
if f.IsPrimaryKey() && f.Type != BaseModel {
482487
if id != nil {
483488
return fmt.Errorf(
484489
"kallax: found more than one primary key in model %s: %s and %s",
@@ -489,14 +494,56 @@ func (m *Model) SetFields(fields []*Field) error {
489494
}
490495

491496
id = f
492-
m.ID = f
497+
} else if f.IsPrimaryKey() {
498+
if f.primaryKey == "" {
499+
return fmt.Errorf(
500+
"kallax: primary key defined in %s has no field name, but it must be specified",
501+
f.Name,
502+
)
503+
}
504+
505+
// the pk is defined in the model, we need to collect the model
506+
// and we'll look for the field afterwards, when we have collected
507+
// all fields. The model is appended to the field set, though,
508+
// because it will not act as a primary key.
509+
id = f
510+
fs = append(fs, f)
493511
} else {
494512
fs = append(fs, f)
495513
}
496514
}
497515

516+
// if the id is a Model we need to look for the specified field
517+
if id != nil && id.Type == BaseModel {
518+
for i, f := range fs {
519+
if f.columnName == id.primaryKey {
520+
f.isPrimaryKey = true
521+
f.isAutoincrement = id.isAutoincrement
522+
id = f
523+
524+
if len(fs)-1 == i {
525+
fs = append(fs[:i])
526+
} else {
527+
fs = append(fs[:i], fs[i+1:]...)
528+
}
529+
break
530+
}
531+
}
532+
533+
// If the ID is still a base model, means we did not find the pk
534+
// field.
535+
if id.Type == BaseModel {
536+
return fmt.Errorf(
537+
"kallax: the primary key was supposed to be %s according to the pk definition in %s, but the field could not be found",
538+
id.primaryKey,
539+
id.Name,
540+
)
541+
}
542+
}
543+
498544
if id != nil {
499545
m.Fields = []*Field{id}
546+
m.ID = id
500547
}
501548
m.Fields = append(m.Fields, fs...)
502549
return nil
@@ -556,6 +603,8 @@ func relationshipsOnFields(fields []*Field) []*Field {
556603
return result
557604
}
558605

606+
// ImplicitFK is a foreign key that is defined on just one side of the
607+
// relationship and needs to be added on the other side.
559608
type ImplicitFK struct {
560609
Name string
561610
Type string
@@ -590,6 +639,11 @@ type Field struct {
590639
// A struct is considered embedded if and only if the struct was embedded
591640
// as defined in Go.
592641
IsEmbedded bool
642+
643+
primaryKey string
644+
isPrimaryKey bool
645+
isAutoincrement bool
646+
columnName string
593647
}
594648

595649
// FieldKind is the kind of a field.
@@ -645,13 +699,49 @@ func (t FieldKind) String() string {
645699

646700
// NewField creates a new field with its name, type and struct tag.
647701
func NewField(n, t string, tag reflect.StructTag) *Field {
702+
pkName, autoincr, isPrimaryKey := pkProperties(tag)
703+
648704
return &Field{
649705
Name: n,
650706
Type: t,
651707
Tag: tag,
708+
709+
primaryKey: pkName,
710+
columnName: columnName(n, tag),
711+
isPrimaryKey: isPrimaryKey,
712+
isAutoincrement: autoincr,
652713
}
653714
}
654715

716+
// pkProperties returns the primary key properties from a struct tag.
717+
// Valid primary key definitions are the following:
718+
// - pk:"" -> non-autoincr primary key without a field name.
719+
// - pk:"autoincr" -> autoincr primary key without a field name.
720+
// - pk:"foobar" -> non-autoincr primary key with a field name.
721+
// - pk:"foobar,autoincr" -> autoincr primary key with a field name.
722+
func pkProperties(tag reflect.StructTag) (name string, autoincr, isPrimaryKey bool) {
723+
val, ok := tag.Lookup("pk")
724+
if !ok {
725+
return
726+
}
727+
728+
isPrimaryKey = true
729+
if val == "autoincr" || val == "" {
730+
if val == "autoincr" {
731+
autoincr = true
732+
}
733+
return
734+
}
735+
736+
parts := strings.Split(val, ",")
737+
name = parts[0]
738+
if len(parts) > 1 && parts[1] == "autoincr" {
739+
autoincr = true
740+
}
741+
742+
return
743+
}
744+
655745
// SetFields sets all the children fields and the current field as a parent of
656746
// the children.
657747
func (f *Field) SetFields(sf []*Field) {
@@ -667,16 +757,20 @@ func (f *Field) SetFields(sf []*Field) {
667757
// is the field name converted to lower snake case.
668758
// If the resultant name is a reserved keyword a _ will be prepended to the name.
669759
func (f *Field) ColumnName() string {
670-
name := strings.TrimSpace(strings.Split(f.Tag.Get("kallax"), ",")[0])
671-
if name == "" {
672-
name = toLowerSnakeCase(f.Name)
760+
return f.columnName
761+
}
762+
763+
func columnName(name string, tag reflect.StructTag) string {
764+
n := strings.TrimSpace(strings.Split(tag.Get("kallax"), ",")[0])
765+
if n == "" {
766+
n = toLowerSnakeCase(name)
673767
}
674768

675-
if _, ok := reservedKeywords[strings.ToLower(name)]; ok {
676-
name = "_" + name
769+
if _, ok := reservedKeywords[strings.ToLower(n)]; ok {
770+
n = "_" + n
677771
}
678772

679-
return name
773+
return n
680774
}
681775

682776
// ForeignKey returns the name of the foreign keys as specified in the struct
@@ -699,13 +793,12 @@ func (f *Field) ForeignKey() string {
699793

700794
// IsPrimaryKey reports whether the field is the primary key.
701795
func (f *Field) IsPrimaryKey() bool {
702-
_, ok := f.Tag.Lookup("pk")
703-
return ok
796+
return f.isPrimaryKey
704797
}
705798

706799
// IsAutoIncrement reports whether the field is an autoincrementable primary key.
707800
func (f *Field) IsAutoIncrement() bool {
708-
return f.Tag.Get("pk") == "autoincr"
801+
return f.isAutoincrement
709802
}
710803

711804
// IsInverse returns whether the field is an inverse relationship.
@@ -1003,6 +1096,22 @@ func toLowerSnakeCase(s string) string {
10031096
return buf.String()
10041097
}
10051098

1099+
// flattenFields will recursively flatten all fields removing the embedded ones
1100+
// from the field set.
1101+
func flattenFields(fields []*Field) []*Field {
1102+
var result = make([]*Field, 0, len(fields))
1103+
1104+
for _, f := range fields {
1105+
if f.IsEmbedded && f.Type != BaseModel {
1106+
result = append(result, flattenFields(f.Fields)...)
1107+
} else {
1108+
result = append(result, f)
1109+
}
1110+
}
1111+
1112+
return result
1113+
}
1114+
10061115
// Event is the name of an event.
10071116
type Event string
10081117

0 commit comments

Comments
 (0)