Skip to content

Commit

Permalink
Fix primary keys reused.
Browse files Browse the repository at this point in the history
Signed-off-by: Jeff Ortel <[email protected]>
  • Loading branch information
jortel committed Aug 22, 2024
1 parent 1a83a4c commit 1da5bcf
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 3 deletions.
1 change: 1 addition & 0 deletions api/migrationwave.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func (h MigrationWaveHandler) Create(ctx *gin.Context) {
_ = ctx.Error(err)
return
}

r.With(m)

h.Respond(ctx, http.StatusCreated, r)
Expand Down
5 changes: 5 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
crd "github.com/konveyor/tackle2-hub/k8s/api"
"github.com/konveyor/tackle2-hub/metrics"
"github.com/konveyor/tackle2-hub/migration"
"github.com/konveyor/tackle2-hub/model"
"github.com/konveyor/tackle2-hub/reaper"
"github.com/konveyor/tackle2-hub/seed"
"github.com/konveyor/tackle2-hub/settings"
Expand Down Expand Up @@ -53,6 +54,10 @@ func Setup() (db *gorm.DB, err error) {
if err != nil {
return
}
err = database.PK.Load(db, model.ALL)
if err != nil {
return
}
return
}

Expand Down
160 changes: 160 additions & 0 deletions database/pk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package database

import (
"errors"
"reflect"
"strings"
"sync"

"github.com/konveyor/tackle2-hub/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/logger"
)

// PK singleton pk sequence.
var PK PkSequence

// PkSequence provides a primary key sequence.
type PkSequence struct {
mutex sync.Mutex
}

// Load highest key for all models.
func (r *PkSequence) Load(db *gorm.DB, models []any) (err error) {
r.mutex.Lock()
defer r.mutex.Unlock()
for _, m := range models {
mt := reflect.TypeOf(m)
if mt.Kind() == reflect.Ptr {
mt = mt.Elem()
}
kind := strings.ToUpper(mt.Name())
db = r.session(db)
q := db.Table(kind)
q = q.Select("MAX(ID) id")
cursor, err := q.Rows()
if err != nil || !cursor.Next() {
// not a table with id.
// discarded.
continue
}
id := int64(0)
err = cursor.Scan(&id)
_ = cursor.Close()
if err != nil {
r.add(db, kind, uint(0))
} else {
r.add(db, kind, uint(id))
}
}
return
}

// Next returns the next primary key.
func (r *PkSequence) Next(db *gorm.DB) (id uint) {
r.mutex.Lock()
defer r.mutex.Unlock()
kind := strings.ToUpper(db.Statement.Table)
m := &model.PK{}
db = r.session(db)
err := db.First(m, "Kind", kind).Error
if err != nil {
return
}
m.LastID++
id = m.LastID
err = db.Save(m).Error
if err != nil {
panic(err)
}
return
}

// session returns a new DB with a new session.
func (r *PkSequence) session(in *gorm.DB) (out *gorm.DB) {
out = &gorm.DB{
Config: in.Config,
}
out.Config.Logger.LogMode(logger.Warn)
out.Statement = &gorm.Statement{
DB: out,
ConnPool: in.Statement.ConnPool,
Context: in.Statement.Context,
Clauses: map[string]clause.Clause{},
Vars: make([]interface{}, 0, 8),
}
return
}

// add the last (higher) id for the kind.
func (r *PkSequence) add(db *gorm.DB, kind string, id uint) {
m := &model.PK{Kind: kind}
db = r.session(db)
err := db.First(m).Error
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
panic(err)
}
}
if m.LastID > id {
return
}
m.LastID = id
db = r.session(db)
err = db.Save(m).Error
if err != nil {
panic(err)
}
}

// assignPk assigns PK as needed.
func assignPk(db *gorm.DB) {
statement := db.Statement
schema := statement.Schema
if schema == nil {
return
}
switch statement.ReflectValue.Kind() {
case reflect.Slice,
reflect.Array:
for i := 0; i < statement.ReflectValue.Len(); i++ {
for _, f := range schema.Fields {
if f.Name != "ID" {
continue
}
_, isZero := f.ValueOf(
statement.Context,
statement.ReflectValue.Index(i))
if isZero {
id := PK.Next(db)
_ = f.Set(
statement.Context,
statement.ReflectValue.Index(i),
id)

}
break
}
}
case reflect.Struct:
for _, f := range schema.Fields {
if f.Name != "ID" {
continue
}
_, isZero := f.ValueOf(
statement.Context,
statement.ReflectValue)
if isZero {
id := PK.Next(db)
_ = f.Set(
statement.Context,
statement.ReflectValue,
id)
}
break
}
default:
log.Info("[WARN] assignPk: unknown kind.")
}
}
11 changes: 10 additions & 1 deletion database/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,16 @@ func Open(enforceFKs bool) (db *gorm.DB, err error) {
err = liberr.Wrap(err)
return
}
err = db.AutoMigrate(model.Setting{})
err = db.AutoMigrate(model.PK{}, model.Setting{})
if err != nil {
err = liberr.Wrap(err)
return
}
err = PK.Load(db, []any{model.Setting{}})
if err != nil {
return
}
err = db.Callback().Create().Before("gorm:before_create").Register("assign-pk", assignPk)
if err != nil {
err = liberr.Wrap(err)
return
Expand Down
7 changes: 7 additions & 0 deletions migration/v14/model/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ type Model struct {
UpdateUser string
}

// PK sequence.
type PK struct {
Kind string `gorm:"<-:create;primaryKey"`
LastID uint
}

// Setting hub settings.
type Setting struct {
Model
Key string `gorm:"<-:create;uniqueIndex"`
Expand Down
1 change: 1 addition & 0 deletions migration/v14/model/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func All() []any {
ImportTag{},
JobFunction{},
MigrationWave{},
PK{},
Proxy{},
Review{},
Setting{},
Expand Down
3 changes: 3 additions & 0 deletions model/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
// Field (data) types.
type JSON = model.JSON

var ALL = model.All()

// Models
type Model = model.Model
type Application = model.Application
Expand All @@ -29,6 +31,7 @@ type ImportSummary = model.ImportSummary
type ImportTag = model.ImportTag
type JobFunction = model.JobFunction
type MigrationWave = model.MigrationWave
type PK = model.PK
type Proxy = model.Proxy
type Questionnaire = model.Questionnaire
type Review = model.Review
Expand Down
6 changes: 6 additions & 0 deletions test/api/migrationwave/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func TestMigrationWaveCRUD(t *testing.T) {
}
assert.Must(t, Application.Create(&expectedApp))
createdApps = append(createdApps, expectedApp)
r.Applications[0].ID = expectedApp.ID
}

createdStakeholders := []api.Stakeholder{}
Expand All @@ -28,6 +29,7 @@ func TestMigrationWaveCRUD(t *testing.T) {
}
assert.Must(t, Stakeholder.Create(&expectedStakeholder))
createdStakeholders = append(createdStakeholders, expectedStakeholder)
r.Stakeholders[0].ID = expectedStakeholder.ID
}

createdStakeholderGroups := []api.StakeholderGroup{}
Expand All @@ -38,6 +40,7 @@ func TestMigrationWaveCRUD(t *testing.T) {
}
assert.Must(t, StakeholderGroup.Create(&expectedStakeholderGroup))
createdStakeholderGroups = append(createdStakeholderGroups, expectedStakeholderGroup)
r.StakeholderGroups[0].ID = expectedStakeholderGroup.ID
}

