diff --git a/pkg/services/sqlstore/migrator/dialect.go b/pkg/services/sqlstore/migrator/dialect.go index c5a0128450d..f68a1b6672d 100644 --- a/pkg/services/sqlstore/migrator/dialect.go +++ b/pkg/services/sqlstore/migrator/dialect.go @@ -27,7 +27,10 @@ type Dialect interface { ShowCreateNull() bool SQLType(col *Column) string SupportEngine() bool + // Deprecated. This doesn't work correctly for all databases. LikeStr() string + // LikeOperator returns SQL snippet and query parameter for case-insensitive LIKE operation, with optional wildcards (%) before/after the pattern. + LikeOperator(column string, wildcardBefore bool, pattern string, wildcardAfter bool) (string, string) Default(col *Column) string // BooleanValue can be used as an argument in SELECT or INSERT statements. For constructing // raw SQL queries, please use BooleanStr instead. @@ -153,6 +156,17 @@ func (b *BaseDialect) LikeStr() string { return "LIKE" } +func (b *BaseDialect) LikeOperator(column string, wildcardBefore bool, pattern string, wildcardAfter bool) (string, string) { + param := pattern + if wildcardBefore { + param = "%" + param + } + if wildcardAfter { + param = param + "%" + } + return fmt.Sprintf("%s LIKE ?", column), param +} + func (b *BaseDialect) OrStr() string { return "OR" } diff --git a/pkg/services/sqlstore/migrator/postgres_dialect.go b/pkg/services/sqlstore/migrator/postgres_dialect.go index 9d343cc2f8f..47a0f2707b5 100644 --- a/pkg/services/sqlstore/migrator/postgres_dialect.go +++ b/pkg/services/sqlstore/migrator/postgres_dialect.go @@ -35,6 +35,17 @@ func (db *PostgresDialect) LikeStr() string { return "ILIKE" } +func (db *PostgresDialect) LikeOperator(column string, wildcardBefore bool, pattern string, wildcardAfter bool) (string, string) { + param := pattern + if wildcardBefore { + param = "%" + param + } + if wildcardAfter { + param = param + "%" + } + return fmt.Sprintf("%s ILIKE ?", column), param +} + func (db *PostgresDialect) AutoIncrStr() string { return "" } diff --git a/pkg/services/sqlstore/migrator/spanner_dialect.go b/pkg/services/sqlstore/migrator/spanner_dialect.go index b4c83bdc07a..db1a1b4bc9b 100644 --- a/pkg/services/sqlstore/migrator/spanner_dialect.go +++ b/pkg/services/sqlstore/migrator/spanner_dialect.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "strings" "time" "cloud.google.com/go/spanner" @@ -44,6 +45,18 @@ func NewSpannerDialect() Dialect { func (s *SpannerDialect) AutoIncrStr() string { return s.d.AutoIncrStr() } func (s *SpannerDialect) Quote(name string) string { return s.d.Quote(name) } func (s *SpannerDialect) SupportEngine() bool { return s.d.SupportEngine() } + +func (s *SpannerDialect) LikeOperator(column string, wildcardBefore bool, pattern string, wildcardAfter bool) (string, string) { + param := strings.ToLower(pattern) + if wildcardBefore { + param = "%" + param + } + if wildcardAfter { + param = param + "%" + } + return fmt.Sprintf("LOWER(%s) LIKE ?", column), param +} + func (s *SpannerDialect) IndexCheckSQL(tableName, indexName string) (string, []any) { return s.d.IndexCheckSql(tableName, indexName) } diff --git a/pkg/services/user/userimpl/store.go b/pkg/services/user/userimpl/store.go index a5216b04e7a..e8d65d5f820 100644 --- a/pkg/services/user/userimpl/store.go +++ b/pkg/services/user/userimpl/store.go @@ -2,15 +2,11 @@ package userimpl import ( "context" - "errors" "fmt" "strconv" "strings" "time" - "github.com/go-sql-driver/mysql" - "github.com/mattn/go-sqlite3" - "github.com/grafana/grafana/pkg/events" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" @@ -76,8 +72,9 @@ func (ss *sqlStore) Insert(ctx context.Context, cmd *user.User) (int64, error) { }) return nil }) + if err != nil { - return 0, handleSQLError(err) + return 0, handleSQLError(ss.dialect, err) } return cmd.ID, nil @@ -482,8 +479,6 @@ func (ss *sqlStore) Search(ctx context.Context, query *user.SearchUsersQuery) (* Users: make([]*user.UserSearchHitDTO, 0), } err := ss.db.WithDbSession(ctx, func(dbSess *db.Session) error { - queryWithWildcards := "%" + query.Query + "%" - whereConditions := make([]string, 0) whereParams := make([]any, 0) sess := dbSess.Table("user").Alias("u") @@ -512,8 +507,11 @@ func (ss *sqlStore) Search(ctx context.Context, query *user.SearchUsersQuery) (* whereParams = append(whereParams, acFilter.Args...) if query.Query != "" { - whereConditions = append(whereConditions, "(email "+ss.dialect.LikeStr()+" ? OR name "+ss.dialect.LikeStr()+" ? OR login "+ss.dialect.LikeStr()+" ?)") - whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards) + emailSql, emailArg := ss.dialect.LikeOperator("email", true, query.Query, true) + nameSql, nameArg := ss.dialect.LikeOperator("name", true, query.Query, true) + loginSql, loginArg := ss.dialect.LikeOperator("login", true, query.Query, true) + whereConditions = append(whereConditions, fmt.Sprintf("(%s OR %s OR %s)", emailSql, nameSql, loginSql)) + whereParams = append(whereParams, emailArg, nameArg, loginArg) } if query.IsDisabled != nil { @@ -606,29 +604,9 @@ func setOptional[T any](v *T, add func(v T)) { } } -func handleSQLError(err error) error { - if isUniqueConstraintError(err) { +func handleSQLError(dialect migrator.Dialect, err error) error { + if dialect.IsUniqueConstraintViolation(err) { return user.ErrUserAlreadyExists } return err } - -func isUniqueConstraintError(err error) bool { - // check mysql error code - var me *mysql.MySQLError - if errors.As(err, &me) && me.Number == 1062 { - return true - } - - // for postgres we check the error message - if strings.Contains(err.Error(), "duplicate key value") { - return true - } - - var se sqlite3.Error - if errors.As(err, &se) && se.ExtendedCode == sqlite3.ErrConstraintUnique { - return true - } - - return false -}