Skip to content

Commit f3ea6d7

Browse files
authored
executor: fix case-insensitive grant and revoke on schema names (#68456)
ref #66867, close #68406
1 parent 037f05b commit f3ea6d7

4 files changed

Lines changed: 286 additions & 147 deletions

File tree

pkg/executor/grant.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ func (e *GrantExec) Next(ctx context.Context, _ *chunk.Chunk) error {
8181
if len(dbName) == 0 {
8282
dbName = e.Ctx().GetSessionVars().CurrentDB
8383
}
84+
if e.Level.Level == ast.GrantLevelDB {
85+
dbName = getTargetSchemaName(e.Ctx(), dbName, e.is)
86+
}
8487

8588
// For table & column level, check whether table exists and privilege is valid
8689
if e.Level.Level == ast.GrantLevelTable {
@@ -493,7 +496,7 @@ func (e *GrantExec) grantLevelPriv(ctx context.Context, priv *ast.PrivElem, user
493496
return e.grantDBLevel(priv, user, internalSession)
494497
case ast.GrantLevelTable:
495498
if len(priv.Cols) == 0 {
496-
return e.grantTableLevel(priv, user, internalSession)
499+
return e.grantTableLevel(ctx, priv, user, internalSession)
497500
}
498501
return e.grantColumnLevel(ctx, priv, user, internalSession)
499502
default:
@@ -547,6 +550,7 @@ func (e *GrantExec) grantDBLevel(priv *ast.PrivElem, user *ast.UserSpec, interna
547550
if len(dbName) == 0 {
548551
dbName = e.Ctx().GetSessionVars().CurrentDB
549552
}
553+
dbName = getTargetSchemaName(e.Ctx(), dbName, e.is)
550554

551555
sql := new(strings.Builder)
552556
sqlescape.MustFormatSQL(sql, "UPDATE %n.%n SET ", mysql.SystemDB, mysql.DBTable)
@@ -562,23 +566,28 @@ func (e *GrantExec) grantDBLevel(priv *ast.PrivElem, user *ast.UserSpec, interna
562566
}
563567

564568
// grantTableLevel manipulates mysql.tables_priv table.
565-
func (e *GrantExec) grantTableLevel(priv *ast.PrivElem, user *ast.UserSpec, internalSession sessionctx.Context) error {
566-
dbName := e.Level.DBName
567-
if len(dbName) == 0 {
568-
dbName = e.Ctx().GetSessionVars().CurrentDB
569+
func (e *GrantExec) grantTableLevel(ctx context.Context, priv *ast.PrivElem, user *ast.UserSpec, internalSession sessionctx.Context) error {
570+
dbName, tbl, err := getTargetSchemaAndTable(ctx, e.Ctx(), e.Level.DBName, e.Level.TableName, e.is)
571+
// Earlier validation may allow GRANT on a missing table, for example CREATE/ALL.
572+
// Keep that behavior here: ignore ErrTableNotExists and continue with the input table name.
573+
if err != nil && !terror.ErrorEqual(err, infoschema.ErrTableNotExists) {
574+
return err
569575
}
570576
tblName := e.Level.TableName
577+
if tbl != nil {
578+
tblName = tbl.Meta().Name.O
579+
}
571580

572581
sql := new(strings.Builder)
573582
sqlescape.MustFormatSQL(sql, "UPDATE %n.%n SET ", mysql.SystemDB, mysql.TablePrivTable)
574-
err := composeTablePrivUpdateForGrant(internalSession, sql, priv.Priv, user.User.Username, user.User.Hostname, dbName, tblName)
583+
err = composeTablePrivUpdateForGrant(internalSession, sql, priv.Priv, user.User.Username, user.User.Hostname, dbName, tblName)
575584
if err != nil {
576585
return err
577586
}
578587
sqlescape.MustFormatSQL(sql, " WHERE User=%? AND Host=%? AND DB=%? AND Table_name=%?", user.User.Username, user.User.Hostname, dbName, tblName)
579588

580-
ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnPrivilege)
581-
_, err = internalSession.GetSQLExecutor().ExecuteInternal(ctx, sql.String())
589+
internalCtx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnPrivilege)
590+
_, err = internalSession.GetSQLExecutor().ExecuteInternal(internalCtx, sql.String())
582591
return err
583592
}
584593

@@ -783,6 +792,22 @@ func getColumnPriv(sctx sessionctx.Context, name string, host string, db string,
783792
return cPriv, nil
784793
}
785794

795+
// getTargetSchemaName returns the real schema name from infoschema when it exists.
796+
// Keep the original input for non-existent schemas so GRANT/REVOKE on them preserves
797+
// the current behavior.
798+
func getTargetSchemaName(sctx sessionctx.Context, dbName string, is infoschema.InfoSchema) string {
799+
if len(dbName) == 0 {
800+
dbName = sctx.GetSessionVars().CurrentDB
801+
}
802+
if len(dbName) == 0 {
803+
return ""
804+
}
805+
if db, ok := is.SchemaByName(ast.NewCIStr(dbName)); ok {
806+
return db.Name.O
807+
}
808+
return dbName
809+
}
810+
786811
// getTargetSchemaAndTable finds the schema and table by dbName and tableName.
787812
func getTargetSchemaAndTable(ctx context.Context, sctx sessionctx.Context, dbName, tableName string, is infoschema.InfoSchema) (string, table.Table, error) {
788813
if len(dbName) == 0 {
@@ -791,6 +816,7 @@ func getTargetSchemaAndTable(ctx context.Context, sctx sessionctx.Context, dbNam
791816
return "", nil, errors.New("miss DB name for grant privilege")
792817
}
793818
}
819+
dbName = getTargetSchemaName(sctx, dbName, is)
794820
name := ast.NewCIStr(tableName)
795821
tbl, err := is.TableByName(ctx, ast.NewCIStr(dbName), name)
796822
if terror.ErrorEqual(err, infoschema.ErrTableNotExists) {

pkg/executor/grant_test.go

Lines changed: 138 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import (
1919
"strings"
2020
"testing"
2121

22+
"github.com/pingcap/tidb/pkg/config"
23+
"github.com/pingcap/tidb/pkg/parser/auth"
2224
"github.com/pingcap/tidb/pkg/parser/mysql"
2325
"github.com/pingcap/tidb/pkg/parser/terror"
2426
"github.com/pingcap/tidb/pkg/testkit"
@@ -27,6 +29,34 @@ import (
2729
"github.com/stretchr/testify/require"
2830
)
2931

32+
func newCollationDisabledBootstrapTestKit(t *testing.T) *testkit.TestKit {
33+
t.Helper()
34+
35+
originCfg := *config.GetGlobalConfig()
36+
t.Cleanup(func() {
37+
config.StoreGlobalConfig(&originCfg)
38+
})
39+
config.UpdateGlobal(func(conf *config.Config) {
40+
conf.NewCollationsEnabledOnFirstBootstrap = false
41+
})
42+
43+
oldNewCollationEnabled := collate.NewCollationEnabled()
44+
t.Cleanup(func() {
45+
collate.SetNewCollationEnabledForTest(oldNewCollationEnabled)
46+
})
47+
// Keep the in-process flag aligned before bootstrap; bootstrap will reload
48+
// and enforce the persisted cluster value again.
49+
collate.SetNewCollationEnabledForTest(false)
50+
51+
store := testkit.CreateMockStore(t)
52+
tk := testkit.NewTestKit(t, store)
53+
require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil, nil))
54+
tk.MustQuery(`SELECT VARIABLE_VALUE FROM mysql.tidb WHERE VARIABLE_NAME='new_collation_enabled'`).
55+
Check(testkit.Rows("False"))
56+
require.False(t, collate.NewCollationEnabled())
57+
return tk
58+
}
59+
3060
func TestGrantGlobal(t *testing.T) {
3161
store := testkit.CreateMockStore(t)
3262

@@ -105,12 +135,7 @@ func TestGrantDBScope(t *testing.T) {
105135
}
106136

107137
func TestGrantDBScopeCaseInsensitiveWithNewCollationDisabled(t *testing.T) {
108-
oldNewCollationEnabled := collate.NewCollationEnabled()
109-
collate.SetNewCollationEnabledForTest(false)
110-
defer collate.SetNewCollationEnabledForTest(oldNewCollationEnabled)
111-
112-
store := testkit.CreateMockStore(t)
113-
tk := testkit.NewTestKit(t, store)
138+
tk := newCollationDisabledBootstrapTestKit(t)
114139

115140
tk.MustExec(`DROP USER IF EXISTS 'testDBCase'@'%'`)
116141
tk.MustExec(`CREATE USER 'testDBCase'@'%' IDENTIFIED BY '123'`)
@@ -165,65 +190,122 @@ func TestGrantTableScope(t *testing.T) {
165190
}
166191

167192
func TestGrantTableScopeCaseInsensitiveWithNewCollationDisabled(t *testing.T) {
168-
oldNewCollationEnabled := collate.NewCollationEnabled()
169-
collate.SetNewCollationEnabledForTest(false)
170-
defer collate.SetNewCollationEnabledForTest(oldNewCollationEnabled)
193+
t.Run("table-name", func(t *testing.T) {
194+
tk := newCollationDisabledBootstrapTestKit(t)
171195

172-
store := testkit.CreateMockStore(t)
173-
tk := testkit.NewTestKit(t, store)
196+
tk.MustExec(`DROP USER IF EXISTS 'testTblCase'@'%'`)
197+
tk.MustExec(`CREATE USER 'testTblCase'@'%' IDENTIFIED BY '123'`)
198+
tk.MustExec(`DROP TABLE IF EXISTS test.issue66867_grant`)
199+
tk.MustExec(`CREATE TABLE test.issue66867_grant(c1 int)`)
200+
201+
tk.MustExec(`GRANT SELECT ON test.issue66867_grant TO 'testTblCase'@'%'`)
202+
tk.MustExec(`GRANT SELECT ON test.ISSUE66867_GRANT TO 'testTblCase'@'%'`)
203+
tk.MustQuery(`SELECT Table_name, Table_priv FROM mysql.tables_priv WHERE User='testTblCase' AND Host='%' AND DB='test' ORDER BY Table_name`).
204+
Check(testkit.Rows("issue66867_grant Select"))
205+
})
206+
207+
t.Run("schema-name", func(t *testing.T) {
208+
tk := newCollationDisabledBootstrapTestKit(t)
209+
210+
tk.MustExec(`DROP USER IF EXISTS 'testTblCaseSchema'@'%'`)
211+
tk.MustExec(`CREATE USER 'testTblCaseSchema'@'%'`)
212+
tk.MustExec(`DROP TABLE IF EXISTS test.issue68406_grant`)
213+
tk.MustExec(`CREATE TABLE test.issue68406_grant(id int, name int)`)
214+
215+
tk.MustExec(`GRANT SELECT ON TEST.issue68406_grant TO 'testTblCaseSchema'@'%'`)
216+
tk.MustExec(`GRANT SELECT,INSERT,UPDATE,DELETE ON test.issue68406_grant TO 'testTblCaseSchema'@'%'`)
217+
tk.MustQuery(`SELECT DB, Table_name, Table_priv FROM mysql.tables_priv WHERE User='testTblCaseSchema' AND Host='%' ORDER BY DB, Table_name`).
218+
Check(testkit.Rows("test issue68406_grant Select,Insert,Update,Delete"))
219+
tk.MustQuery(`SHOW GRANTS FOR 'testTblCaseSchema'@'%'`).
220+
Check(testkit.Rows(
221+
"GRANT USAGE ON *.* TO 'testTblCaseSchema'@'%'",
222+
"GRANT SELECT,INSERT,UPDATE,DELETE ON `test`.`issue68406_grant` TO 'testTblCaseSchema'@'%'",
223+
))
224+
225+
tkUser := testkit.NewTestKit(t, tk.Session().GetStore())
226+
require.NoError(t, tkUser.Session().Auth(&auth.UserIdentity{Username: "testTblCaseSchema", Hostname: "%"}, nil, nil, nil))
227+
tkUser.MustExec(`INSERT INTO test.issue68406_grant VALUES (1, 2)`)
228+
tkUser.MustQuery(`SELECT * FROM test.issue68406_grant`).Check(testkit.Rows("1 2"))
229+
})
230+
231+
t.Run("missing-table fallback", func(t *testing.T) {
232+
tk := newCollationDisabledBootstrapTestKit(t)
174233

175-
tk.MustExec(`DROP USER IF EXISTS 'testTblCase'@'%'`)
176-
tk.MustExec(`CREATE USER 'testTblCase'@'%' IDENTIFIED BY '123'`)
177-
tk.MustExec(`DROP TABLE IF EXISTS test.issue66867_grant`)
178-
tk.MustExec(`CREATE TABLE test.issue66867_grant(c1 int)`)
234+
tk.MustExec(`DROP USER IF EXISTS 'testTblCaseMissing'@'%'`)
235+
tk.MustExec(`CREATE USER 'testTblCaseMissing'@'%'`)
236+
tk.MustExec(`DROP TABLE IF EXISTS test.missing_tbl`)
179237

180-
tk.MustExec(`GRANT SELECT ON test.issue66867_grant TO 'testTblCase'@'%'`)
181-
tk.MustExec(`GRANT SELECT ON test.ISSUE66867_GRANT TO 'testTblCase'@'%'`)
182-
tk.MustQuery(`SELECT Table_name, Table_priv FROM mysql.tables_priv WHERE User='testTblCase' AND Host='%' AND DB='test' ORDER BY Table_name`).
183-
Check(testkit.Rows("issue66867_grant Select"))
238+
tk.MustExec(`GRANT CREATE ON TEST.missing_tbl TO 'testTblCaseMissing'@'%'`)
239+
tk.MustQuery(`SELECT DB, Table_name, Table_priv FROM mysql.tables_priv WHERE User='testTblCaseMissing' AND Host='%'`).
240+
Check(testkit.Rows("test missing_tbl Create"))
241+
tk.MustQuery(`SHOW GRANTS FOR 'testTblCaseMissing'@'%'`).
242+
Check(testkit.Rows(
243+
"GRANT USAGE ON *.* TO 'testTblCaseMissing'@'%'",
244+
"GRANT CREATE ON `test`.`missing_tbl` TO 'testTblCaseMissing'@'%'",
245+
))
246+
})
184247
}
185248

186249
func TestGrantColumnScope(t *testing.T) {
187-
store := testkit.CreateMockStore(t)
250+
t.Run("basic", func(t *testing.T) {
251+
store := testkit.CreateMockStore(t)
188252

189-
tk := testkit.NewTestKit(t, store)
190-
// Create a new user.
191-
createUserSQL := `CREATE USER 'testCol'@'localhost' IDENTIFIED BY '123';`
192-
tk.MustExec(createUserSQL)
193-
tk.MustExec(`CREATE TABLE test.test3(c1 int, c2 int);`)
253+
tk := testkit.NewTestKit(t, store)
254+
// Create a new user.
255+
createUserSQL := `CREATE USER 'testCol'@'localhost' IDENTIFIED BY '123';`
256+
tk.MustExec(createUserSQL)
257+
tk.MustExec(`CREATE TABLE test.test3(c1 int, c2 int);`)
194258

195-
// Make sure all the column privs for new user is empty.
196-
tk.MustQuery(`SELECT * FROM mysql.Columns_priv WHERE User="testCol" and host="localhost" and db="test" and Table_name="test3" and Column_name="c1"`).Check(testkit.Rows())
197-
tk.MustQuery(`SELECT * FROM mysql.Columns_priv WHERE User="testCol" and host="localhost" and db="test" and Table_name="test3" and Column_name="c2"`).Check(testkit.Rows())
259+
// Make sure all the column privs for new user is empty.
260+
tk.MustQuery(`SELECT * FROM mysql.Columns_priv WHERE User="testCol" and host="localhost" and db="test" and Table_name="test3" and Column_name="c1"`).Check(testkit.Rows())
261+
tk.MustQuery(`SELECT * FROM mysql.Columns_priv WHERE User="testCol" and host="localhost" and db="test" and Table_name="test3" and Column_name="c2"`).Check(testkit.Rows())
198262

199-
// Grant each priv to the user.
200-
for _, v := range mysql.AllColumnPrivs {
201-
sql := fmt.Sprintf("GRANT %s(c1) ON test.test3 TO 'testCol'@'localhost';", mysql.Priv2Str[v])
202-
tk.MustExec(sql)
203-
rows := tk.MustQuery(`SELECT Column_priv FROM mysql.Columns_priv WHERE User="testCol" and host="localhost" and db="test" and Table_name="test3" and Column_name="c1";`).Rows()
204-
require.Len(t, rows, 1)
205-
row := rows[0]
206-
require.Len(t, rows, 1)
207-
p := fmt.Sprintf("%v", row[0])
208-
require.Greater(t, strings.Index(p, mysql.Priv2SetStr[v]), -1)
209-
}
263+
// Grant each priv to the user.
264+
for _, v := range mysql.AllColumnPrivs {
265+
sql := fmt.Sprintf("GRANT %s(c1) ON test.test3 TO 'testCol'@'localhost';", mysql.Priv2Str[v])
266+
tk.MustExec(sql)
267+
rows := tk.MustQuery(`SELECT Column_priv FROM mysql.Columns_priv WHERE User="testCol" and host="localhost" and db="test" and Table_name="test3" and Column_name="c1";`).Rows()
268+
require.Len(t, rows, 1)
269+
row := rows[0]
270+
require.Len(t, rows, 1)
271+
p := fmt.Sprintf("%v", row[0])
272+
require.Greater(t, strings.Index(p, mysql.Priv2SetStr[v]), -1)
273+
}
210274

211-
// Create a new user.
212-
createUserSQL = `CREATE USER 'testCol1'@'localhost' IDENTIFIED BY '123';`
213-
tk.MustExec(createUserSQL)
214-
tk.MustExec("USE test;")
215-
// Grant all column scope privs.
216-
tk.MustExec("GRANT ALL(c2) ON test3 TO 'testCol1'@'localhost';")
217-
// Make sure all the column privs for granted user are in the Column_priv set.
218-
for _, v := range mysql.AllColumnPrivs {
219-
rows := tk.MustQuery(`SELECT Column_priv FROM mysql.Columns_priv WHERE User="testCol1" and host="localhost" and db="test" and Table_name="test3" and Column_name="c2";`).Rows()
220-
require.Len(t, rows, 1)
221-
row := rows[0]
222-
require.Len(t, rows, 1)
223-
p := fmt.Sprintf("%v", row[0])
224-
require.Greater(t, strings.Index(p, mysql.Priv2SetStr[v]), -1)
225-
}
275+
// Create a new user.
276+
createUserSQL = `CREATE USER 'testCol1'@'localhost' IDENTIFIED BY '123';`
277+
tk.MustExec(createUserSQL)
278+
tk.MustExec("USE test;")
279+
// Grant all column scope privs.
280+
tk.MustExec("GRANT ALL(c2) ON test3 TO 'testCol1'@'localhost';")
281+
// Make sure all the column privs for granted user are in the Column_priv set.
282+
for _, v := range mysql.AllColumnPrivs {
283+
rows := tk.MustQuery(`SELECT Column_priv FROM mysql.Columns_priv WHERE User="testCol1" and host="localhost" and db="test" and Table_name="test3" and Column_name="c2";`).Rows()
284+
require.Len(t, rows, 1)
285+
row := rows[0]
286+
require.Len(t, rows, 1)
287+
p := fmt.Sprintf("%v", row[0])
288+
require.Greater(t, strings.Index(p, mysql.Priv2SetStr[v]), -1)
289+
}
290+
291+
tk.MustGetErrMsg("GRANT SUPER(c2) ON test3 TO 'testCol1'@'localhost';",
292+
"[executor:1221]Incorrect usage of COLUMN GRANT and NON-COLUMN PRIVILEGES")
293+
})
294+
295+
t.Run("case-insensitive schema-name with new collation disabled", func(t *testing.T) {
296+
tk := newCollationDisabledBootstrapTestKit(t)
297+
298+
tk.MustExec(`DROP USER IF EXISTS 'testColCaseSchema'@'%'`)
299+
tk.MustExec(`CREATE USER 'testColCaseSchema'@'%' IDENTIFIED BY '123'`)
300+
tk.MustExec(`DROP TABLE IF EXISTS test.issue68406_grant_col`)
301+
tk.MustExec(`CREATE TABLE test.issue68406_grant_col(id int, name int)`)
226302

227-
tk.MustGetErrMsg("GRANT SUPER(c2) ON test3 TO 'testCol1'@'localhost';",
228-
"[executor:1221]Incorrect usage of COLUMN GRANT and NON-COLUMN PRIVILEGES")
303+
tk.MustExec(`GRANT SELECT(id) ON TEST.issue68406_grant_col TO 'testColCaseSchema'@'%'`)
304+
tk.MustExec(`GRANT INSERT(id), UPDATE(name) ON test.issue68406_grant_col TO 'testColCaseSchema'@'%'`)
305+
tk.MustQuery(`SELECT DB, Table_name, Column_name, Column_priv FROM mysql.columns_priv WHERE User='testColCaseSchema' AND Host='%' ORDER BY DB, Table_name, Column_name`).
306+
Check(testkit.Rows(
307+
"test issue68406_grant_col id Select,Insert",
308+
"test issue68406_grant_col name Update",
309+
))
310+
})
229311
}

pkg/executor/revoke.go

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -157,44 +157,38 @@ func (e *RevokeExec) revokeOneUser(ctx context.Context, internalSession sessionc
157157
if len(dbName) == 0 {
158158
dbName = e.Ctx().GetSessionVars().CurrentDB
159159
}
160+
requestedDBName := dbName
160161

161162
// If there is no privilege entry in corresponding table, insert a new one.
162163
// DB scope: mysql.DB
163164
// Table scope: mysql.Tables_priv
164165
// Column scope: mysql.Columns_priv
165166
switch e.Level.Level {
166167
case ast.GrantLevelDB:
168+
dbName = getTargetSchemaName(e.Ctx(), dbName, e.is)
167169
ok, err := dbUserExists(internalSession, user, host, dbName)
168170
if err != nil {
169171
return err
170172
}
171173
if !ok {
172-
return errors.Errorf("There is no such grant defined for user '%s' on host '%s' on database %s", user, host, dbName)
174+
return errors.Errorf("There is no such grant defined for user '%s' on host '%s' on database %s", user, host, requestedDBName)
173175
}
174176
case ast.GrantLevelTable:
175-
tblName := e.Level.TableName
176-
if len(dbName) > 0 {
177-
// Normalize db/table names before checking mysql.tables_priv.
178-
// Different input case should still map to the same object.
179-
dbNameCI := ast.NewCIStr(dbName)
180-
if db, succ := e.is.SchemaByName(dbNameCI); succ {
181-
dbName = db.Name.O
182-
}
183-
tbl, err := e.is.TableByName(ctx, dbNameCI, ast.NewCIStr(e.Level.TableName))
184-
if err != nil && !terror.ErrorEqual(err, infoschema.ErrTableNotExists) {
185-
return err
186-
}
187-
if err == nil {
188-
// Use the real table name from schema metadata.
189-
tblName = tbl.Meta().Name.O
190-
}
177+
requestedTblName := e.Level.TableName
178+
dbName, tbl, err := getTargetSchemaAndTable(ctx, e.Ctx(), dbName, e.Level.TableName, e.is)
179+
if err != nil && !terror.ErrorEqual(err, infoschema.ErrTableNotExists) {
180+
return err
181+
}
182+
tblName := requestedTblName
183+
if tbl != nil {
184+
tblName = tbl.Meta().Name.O
191185
}
192186
ok, err := tableUserExists(internalSession, user, host, dbName, tblName)
193187
if err != nil {
194188
return err
195189
}
196190
if !ok {
197-
return errors.Errorf("There is no such grant defined for user '%s' on host '%s' on table %s.%s", user, host, dbName, e.Level.TableName)
191+
return errors.Errorf("There is no such grant defined for user '%s' on host '%s' on table %s.%s", user, host, requestedDBName, requestedTblName)
198192
}
199193
}
200194

@@ -264,6 +258,7 @@ func (e *RevokeExec) revokeDBPriv(internalSession sessionctx.Context, priv *ast.
264258
if len(dbName) == 0 {
265259
dbName = e.Ctx().GetSessionVars().CurrentDB
266260
}
261+
dbName = getTargetSchemaName(e.Ctx(), dbName, e.is)
267262

268263
sql := new(strings.Builder)
269264
sqlescape.MustFormatSQL(sql, "UPDATE %n.%n SET ", mysql.SystemDB, mysql.DBTable)

0 commit comments

Comments
 (0)