assert.Must(t, MigrationWave.Create(&r))
Expand Down Expand Up @@ -102,6 +105,7 @@ func TestMigrationWaveList(t *testing.T) {
}
assert.Must(t, Application.Create(&expectedApp))
createdApps = append(createdApps, expectedApp)
r.Applications[0].ID = expectedApp.ID
}

for _, stakeholder := range r.Stakeholders {
Expand All @@ -111,6 +115,7 @@ func TestMigrationWaveList(t *testing.T) {
}
assert.Must(t, Stakeholder.Create(&expectedStakeholder))
createdStakeholders = append(createdStakeholders, expectedStakeholder)
r.Stakeholders[0].ID = expectedStakeholder.ID
}

for _, stakeholderGroup := range r.StakeholderGroups {
Expand All @@ -120,6 +125,7 @@ func TestMigrationWaveList(t *testing.T) {
}
assert.Must(t, StakeholderGroup.Create(&expectedStakeholderGroup))
createdStakeholderGroups = append(createdStakeholderGroups, expectedStakeholderGroup)
r.StakeholderGroups[0].ID = expectedStakeholderGroup.ID
}
assert.Must(t, MigrationWave.Create(&r))
createdMigrationWaves = append(createdMigrationWaves, r)
Expand Down
2 changes: 1 addition & 1 deletion test/api/review/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func TestReviewList(t *testing.T) {

// Delete related reviews and applications.
for _, review := range createdReviews {
assert.Must(t, Application.Delete(review.ID))
assert.Must(t, Application.Delete(review.Application.ID))
assert.Must(t, Review.Delete(review.ID))
}
}
Expand Down
10 changes: 9 additions & 1 deletion test/api/ticket/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func TestTicketCRUD(t *testing.T) {
Name: r.Application.Name,
}
assert.Must(t, Application.Create(&app))
r.Application.ID = app.ID

createdIdentities := []api.Identity{}
createdTrackers := []api.Tracker{}
Expand All @@ -27,8 +28,11 @@ func TestTicketCRUD(t *testing.T) {
Kind: tracker.Kind,
}
assert.Must(t, Identity.Create(&identity))
tracker.Identity.ID = identity.ID
createdIdentities = append(createdIdentities, identity)
assert.Must(t, Tracker.Create(&tracker))
r.Tracker.ID = tracker.ID
r.Tracker.Name = tracker.Name
createdTrackers = append(createdTrackers, tracker)
}

Expand Down Expand Up @@ -72,6 +76,7 @@ func TestTicketList(t *testing.T) {
Name: r.Application.Name,
}
assert.Must(t, Application.Create(&app))
r.Application.ID = app.ID

createdIdentities := []api.Identity{}
createdTrackers := []api.Tracker{}
Expand All @@ -82,8 +87,11 @@ func TestTicketList(t *testing.T) {
Kind: tracker.Kind,
}
assert.Must(t, Identity.Create(&identity))
tracker.Identity.ID = identity.ID
createdIdentities = append(createdIdentities, identity)
assert.Must(t, Tracker.Create(&tracker))
r.Tracker.ID = tracker.ID
r.Tracker.Name = tracker.Name
createdTrackers = append(createdTrackers, tracker)
}

Expand Down Expand Up @@ -113,7 +121,7 @@ func TestTicketList(t *testing.T) {
// Delete tickets and related resources.
for _, ticket := range createdTickets {
assert.Must(t, Ticket.Delete(ticket.ID))
assert.Must(t, Application.Delete(ticket.ID))
assert.Must(t, Application.Delete(ticket.Application.ID))
}
for _, tracker := range createdTrackers {
assert.Must(t, Tracker.Delete(tracker.ID))
Expand Down

0 comments on commit 1da5bcf

Please sign in to comment.