fullstack-lang / gong

Gong (go+ng) is a go sub-language that compiles to go and angular. All valid go is valid gong and vice versa. Gong enables fast development of web applications. Gong is model centric and by default, a UML diagram editor of the model is embedded in each application.
MIT License
11 stars 1 forks source link

Use a "Many" association for gorm instead of the reverse pointer (for the back) #428

Closed thomaspeugeot closed 11 months ago

thomaspeugeot commented 11 months ago

Steps

Rationale

With package hierarchy, navigating from the target to the source in a relation:

What is lost is

Opportunities

Associations to interface (pointer or slice of pointers) polymorphic association

type Layer struct {
    Display bool

    Name string

    Rects         []*Rect
    Texts         []*Text
    Circles       []*Circle
    Lines         []*Line
    Ellipses      []*Ellipse
    Polylines     []*Polyline
    Polygones     []*Polygone
    Paths         []*Path
    Links         []*Link
    RectLinkLinks []*RectLinkLink
}

could be rewritten to

type Layer struct {
    Display bool

    Name string

    Features  []*FeatureInterface
}

could the pointer be encoded in "named struct code" + "instance ID" ?

Exploration

How does gorm encode Many association ? Well, not good

the example given below is not good. It does not encode the slice of CreditCardas a slice of pointers but as a slice...gloop

// User has many CreditCards, UserID is the foreign key
type User struct {
  gorm.Model
  CreditCards []CreditCard
}

type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}

How does gorm encode slice of integers, of struct with two integers ?

type Entity struct {
    ID   uint        `gorm:"primaryKey"`
    Nums GobIntSlice `gorm:"type:BLOB"`
}

type GobIntSlice []int

func (gs *GobIntSlice) Scan(value interface{}) error {
    b := bytes.NewReader(value.([]byte))
    dec := gob.NewDecoder(b)
    return dec.Decode(gs)
}

func (gs GobIntSlice) Value() (driver.Value, error) {
    var b bytes.Buffer
    enc := gob.NewEncoder(&b)
    if err := enc.Encode(gs); err != nil {
        return nil, err
    }
    return b.Bytes(), nil
}

space efficient

type Entity struct {
    ID   uint        `gorm:"primaryKey"`
    Nums StringSlice `gorm:"type:TEXT"`
}

type StringSlice []int

func (ss *StringSlice) Scan(value interface{}) error {
    return json.Unmarshal([]byte(value.(string)), ss)
}

func (ss StringSlice) Value() (driver.Value, error) {
    b, err := json.Marshal(ss)
    return string(b), err
}

easier to debug

