Chore: Add function for detecting if the SQL driver supported CTEs (#64441)

* Add interface method for detecting if the SQL driver supported CTEs

* Update nested folder store to call RecursiveQueriesAreSupported()
This commit is contained in:
Sofia Papagiannaki 2023-03-20 10:27:08 +02:00 committed by GitHub
parent fd6e97d52d
commit 4ee0be6fdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 72 additions and 26 deletions

View File

@ -20,6 +20,9 @@ type DB interface {
GetSqlxSession() *session.SessionDB GetSqlxSession() *session.SessionDB
InTransaction(ctx context.Context, fn func(ctx context.Context) error) error InTransaction(ctx context.Context, fn func(ctx context.Context) error) error
Quote(value string) string 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 type Session = sqlstore.DBSession

View File

@ -51,6 +51,10 @@ func (f *FakeDB) Quote(value string) string {
return "" return ""
} }
func (f *FakeDB) RecursiveQueriesAreSupported() (bool, error) {
return false, nil
}
// TODO: service-specific methods not yet split out ; to be removed // TODO: service-specific methods not yet split out ; to be removed
func (f *FakeDB) UpdateTempUserWithEmailSent(ctx context.Context, cmd *tempuser.UpdateTempUserWithEmailSentCommand) error { func (f *FakeDB) UpdateTempUserWithEmailSent(ctx context.Context, cmd *tempuser.UpdateTempUserWithEmailSentCommand) error {
return f.ExpectedError return f.ExpectedError

View File

@ -2,13 +2,9 @@ package folderimpl
import ( import (
"context" "context"
"errors"
"strings" "strings"
"time" "time"
"github.com/VividCortex/mysqlerr"
"github.com/go-sql-driver/mysql"
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/slugify" "github.com/grafana/grafana/pkg/infra/slugify"
@ -196,6 +192,12 @@ func (ss *sqlStore) GetParents(ctx context.Context, q folder.GetParentsQuery) ([
SELECT * FROM RecQry; SELECT * FROM RecQry;
` `
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 { if err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {
err := sess.SQL(recQuery, q.UID, q.OrgID).Find(&folders) err := sess.SQL(recQuery, q.UID, q.OrgID).Find(&folders)
if err != nil { if err != nil {
@ -203,15 +205,12 @@ func (ss *sqlStore) GetParents(ctx context.Context, q folder.GetParentsQuery) ([
} }
return nil return nil
}); err != nil { }); err != nil {
var driverErr *mysql.MySQLError return nil, err
if errors.As(err, &driverErr) { }
if driverErr.Number == mysqlerr.ER_PARSE_ERROR { default:
ss.log.Debug("recursive CTE subquery is not supported; it fallbacks to the iterative implementation") ss.log.Debug("recursive CTE subquery is not supported; it fallbacks to the iterative implementation")
return ss.getParentsMySQL(ctx, q) return ss.getParentsMySQL(ctx, q)
} }
}
return nil, err
}
if len(folders) < 1 { if len(folders) < 1 {
// the query is expected to return at least the same folder // the query is expected to return at least the same folder

View File

@ -2,6 +2,7 @@ package sqlstore
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
@ -11,6 +12,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/VividCortex/mysqlerr"
"github.com/dlmiddlecote/sqlstats" "github.com/dlmiddlecote/sqlstats"
"github.com/go-sql-driver/mysql" "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
@ -52,6 +54,7 @@ type SQLStore struct {
skipEnsureDefaultOrgAndUser bool skipEnsureDefaultOrgAndUser bool
migrations registry.DatabaseMigrator migrations registry.DatabaseMigrator
tracer tracing.Tracer tracer tracing.Tracer
recursiveQueriesAreSupported *bool
} }
func ProvideService(cfg *setting.Cfg, cacheService *localcache.CacheService, migrations registry.DatabaseMigrator, bus bus.Bus, tracer tracing.Tracer) (*SQLStore, error) { 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 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 // ITestDB is an interface of arguments for testing db
type ITestDB interface { type ITestDB interface {
Helper() Helper()