diamond-orm/relationship.go

210 lines
6.0 KiB
Go

package orm
import (
"fmt"
"reflect"
"strings"
)
type RelationshipType int
const (
HasOne RelationshipType = iota
HasMany
ManyToMany
)
type Relationship struct {
Type RelationshipType
Model *Model
FieldName string
Idx int
RelatedType reflect.Type
RelatedModel *Model
Kind reflect.Kind // field kind (struct, slice, ...)
m2mInverse *Relationship
}
func (r *Relationship) JoinTable() string {
return r.Model.TableName + "_" + r.RelatedModel.TableName
}
func (r *Relationship) JoinField() string {
isMany := r.Type == HasMany //|| r.Type == ManyToMany
if isMany && r.Model.embeddedIsh {
return r.Model.Name + r.FieldName + "ID"
} else if isMany && !r.Model.embeddedIsh && r.m2mInverse == nil {
return r.Model.Name + "ID"
} else if r.Type == ManyToMany && !r.Model.embeddedIsh {
return r.RelatedModel.Name + "ID"
}
return r.FieldName + "ID"
}
func (r *Relationship) aliasThingy() string {
return pascalToSnakeCase(r.Model.Name + "." + r.FieldName)
}
func (r *Relationship) relatedAlias() string {
return pascalToSnakeCase(r.RelatedModel.Name + "." + r.FieldName)
}
func (r *Relationship) m2mIsh() bool {
return r.Model.embeddedIsh && !r.RelatedModel.embeddedIsh && r.Type == HasMany
}
func (r *Relationship) joinInsert(v reflect.Value, e *Query, pfk any) error {
if r.Type != ManyToMany &&
!r.m2mIsh() {
return nil
}
ichild := v
for ichild.Kind() == reflect.Ptr {
ichild = ichild.Elem()
}
if ichild.Kind() == reflect.Struct {
jtable := r.JoinTable()
jargs := make([]any, 0)
jcols := make([]string, 0)
jcols = append(jcols, fmt.Sprintf("%s_%s",
r.Model.TableName, pascalToSnakeCase(r.FieldName),
))
jargs = append(jargs, pfk)
jcols = append(jcols, r.RelatedModel.TableName+"_id")
jargs = append(jargs, ichild.FieldByName(r.RelatedModel.IDField).Interface())
var ecnt int
e.tx.QueryRow(e.ctx,
fmt.Sprintf("SELECT count(*) from %s where %s = $1 and %s = $2", r.JoinTable(), jcols[0], jcols[1]), jargs...).Scan(&ecnt)
if ecnt > 0 {
return nil
}
jsql := fmt.Sprintf("INSERT INTO %s (%s) VALUES ($1, $2)", jtable, strings.Join(jcols, ", "))
fmt.Printf("[INSERT/JOIN] %s { %s }\n", jsql, logTrunc(jargs, 200))
if !e.engine.dryRun {
_ = e.tx.QueryRow(e.ctx, jsql, jargs...).Scan()
}
}
return nil
}
func (r *Relationship) joinDelete(pk, fk any, q *Query) error {
jc := fmt.Sprintf("%s_%s", r.Model.TableName, pascalToSnakeCase(r.FieldName))
ds := fmt.Sprintf("DELETE FROM %s where %s = $1 and %s = $2",
r.JoinTable(), jc, r.RelatedModel.TableName+"_id")
fmt.Printf("[DELETE/JOIN] %s { %s }\n", ds, logTrunc([]any{pk, fk}, 200))
if !q.engine.dryRun {
_, err := q.tx.Exec(q.ctx, ds, pk, fk)
return err
}
return nil
}
func parseRelationship(field reflect.StructField, modelMap map[string]*Model, outerType reflect.Type, idx int) *Relationship {
rel := &Relationship{
Model: modelMap[outerType.Name()],
RelatedModel: modelMap[field.Type.Name()],
RelatedType: field.Type,
Idx: idx,
Kind: field.Type.Kind(),
FieldName: field.Name,
}
if rel.RelatedType.Kind() == reflect.Slice || rel.RelatedType.Kind() == reflect.Array {
rel.RelatedType = rel.RelatedType.Elem()
}
if rel.RelatedModel == nil {
if rel.RelatedType.Name() == "" {
rt := rel.RelatedType
for rt.Kind() == reflect.Ptr || rt.Kind() == reflect.Slice || rt.Kind() == reflect.Array {
rel.RelatedType = rel.RelatedType.Elem()
rt = rel.RelatedType
}
}
rel.RelatedModel = modelMap[rel.RelatedType.Name()]
if _, ok := modelMap[rel.RelatedType.Name()]; !ok {
rel.RelatedModel = parseModel(reflect.New(rel.RelatedType).Interface())
modelMap[rel.RelatedType.Name()] = rel.RelatedModel
parseModelFields(rel.RelatedModel, modelMap)
rel.RelatedModel.embeddedIsh = true
}
}
switch field.Type.Kind() {
case reflect.Struct:
rel.Type = HasOne
case reflect.Slice:
rel.Type = HasMany
}
return rel
}
func addForeignKeyFields(ref *Relationship) {
rf := ref.RelatedModel.Fields[ref.RelatedModel.IDField]
if rf != nil {
if !ref.RelatedModel.embeddedIsh && !ref.Model.embeddedIsh {
ff := ref.Model.Fields[ref.FieldName]
ff.ColumnType = rf.ColumnType
ff.ColumnName = pascalToSnakeCase(ref.JoinField())
ff.isForeignKey = true
ff.fk = ref
} else if !ref.Model.embeddedIsh {
sid := strings.TrimSuffix(ref.JoinField(), "ID")
ref.RelatedModel.Relationships[sid] = &Relationship{
FieldName: sid,
Type: HasOne,
RelatedModel: ref.Model,
Model: ref.RelatedModel,
Kind: ref.RelatedModel.Type.Kind(),
Idx: -1,
RelatedType: ref.Model.Type,
}
ref.RelatedModel.addField(&Field{
ColumnType: rf.ColumnType,
ColumnName: pascalToSnakeCase(ref.JoinField()),
Name: sid,
isForeignKey: true,
Type: rf.Type,
Index: -1,
fk: ref.RelatedModel.Relationships[sid],
})
} else if ref.Model.embeddedIsh && !ref.RelatedModel.embeddedIsh {
}
} else {
ref.RelatedModel.addField(&Field{
Name: "ID",
ColumnName: "id",
ColumnType: "bigserial",
PrimaryKey: true,
Type: ref.RelatedType,
Index: -1,
AutoIncrement: true,
})
ff := ref.Model.Fields[ref.FieldName]
ff.ColumnType = "bigint"
ff.ColumnName = pascalToSnakeCase(ref.RelatedModel.Name + "ID")
ff.isForeignKey = true
ff.fk = ref
ref.RelatedModel.IDField = "ID"
/*
nn := ref.Model.Name + "ID"
ref.RelatedModel.Relationships[ref.Model.Name] = &Relationship{
Type: HasOne,
RelatedModel: ref.Model,
Model: ref.RelatedModel,
Idx: 65536,
Kind: ref.RelatedModel.Type.Kind(),
RelatedType: ref.RelatedModel.Type,
FieldName: nn,
}
ref.RelatedModel.addField(&Field{
Name: nn,
Type: ref.Model.Type,
fk: ref.RelatedModel.Relationships[nn],
ColumnName: pascalToSnakeCase(nn),
Index: -1,
ColumnType: ref.Model.Fields[ref.Model.IDField].ColumnType,
isForeignKey: true,
})*/
}
}