diff --git a/pkg/infra/db/db.go b/pkg/infra/db/db.go index 856a1755f29..559609aaff8 100644 --- a/pkg/infra/db/db.go +++ b/pkg/infra/db/db.go @@ -20,6 +20,9 @@ type DB interface { GetSqlxSession() *session.SessionDB InTransaction(ctx context.Context, fn func(ctx context.Context) error) error Quote(value string) string + // RecursiveQueriesAreSupported runs a dummy recursive query and it returns true + // if the query runs successfully or false if it fails with mysqlerr.ER_PARSE_ERROR error or any other error + RecursiveQueriesAreSupported() (bool, error) } type Session = sqlstore.DBSession diff --git a/pkg/infra/db/dbtest/dbtest.go b/pkg/infra/db/dbtest/dbtest.go index 730d08bfe91..d3c7afeb684 100644 --- a/pkg/infra/db/dbtest/dbtest.go +++ b/pkg/infra/db/dbtest/dbtest.go @@ -51,6 +51,10 @@ func (f *FakeDB) Quote(value string) string { return "" } +func (f *FakeDB) RecursiveQueriesAreSupported() (bool, error) { + return false, nil +} + // TODO: service-specific methods not yet split out ; to be removed func (f *FakeDB) UpdateTempUserWithEmailSent(ctx context.Context, cmd *tempuser.UpdateTempUserWithEmailSentCommand) error { return f.ExpectedError diff --git a/pkg/services/folder/folderimpl/sqlstore.go b/pkg/services/folder/folderimpl/sqlstore.go index 4e7767d745d..c05cb02280b 100644 --- a/pkg/services/folder/folderimpl/sqlstore.go +++ b/pkg/services/folder/folderimpl/sqlstore.go @@ -2,13 +2,9 @@ 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/infra/slugify" @@ -196,22 +192,25 @@ func (ss *sqlStore) GetParents(ctx context.Context, q folder.GetParentsQuery) ([ SELECT * FROM RecQry; ` - 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 - }); 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) - } - } + recursiveQueriesAreSupported, err := ss.db.RecursiveQueriesAreSupported() + if err != nil { return nil, err } + switch recursiveQueriesAreSupported { + case true: + 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 + }); err != nil { + return nil, err + } + default: + ss.log.Debug("recursive CTE subquery is not supported; it fallbacks to the iterative implementation") + return ss.getParentsMySQL(ctx, q) + } if len(folders) < 1 { // the query is expected to return at least the same folder diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 4d4bd3dc50c..909289a9bf4 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -2,6 +2,7 @@ package sqlstore import ( "context" + "errors" "fmt" "net/url" "os" @@ -11,6 +12,7 @@ import ( "sync" "time" + "github.com/VividCortex/mysqlerr" "github.com/dlmiddlecote/sqlstats" "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" @@ -44,14 +46,15 @@ type SQLStore struct { sqlxsession *session.SessionDB CacheService *localcache.CacheService - bus bus.Bus - dbCfg DatabaseConfig - engine *xorm.Engine - log log.Logger - Dialect migrator.Dialect - skipEnsureDefaultOrgAndUser bool - migrations registry.DatabaseMigrator - tracer tracing.Tracer + bus bus.Bus + dbCfg DatabaseConfig + engine *xorm.Engine + log log.Logger + Dialect migrator.Dialect + skipEnsureDefaultOrgAndUser bool + migrations registry.DatabaseMigrator + tracer tracing.Tracer + recursiveQueriesAreSupported *bool } func ProvideService(cfg *setting.Cfg, cacheService *localcache.CacheService, migrations registry.DatabaseMigrator, bus bus.Bus, tracer tracing.Tracer) (*SQLStore, error) { @@ -476,6 +479,43 @@ func (ss *SQLStore) readConfig() error { return nil } +func (ss *SQLStore) RecursiveQueriesAreSupported() (bool, error) { + if ss.recursiveQueriesAreSupported != nil { + return *ss.recursiveQueriesAreSupported, nil + } + recursiveQueriesAreSupported := func() (bool, error) { + var result []int + if err := ss.WithDbSession(context.Background(), func(sess *DBSession) error { + recQry := `WITH RECURSIVE cte (n) AS + ( + SELECT 1 + UNION ALL + SELECT n + 1 FROM cte WHERE n < 2 + ) + SELECT * FROM cte; + ` + err := sess.SQL(recQry).Find(&result) + return err + }); err != nil { + var driverErr *mysql.MySQLError + if errors.As(err, &driverErr) { + if driverErr.Number == mysqlerr.ER_PARSE_ERROR { + return false, nil + } + } + return false, err + } + return true, nil + } + + areSupported, err := recursiveQueriesAreSupported() + if err != nil { + return false, err + } + ss.recursiveQueriesAreSupported = &areSupported + return *ss.recursiveQueriesAreSupported, nil +} + // ITestDB is an interface of arguments for testing db type ITestDB interface { Helper()