added migration to normalize the system collection and field ids

This commit is contained in:
Gani Georgiev 2024-11-04 19:03:33 +02:00
parent b3d88349d7
commit 9e70c77736
9 changed files with 177 additions and 9 deletions

View File

@ -1,3 +1,15 @@
## v0.23.0-rc10
> [!CAUTION]
> **This is a prerelease intended for test and experimental purposes only!**
- Restore the CRC32 checksum autogeneration for the collection/field ids in order to maintain deterministic default identifier value and minimize conflicts between custom migrations and full collections snapshots.
_There is a system migration that will attempt to normalize existing system collections ids, but if you already migrated to v0.23.0-rc and have generated a full collections snapshot migration, you have to delete it and regenerate a new one._
- Change the behavior of the default generated collections snapshot migration to act as "extend" instead of "replace" to prevent accidental data deletion.
_I think this would be rare but if you want the old behaviour you can edit the generated snapshot file and replace the second argument (`deleteMissing`) of `App.ImportCollection/App.ImportCollectionsByMarshaledJSON` from `false` to `true`._
## v0.23.0-rc9
> [!CAUTION]

View File

@ -2,6 +2,7 @@ package core_test
import (
"context"
"database/sql"
"log/slog"
"os"
"testing"
@ -9,6 +10,7 @@ import (
_ "unsafe"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tests"
"github.com/pocketbase/pocketbase/tools/logger"
@ -351,6 +353,12 @@ func TestBaseAppRefreshSettingsLoggerMinLevelEnabled(t *testing.T) {
t.Fatal(err)
}
// silence query logs
app.DB().(*dbx.DB).ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {}
app.DB().(*dbx.DB).QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {}
app.NonconcurrentDB().(*dbx.DB).ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {}
app.NonconcurrentDB().(*dbx.DB).QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {}
handler, ok := app.Logger().Handler().(*logger.BatchHandler)
if !ok {
t.Fatalf("Expected BatchHandler, got %v", app.Logger().Handler())

View File

@ -488,12 +488,13 @@ func (m *Collection) UnmarshalJSON(b []byte) error {
minimal := &struct {
Type string `json:"type"`
Name string `json:"name"`
Id string `json:"id"`
}{}
if err := json.Unmarshal(b, minimal); err != nil {
return err
}
blank := NewCollection(minimal.Type, minimal.Name)
blank := NewCollection(minimal.Type, minimal.Name, minimal.Id)
*m = *blank
}

View File

@ -2,10 +2,13 @@ package core
import (
"context"
"database/sql"
"log/slog"
"os"
"testing"
"time"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/tools/list"
"github.com/pocketbase/pocketbase/tools/logger"
)
@ -51,6 +54,12 @@ func TestBaseAppLoggerLevelDevPrint(t *testing.T) {
t.Fatal(err)
}
// silence query logs
app.DB().(*dbx.DB).ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {}
app.DB().(*dbx.DB).QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {}
app.NonconcurrentDB().(*dbx.DB).ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {}
app.NonconcurrentDB().(*dbx.DB).QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {}
app.Settings().Logs.MinLevel = testLogLevel
if err := app.Save(app.Settings()); err != nil {
t.Fatal(err)

View File

@ -118,7 +118,7 @@ func createParamsTable(txApp core.App) error {
}
func createMFAsCollection(txApp core.App) error {
col := core.NewBaseCollection(core.CollectionNameMFAs, "_pbc"+core.CollectionNameMFAs)
col := core.NewBaseCollection(core.CollectionNameMFAs)
col.System = true
ownerRule := "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId"
@ -157,7 +157,7 @@ func createMFAsCollection(txApp core.App) error {
}
func createOTPsCollection(txApp core.App) error {
col := core.NewBaseCollection(core.CollectionNameOTPs, "_pbc"+core.CollectionNameOTPs)
col := core.NewBaseCollection(core.CollectionNameOTPs)
col.System = true
ownerRule := "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId"
@ -198,7 +198,7 @@ func createOTPsCollection(txApp core.App) error {
}
func createAuthOriginsCollection(txApp core.App) error {
col := core.NewBaseCollection(core.CollectionNameAuthOrigins, "_pbc"+core.CollectionNameAuthOrigins)
col := core.NewBaseCollection(core.CollectionNameAuthOrigins)
col.System = true
ownerRule := "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId"
@ -238,7 +238,7 @@ func createAuthOriginsCollection(txApp core.App) error {
}
func createExternalAuthsCollection(txApp core.App) error {
col := core.NewBaseCollection(core.CollectionNameExternalAuths, "_pbc"+core.CollectionNameExternalAuths)
col := core.NewBaseCollection(core.CollectionNameExternalAuths)
col.System = true
ownerRule := "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId"
@ -284,7 +284,7 @@ func createExternalAuthsCollection(txApp core.App) error {
}
func createSuperusersCollection(txApp core.App) error {
superusers := core.NewAuthCollection(core.CollectionNameSuperusers, "_pbc"+core.CollectionNameSuperusers)
superusers := core.NewAuthCollection(core.CollectionNameSuperusers)
superusers.System = true
superusers.Fields.Add(&core.EmailField{
Name: "email",

View File

@ -0,0 +1,138 @@
package migrations
import (
"hash/crc32"
"strconv"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
)
// note: this migration will be deleted in future version
func collectionIdChecksum(c *core.Collection) string {
return "pbc_" + strconv.Itoa(int(crc32.ChecksumIEEE([]byte(c.Type+c.Name))))
}
func fieldIdChecksum(f core.Field) string {
return f.Type() + strconv.Itoa(int(crc32.ChecksumIEEE([]byte(f.GetName()))))
}
// normalize system collection and field ids
func init() {
core.SystemMigrations.Register(func(txApp core.App) error {
collections := []*core.Collection{}
err := txApp.CollectionQuery().
AndWhere(dbx.In(
"name",
core.CollectionNameMFAs,
core.CollectionNameOTPs,
core.CollectionNameExternalAuths,
core.CollectionNameAuthOrigins,
core.CollectionNameSuperusers,
)).
All(&collections)
if err != nil {
return err
}
for _, c := range collections {
var needUpdate bool
references, err := txApp.FindCollectionReferences(c, c.Id)
if err != nil {
return err
}
authOrigins, err := txApp.FindAllAuthOriginsByCollection(c)
if err != nil {
return err
}
mfas, err := txApp.FindAllMFAsByCollection(c)
if err != nil {
return err
}
otps, err := txApp.FindAllOTPsByCollection(c)
if err != nil {
return err
}
originalId := c.Id
// normalize collection id
if checksum := collectionIdChecksum(c); c.Id != checksum {
c.Id = checksum
needUpdate = true
}
// normalize system fields
for _, f := range c.Fields {
if !f.GetSystem() {
continue
}
if checksum := fieldIdChecksum(f); f.GetId() != checksum {
f.SetId(checksum)
needUpdate = true
}
}
if !needUpdate {
continue
}
rawExport, err := c.DBExport(txApp)
if err != nil {
return err
}
_, err = txApp.DB().Update("_collections", rawExport, dbx.HashExp{"id": originalId}).Execute()
if err != nil {
return err
}
// update collection references
for refCollection, fields := range references {
for _, f := range fields {
relationField, ok := f.(*core.RelationField)
if !ok || relationField.CollectionId == originalId {
continue
}
relationField.CollectionId = c.Id
}
if err = txApp.Save(refCollection); err != nil {
return err
}
}
// update mfas references
for _, item := range mfas {
item.SetCollectionRef(c.Id)
if err = txApp.Save(item); err != nil {
return err
}
}
// update otps references
for _, item := range otps {
item.SetCollectionRef(c.Id)
if err = txApp.Save(item); err != nil {
return err
}
}
// update authOrigins references
for _, item := range authOrigins {
item.SetCollectionRef(c.Id)
if err = txApp.Save(item); err != nil {
return err
}
}
}
return nil
}, nil)
}

View File

@ -62,7 +62,7 @@ func (p *plugin) jsSnapshotTemplate(collections []*core.Collection) (string, err
const template = jsTypesDirective + `migrate((app) => {
const snapshot = %s;
return app.importCollections(snapshot, true);
return app.importCollections(snapshot, false);
}, (app) => {
return null;
})
@ -348,7 +348,7 @@ func init() {
m.Register(func(app core.App) error {
jsonData := ` + "`%s`" + `
return app.ImportCollectionsByMarshaledJSON([]byte(jsonData), true)
return app.ImportCollectionsByMarshaledJSON([]byte(jsonData), false)
}, func(app core.App) error {
return nil
})

Binary file not shown.

View File

@ -10,4 +10,4 @@ PB_DOCS_URL = "https://pocketbase.io/docs/"
PB_JS_SDK_URL = "https://github.com/pocketbase/js-sdk"
PB_DART_SDK_URL = "https://github.com/pocketbase/dart-sdk"
PB_RELEASES = "https://github.com/pocketbase/pocketbase/releases"
PB_VERSION = "v0.23.0-rc9"
PB_VERSION = "v0.23.0-rc10"