210 lines
6.0 KiB
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,
|
|
})*/
|
|
|
|
}
|
|
}
|