However, the space efficiency makes senses only when integers are above 1 billions (see https://github.com/thomaspeugeot/test_gorm)

Impact analysis on the back (ORM)

There is the PointerEncodingpart where it is defined. For Astruct.Ananrrayofb

Owner

func (backRepoAstruct *BackRepoAstructStruct) CommitPhaseTwoInstance(backRepo *BackRepoStruct, idx uint, astruct *models.Astruct) (Error error) {

....
        // This loop encodes the slice of pointers astruct.Anarrayofb into the back repo.
        // Each back repo instance at the end of the association encode the ID of the association start
        // into a dedicated field for coding the association. The back repo instance is then saved to the db
        for idx, bstructAssocEnd := range astruct.Anarrayofb {

            // get the back repo instance at the association end
            bstructAssocEnd_DB :=
                backRepo.BackRepoBstruct.GetBstructDBFromBstructPtr(bstructAssocEnd)

            // encode reverse pointer in the association end back repo instance
            bstructAssocEnd_DB.Astruct_AnarrayofbDBID.Int64 = int64(astructDB.ID)
            bstructAssocEnd_DB.Astruct_AnarrayofbDBID.Valid = true
            bstructAssocEnd_DB.Astruct_AnarrayofbDBID_Index.Int64 = int64(idx)
            bstructAssocEnd_DB.Astruct_AnarrayofbDBID_Index.Valid = true
            if q := backRepoAstruct.db.Save(bstructAssocEnd_DB); q.Error != nil {
                return q.Error
            }
        }

Destination


func (backRepoBstruct *BackRepoBstructStruct) RestorePhaseTwo() {
...
        // insertion point for reindexing pointers encoding
        // This reindex bstruct.Anarrayofb
        if bstructDB.Astruct_AnarrayofbDBID.Int64 != 0 {
            bstructDB.Astruct_AnarrayofbDBID.Int64 =
                int64(BackRepoAstructid_atBckpTime_newID[uint(bstructDB.Astruct_AnarrayofbDBID.Int64)])
        }
....

func (backRepoBstruct *BackRepoBstructStruct) ResetReversePointersInstance(backRepo *BackRepoStruct, idx uint, astruct *models.Bstruct) (Error error) {

...

        // insertion point for reverse pointers reset
        if bstructDB.Astruct_AnarrayofbDBID.Int64 != 0 {
            bstructDB.Astruct_AnarrayofbDBID.Int64 = 0
            bstructDB.Astruct_AnarrayofbDBID.Valid = true

            // save the reset
            if q := backRepoBstruct.db.Save(bstructDB); q.Error != nil {
                return q.Error
            }
        }
func GetReverseFieldOwnerName[T models.Gongstruct](

        case "Astruct":
            switch reverseField.Fieldname {
            case "Anarrayofb":
                if tmp != nil && tmp.Astruct_AnarrayofbDBID.Int64 != 0 {
                    id := uint(tmp.Astruct_AnarrayofbDBID.Int64)
                    reservePointerTarget := backRepo.BackRepoAstruct.Map_AstructDBID_AstructPtr[id]
                    res = reservePointerTarget.Name
                }

Impact analysis on the front

  pull(GONG__StackPath: string = ""): Observable<FrontRepo> {

...

      bstructs.forEach(
              bstruct => {
                // insertion point sub sub template for ONE-/ZERO-ONE associations pointers redeeming

                // insertion point for redeeming ONE-MANY associations
                // insertion point for slice of pointer field Astruct.Anarrayofb redeeming
                {
                  let _astruct = this.frontRepo.Astructs.get(bstruct.Astruct_AnarrayofbDBID.Int64)
                  if (_astruct) {
                    if (_astruct.Anarrayofb == undefined) {
                      _astruct.Anarrayofb = new Array<BstructDB>()
                    }
                    _astruct.Anarrayofb.push(bstruct)
                    if (bstruct.Astruct_Anarrayofb_reverse == undefined) {
                      bstruct.Astruct_Anarrayofb_reverse = _astruct
                    }
                  }
                }

  BstructPull(): Observable<FrontRepo> {

            bstructs.forEach(
              bstruct => {
                this.frontRepo.Bstructs.set(bstruct.ID, bstruct)
                this.frontRepo.Bstructs_batch.set(bstruct.ID, bstruct)

                // insertion point for redeeming ONE/ZERO-ONE associations

                // insertion point for redeeming ONE-MANY associations
                // insertion point for slice of pointer field Astruct.Anarrayofb redeeming
                {
                  let _astruct = this.frontRepo.Astructs.get(bstruct.Astruct_AnarrayofbDBID.Int64)
                  if (_astruct) {
                    if (_astruct.Anarrayofb == undefined) {
                      _astruct.Anarrayofb = new Array<BstructDB>()
                    }
                    _astruct.Anarrayofb.push(bstruct)
                    if (bstruct.Astruct_Anarrayofb_reverse == undefined) {
                      bstruct.Astruct_Anarrayofb_reverse = _astruct
                    }
                  }
                }
            // Third Step: sort arrays (slices in go) according to their index
            // insertion point sub template for redeem 
            astructs.forEach(
              astruct => {
                // insertion point for sorting
                astruct.Anarrayofb?.sort((t1, t2) => {
                  if (t1.Astruct_AnarrayofbDBID_Index.Int64 > t2.Astruct_AnarrayofbDBID_Index.Int64) {
                    return 1;
                  }
                  if (t1.Astruct_AnarrayofbDBID_Index.Int64 < t2.Astruct_AnarrayofbDBID_Index.Int64) {
                    return -1;
                  }
                  return 0;
                })

Impact analysis on frequently used stacks

thomaspeugeot commented 11 months ago

There is a gin problem with JSON serialize when Bs in PointerEncoding is the same as in Models.A

type AAPI struct {
    gorm.Model

    // why models.A It should be AWOP
    models.A

    // encoding of pointers
    APointersEncoding
}

// APointersEncoding encodes pointers to Struct and
// reverse pointers of slice of poitners to Struct
type APointersEncoding struct {
    // insertion for pointer fields encoding declaration
    B2s StringSlice `gorm:"type:TEXT"`
}

Solution is to have AAPI uses AWOP.