@@ -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.
476481func (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.
559608type 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.
647701func 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.
657747func (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.
669759func (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.
701795func (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.
707800func (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.
10071116type Event string
10081117
0 commit comments