[#5611] removed writable_schema usage

This commit is contained in:
Gani Georgiev 2024-10-18 07:55:25 +03:00
parent ade061cc80
commit 5dbf975424
4 changed files with 66 additions and 49 deletions

View File

@ -7,6 +7,8 @@
- Fixed the JSVM types to include properly generated function declarations when the related Go functions have shortened/combined return values. - Fixed the JSVM types to include properly generated function declarations when the related Go functions have shortened/combined return values.
- Reorganized the record table fields<->columns syncing to remove the `PRAGMA writable_schema` usage.
## v0.23.0-rc6 ## v0.23.0-rc6

View File

@ -265,7 +265,7 @@ func (app *BaseApp) registerCollectionHooks() {
// --- // ---
onErrorReloadCachedCollections := func(ce *CollectionErrorEvent) error { onErrorReloadCachedCollections := func(ce *CollectionErrorEvent) error {
if err := ce.App.ReloadCachedCollections(); err != nil { if err := ce.App.ReloadCachedCollections(); err != nil {
ce.App.Logger().Warn("Failed to reload collections cache", "error", err) ce.App.Logger().Warn("Failed to reload collections cache after collection change error", "error", err)
} }
return ce.Next() return ce.Next()

View File

@ -158,15 +158,6 @@ func normalizeSingleVsMultipleFieldChanges(app App, newCollection *Collection, o
} }
return app.RunInTransaction(func(txApp App) error { return app.RunInTransaction(func(txApp App) error {
// temporary disable the schema error checks to prevent view and trigger errors
// when "altering" (aka. deleting and recreating) the non-normalized columns
if _, err := txApp.DB().NewQuery("PRAGMA writable_schema = ON").Execute(); err != nil {
return err
}
// executed with defer to make sure that the pragma is always reverted
// in case of an error and when nested transactions are used
defer txApp.DB().NewQuery("PRAGMA writable_schema = RESET").Execute()
for _, newField := range newCollection.Fields { for _, newField := range newCollection.Fields {
// allow to continue even if there is no old field for the cases // allow to continue even if there is no old field for the cases
// when a new field is added and there are already inserted data // when a new field is added and there are already inserted data
@ -186,17 +177,42 @@ func normalizeSingleVsMultipleFieldChanges(app App, newCollection *Collection, o
continue // no change continue // no change
} }
// update the column definition by: // -------------------------------------------------------
// 1. inserting a new column with the new definition // update the field column definition
// 2. copy normalized values from the original column to the new one
// 3. drop the original column
// 4. rename the new column to the original column
// ------------------------------------------------------- // -------------------------------------------------------
originalName := newField.GetName() // temporary drop all views to prevent reference errors during the columns renaming
tempName := "_" + newField.GetName() + security.PseudorandomString(5) // (this is used as an "alternative" to the writable_schema PRAGMA)
views := []struct {
Name string `db:"name"`
SQL string `db:"sql"`
}{}
err := txApp.DB().Select("name", "sql").
From("sqlite_master").
AndWhere(dbx.NewExp("sql is not null")).
AndWhere(dbx.HashExp{"type": "view"}).
All(&views)
if err != nil {
return err
}
for _, view := range views {
err = txApp.DeleteView(view.Name)
if err != nil {
return err
}
}
_, err := txApp.DB().AddColumn(newCollection.Name, tempName, newField.ColumnType(txApp)).Execute() originalName := newField.GetName()
oldTempName := "_" + newField.GetName() + security.PseudorandomString(5)
// rename temporary the original column to something else to allow inserting a new one in its place
_, err = txApp.DB().RenameColumn(newCollection.Name, originalName, oldTempName).Execute()
if err != nil {
return err
}
// reinsert the field column with the new type
_, err = txApp.DB().AddColumn(newCollection.Name, originalName, newField.ColumnType(txApp)).Execute()
if err != nil { if err != nil {
return err return err
} }
@ -220,12 +236,12 @@ func normalizeSingleVsMultipleFieldChanges(app App, newCollection *Collection, o
END END
)`, )`,
newCollection.Name, newCollection.Name,
tempName,
originalName,
originalName,
originalName,
originalName,
originalName, originalName,
oldTempName,
oldTempName,
oldTempName,
oldTempName,
oldTempName,
)) ))
} else { } else {
// multiple -> single (keep only the last element) // multiple -> single (keep only the last element)
@ -247,35 +263,37 @@ func normalizeSingleVsMultipleFieldChanges(app App, newCollection *Collection, o
END END
)`, )`,
newCollection.Name, newCollection.Name,
tempName,
originalName,
originalName,
originalName,
originalName,
originalName, originalName,
oldTempName,
oldTempName,
oldTempName,
oldTempName,
oldTempName,
)) ))
} }
// copy the normalized values // copy the normalized values
if _, err := copyQuery.Execute(); err != nil { _, err = copyQuery.Execute()
if err != nil {
return err return err
} }
// drop the original column // drop the original column
if _, err := txApp.DB().DropColumn(newCollection.Name, originalName).Execute(); err != nil { _, err = txApp.DB().DropColumn(newCollection.Name, oldTempName).Execute()
if err != nil {
return err return err
} }
// rename the new column back to the original // restore views
if _, err := txApp.DB().RenameColumn(newCollection.Name, tempName, originalName).Execute(); err != nil { for _, view := range views {
return err _, err = txApp.DB().NewQuery(view.SQL).Execute()
if err != nil {
return err
}
} }
} }
// revert the pragma and reload the schema return nil
_, revertErr := txApp.DB().NewQuery("PRAGMA writable_schema = RESET").Execute()
return revertErr
}) })
} }

