diff --git a/CHANGELOG.md b/CHANGELOG.md index 245ef7e2..5fcd3399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - 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 diff --git a/core/collection_model.go b/core/collection_model.go index 2abb2017..c0cd57c0 100644 --- a/core/collection_model.go +++ b/core/collection_model.go @@ -265,7 +265,7 @@ func (app *BaseApp) registerCollectionHooks() { // --- onErrorReloadCachedCollections := func(ce *CollectionErrorEvent) error { 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() diff --git a/core/collection_record_table_sync.go b/core/collection_record_table_sync.go index 30aa675d..7eda8a82 100644 --- a/core/collection_record_table_sync.go +++ b/core/collection_record_table_sync.go @@ -158,15 +158,6 @@ func normalizeSingleVsMultipleFieldChanges(app App, newCollection *Collection, o } 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 { // 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 @@ -186,17 +177,42 @@ func normalizeSingleVsMultipleFieldChanges(app App, newCollection *Collection, o continue // no change } - // update the column definition by: - // 1. inserting a new column with the new 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 + // ------------------------------------------------------- + // update the field column definition // ------------------------------------------------------- - originalName := newField.GetName() - tempName := "_" + newField.GetName() + security.PseudorandomString(5) + // temporary drop all views to prevent reference errors during the columns renaming + // (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 { return err } @@ -220,12 +236,12 @@ func normalizeSingleVsMultipleFieldChanges(app App, newCollection *Collection, o END )`, newCollection.Name, - tempName, - originalName, - originalName, - originalName, - originalName, originalName, + oldTempName, + oldTempName, + oldTempName, + oldTempName, + oldTempName, )) } else { // multiple -> single (keep only the last element) @@ -247,35 +263,37 @@ func normalizeSingleVsMultipleFieldChanges(app App, newCollection *Collection, o END )`, newCollection.Name, - tempName, - originalName, - originalName, - originalName, - originalName, originalName, + oldTempName, + oldTempName, + oldTempName, + oldTempName, + oldTempName, )) } // copy the normalized values - if _, err := copyQuery.Execute(); err != nil { + _, err = copyQuery.Execute() + if err != nil { return err } // 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 } - // rename the new column back to the original - if _, err := txApp.DB().RenameColumn(newCollection.Name, tempName, originalName).Execute(); err != nil { - return err + // restore views + for _, view := range views { + _, err = txApp.DB().NewQuery(view.SQL).Execute() + if err != nil { + return err + } } } - // revert the pragma and reload the schema - _, revertErr := txApp.DB().NewQuery("PRAGMA writable_schema = RESET").Execute() - - return revertErr + return nil }) } diff --git a/core/db.go b/core/db.go index 12df9b5f..48b4b773 100644 --- a/core/db.go +++ b/core/db.go @@ -140,10 +140,9 @@ func (app *BaseApp) delete(ctx context.Context, model Model, isForAuxDB bool) er }) }) if deleteErr != nil { - hookErr := app.OnModelAfterDeleteError().Trigger(&ModelErrorEvent{ - ModelEvent: *event, - Error: deleteErr, - }) + errEvent := &ModelErrorEvent{ModelEvent: *event, Error: deleteErr} + errEvent.App = app + hookErr := app.OnModelAfterDeleteError().Trigger(errEvent) if hookErr != nil { return errors.Join(deleteErr, hookErr) } @@ -332,10 +331,9 @@ func (app *BaseApp) create(ctx context.Context, model Model, withValidations boo if saveErr != nil { event.Model.MarkAsNew() // reset "new" state - hookErr := app.OnModelAfterCreateError().Trigger(&ModelErrorEvent{ - ModelEvent: *event, - Error: saveErr, - }) + errEvent := &ModelErrorEvent{ModelEvent: *event, Error: saveErr} + errEvent.App = app + hookErr := app.OnModelAfterCreateError().Trigger(errEvent) if hookErr != nil { return errors.Join(saveErr, hookErr) } @@ -417,10 +415,9 @@ func (app *BaseApp) update(ctx context.Context, model Model, withValidations boo }) }) if saveErr != nil { - hookErr := app.OnModelAfterUpdateError().Trigger(&ModelErrorEvent{ - ModelEvent: *event, - Error: saveErr, - }) + errEvent := &ModelErrorEvent{ModelEvent: *event, Error: saveErr} + errEvent.App = app + hookErr := app.OnModelAfterUpdateError().Trigger(errEvent) if hookErr != nil { return errors.Join(saveErr, hookErr) }