-
Notifications
You must be signed in to change notification settings - Fork 5.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
privilege/privileges: sort user records in privilege cache #7211
Changes from 2 commits
8a9f4a1
4883b7b
5c7a96e
a85f386
851f8c8
d9aa8f4
c2991c1
e1137c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ package privileges | |
|
||
import ( | ||
"fmt" | ||
"sort" | ||
"strings" | ||
"sync/atomic" | ||
"time" | ||
|
@@ -47,7 +48,8 @@ func computePrivMask(privs []mysql.PrivilegeType) mysql.PrivilegeType { | |
return mask | ||
} | ||
|
||
type userRecord struct { | ||
// UserRecord is used to represent a user record in privilege cache. | ||
type UserRecord struct { | ||
Host string // max length 60, primary key | ||
User string // max length 16, primary key | ||
Password string // max length 41 | ||
|
@@ -103,7 +105,7 @@ type columnsPrivRecord struct { | |
|
||
// MySQLPrivilege is the in-memory cache of mysql privilege tables. | ||
type MySQLPrivilege struct { | ||
User []userRecord | ||
User []UserRecord | ||
DB []dbRecord | ||
TablesPriv []tablesPrivRecord | ||
ColumnsPriv []columnsPrivRecord | ||
|
@@ -154,7 +156,87 @@ func noSuchTable(err error) bool { | |
|
||
// LoadUserTable loads the mysql.user table from database. | ||
func (p *MySQLPrivilege) LoadUserTable(ctx sessionctx.Context) error { | ||
return p.loadTable(ctx, "select Host,User,Password,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Process_priv,Grant_priv,References_priv,Alter_priv,Show_db_priv,Super_priv,Execute_priv,Index_priv,Create_user_priv,Trigger_priv from mysql.user order by host, user;", p.decodeUserTableRow) | ||
err := p.loadTable(ctx, "select Host,User,Password,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Process_priv,Grant_priv,References_priv,Alter_priv,Show_db_priv,Super_priv,Execute_priv,Index_priv,Create_user_priv,Trigger_priv from mysql.user;", p.decodeUserTableRow) | ||
if err != nil { | ||
return errors.Trace(err) | ||
} | ||
// See https://dev.mysql.com/doc/refman/8.0/en/connection-access.html | ||
// When multiple matches are possible, the server must determine which of them to use. It resolves this issue as follows: | ||
// 1. Whenever the server reads the user table into memory, it sorts the rows. | ||
// 2. When a client attempts to connect, the server looks through the rows in sorted order. | ||
// 3. The server uses the first row that matches the client host name and user name. | ||
// The server uses sorting rules that order rows with the most-specific Host values first. | ||
p.SortUserTable() | ||
return nil | ||
} | ||
|
||
type sortedUserRecord []UserRecord | ||
|
||
func (s sortedUserRecord) Len() int { | ||
return len([]UserRecord(s)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
func (s sortedUserRecord) Less(i, j int) bool { | ||
x := ([]UserRecord(s))[i] | ||
y := ([]UserRecord(s))[j] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
works too~ |
||
|
||
// Compare two item by user's host first. | ||
c1 := compareHost(x.Host, y.Host) | ||
if c1 < 0 { | ||
return true | ||
} | ||
if c1 > 0 { | ||
return false | ||
} | ||
|
||
// Then, compare item by user's name value. | ||
return x.User < y.User | ||
} | ||
|
||
// compareHost compares two host string using some special rules, return value 1, 0, -1 means > = <. | ||
func compareHost(x, y string) int { | ||
// The more-specific, the smaller it is. | ||
// The pattern '%' means “any host” and is least specific. | ||
if y == `%` { | ||
if x == `%` { | ||
return 0 | ||
} | ||
return -1 | ||
} | ||
|
||
// The empty string '' also means “any host” but sorts after '%'. | ||
if y == "" { | ||
if x == "" { | ||
return 0 | ||
} | ||
return -1 | ||
} | ||
|
||
if strings.HasSuffix(y, `%`) { | ||
if !strings.HasSuffix(x, `%`) { | ||
return -1 | ||
} | ||
return 0 | ||
} | ||
|
||
// For other case, the order is nondeterministic. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a check by length. |
||
switch x < y { | ||
case true: | ||
return -1 | ||
case false: | ||
return 1 | ||
} | ||
return 0 | ||
} | ||
|
||
func (s sortedUserRecord) Swap(i, j int) { | ||
s1 := []UserRecord(s) | ||
s1[i], s1[j] = s1[j], s1[i] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto~ |
||
} | ||
|
||
// SortUserTable sorts p.User in the MySQLPrivilege struct. | ||
func (p MySQLPrivilege) SortUserTable() { | ||
sort.Sort(sortedUserRecord(p.User)) | ||
} | ||
|
||
// LoadDBTable loads the mysql.db table from database. | ||
|
@@ -206,7 +288,7 @@ func (p *MySQLPrivilege) loadTable(sctx sessionctx.Context, sql string, | |
} | ||
|
||
func (p *MySQLPrivilege) decodeUserTableRow(row chunk.Row, fs []*ast.ResultField) error { | ||
var value userRecord | ||
var value UserRecord | ||
for i, f := range fs { | ||
switch { | ||
case f.ColumnAsName.L == "user": | ||
|
@@ -326,7 +408,7 @@ func decodeSetToPrivilege(s types.Set) mysql.PrivilegeType { | |
return ret | ||
} | ||
|
||
func (record *userRecord) match(user, host string) bool { | ||
func (record *UserRecord) match(user, host string) bool { | ||
return record.User == user && patternMatch(host, record.patChars, record.patTypes) | ||
} | ||
|
||
|
@@ -355,7 +437,7 @@ func patternMatch(str string, patChars, patTypes []byte) bool { | |
} | ||
|
||
// connectionVerification verifies the connection have access to TiDB server. | ||
func (p *MySQLPrivilege) connectionVerification(user, host string) *userRecord { | ||
func (p *MySQLPrivilege) connectionVerification(user, host string) *UserRecord { | ||
for i := 0; i < len(p.User); i++ { | ||
record := &p.User[i] | ||
if record.match(user, host) { | ||
|
@@ -365,7 +447,7 @@ func (p *MySQLPrivilege) connectionVerification(user, host string) *userRecord { | |
return nil | ||
} | ||
|
||
func (p *MySQLPrivilege) matchUser(user, host string) *userRecord { | ||
func (p *MySQLPrivilege) matchUser(user, host string) *UserRecord { | ||
for i := 0; i < len(p.User); i++ { | ||
record := &p.User[i] | ||
if record.match(user, host) { | ||
|
@@ -557,7 +639,7 @@ func (p *MySQLPrivilege) UserPrivilegesTable() [][]types.Datum { | |
return rows | ||
} | ||
|
||
func appendUserPrivilegesTableRow(rows [][]types.Datum, user userRecord) [][]types.Datum { | ||
func appendUserPrivilegesTableRow(rows [][]types.Datum, user UserRecord) [][]types.Datum { | ||
var isGrantable string | ||
if user.Privileges&mysql.GrantPriv > 0 { | ||
isGrantable = "YES" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe no need to make this public if want to test it.
maybe try this trick...https://github.com/lysu/tidb/blob/87ce884b2e0dc679554cb666b05f3347b3c72957/types/export_test.go#L17
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cache_test.go use a different package, so this trick can't work.