diff --git a/pkg/services/folder/folderimpl/sqlstore.go b/pkg/services/folder/folderimpl/sqlstore.go index 4c82a7a2dd9..1a6351ab41a 100644 --- a/pkg/services/folder/folderimpl/sqlstore.go +++ b/pkg/services/folder/folderimpl/sqlstore.go @@ -2,14 +2,16 @@ package folderimpl import ( "context" + "errors" "strings" "time" + "github.com/VividCortex/mysqlerr" + "github.com/go-sql-driver/mysql" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" - "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -183,9 +185,6 @@ func (ss *sqlStore) Get(ctx context.Context, q folder.GetFolderQuery) (*folder.F func (ss *sqlStore) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) { var folders []*folder.Folder - if ss.db.GetDBType() == migrator.MySQL { - return ss.getParentsMySQL(ctx, q) - } recQuery := ` WITH RECURSIVE RecQry AS ( @@ -195,14 +194,23 @@ func (ss *sqlStore) GetParents(ctx context.Context, q folder.GetParentsQuery) ([ SELECT * FROM RecQry; ` - err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { + if err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { err := sess.SQL(recQuery, q.UID, q.OrgID).Find(&folders) if err != nil { return folder.ErrDatabaseError.Errorf("failed to get folder parents: %w", err) } return nil - }) - return util.Reverse(folders[1:]), err + }); err != nil { + var driverErr *mysql.MySQLError + if errors.As(err, &driverErr) { + if driverErr.Number == mysqlerr.ER_PARSE_ERROR { + ss.log.Debug("recursive CTE subquery is not supported; it fallbacks to the iterative implementation") + return ss.getParentsMySQL(ctx, q) + } + } + return nil, err + } + return util.Reverse(folders[1:]), nil } func (ss *sqlStore) GetChildren(ctx context.Context, q folder.GetTreeQuery) ([]*folder.Folder, error) { @@ -228,20 +236,32 @@ func (ss *sqlStore) GetChildren(ctx context.Context, q folder.GetTreeQuery) ([]* return folders, err } -func (ss *sqlStore) getParentsMySQL(ctx context.Context, cmd folder.GetParentsQuery) ([]*folder.Folder, error) { - var foldrs []*folder.Folder - var foldr *folder.Folder - err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { - uid := cmd.UID - for uid != folder.GeneralFolderUID && len(foldrs) < 8 { - err := sess.Where("uid=? AND org_id=>", uid, cmd.OrgID).Find(foldr) +func (ss *sqlStore) getParentsMySQL(ctx context.Context, cmd folder.GetParentsQuery) (folders []*folder.Folder, err error) { + err = ss.db.WithDbSession(ctx, func(sess *db.Session) error { + uid := "" + ok, err := sess.SQL("SELECT parent_uid FROM folder WHERE org_id=? AND uid=?", cmd.OrgID, cmd.UID).Get(&uid) + if err != nil { + return err + } + if !ok { + return folder.ErrFolderNotFound + } + for { + f := &folder.Folder{} + ok, err := sess.SQL("SELECT * FROM folder WHERE org_id=? AND uid=?", cmd.OrgID, uid).Get(f) if err != nil { - return folder.ErrDatabaseError.Errorf("failed to get folder parents: %w", err) + return err + } + if !ok { + break + } + folders = append(folders, f) + uid = f.ParentUID + if len(folders) > folder.MaxNestedFolderDepth { + return folder.ErrFolderTooDeep } - foldrs = append(foldrs, foldr) - uid = foldr.ParentUID } return nil }) - return foldrs, err + return folders, err } diff --git a/pkg/services/folder/model.go b/pkg/services/folder/model.go index ee5392db273..b8d6ed7a76a 100644 --- a/pkg/services/folder/model.go +++ b/pkg/services/folder/model.go @@ -10,6 +10,7 @@ var ErrMaximumDepthReached = errutil.NewBase(errutil.StatusBadRequest, "folder.m var ErrBadRequest = errutil.NewBase(errutil.StatusBadRequest, "folder.bad-request") var ErrDatabaseError = errutil.NewBase(errutil.StatusInternal, "folder.database-error") var ErrInternal = errutil.NewBase(errutil.StatusInternal, "folder.internal") +var ErrFolderTooDeep = errutil.NewBase(errutil.StatusInternal, "folder.too-deep") const ( GeneralFolderUID = "general"