View File

@ -140,10 +140,9 @@ func (app *BaseApp) delete(ctx context.Context, model Model, isForAuxDB bool) er
}) })
}) })
if deleteErr != nil { if deleteErr != nil {
hookErr := app.OnModelAfterDeleteError().Trigger(&ModelErrorEvent{ errEvent := &ModelErrorEvent{ModelEvent: *event, Error: deleteErr}
ModelEvent: *event, errEvent.App = app
Error: deleteErr, hookErr := app.OnModelAfterDeleteError().Trigger(errEvent)
})
if hookErr != nil { if hookErr != nil {
return errors.Join(deleteErr, hookErr) return errors.Join(deleteErr, hookErr)
} }
@ -332,10 +331,9 @@ func (app *BaseApp) create(ctx context.Context, model Model, withValidations boo
if saveErr != nil { if saveErr != nil {
event.Model.MarkAsNew() // reset "new" state event.Model.MarkAsNew() // reset "new" state
hookErr := app.OnModelAfterCreateError().Trigger(&ModelErrorEvent{ errEvent := &ModelErrorEvent{ModelEvent: *event, Error: saveErr}
ModelEvent: *event, errEvent.App = app
Error: saveErr, hookErr := app.OnModelAfterCreateError().Trigger(errEvent)
})
if hookErr != nil { if hookErr != nil {
return errors.Join(saveErr, hookErr) return errors.Join(saveErr, hookErr)
} }
@ -417,10 +415,9 @@ func (app *BaseApp) update(ctx context.Context, model Model, withValidations boo
}) })
}) })
if saveErr != nil { if saveErr != nil {
hookErr := app.OnModelAfterUpdateError().Trigger(&ModelErrorEvent{ errEvent := &ModelErrorEvent{ModelEvent: *event, Error: saveErr}
ModelEvent: *event, errEvent.App = app
Error: saveErr, hookErr := app.OnModelAfterUpdateError().Trigger(errEvent)
})
if hookErr != nil { if hookErr != nil {
return errors.Join(saveErr, hookErr) return errors.Join(saveErr, hookErr)
} }