Skip to content
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

WIP: Proof of concept support for GET_LOCK #33361

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions errno/errcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@ const (
ErrInvalidFieldSize = 3013
ErrInvalidArgumentForLogarithm = 3020
ErrAggregateOrderNonAggQuery = 3029
ErrUserLockDeadlock = 3058
ErrIncorrectType = 3064
ErrFieldInOrderNotSelect = 3065
ErrAggregateInOrderNotSelect = 3066
Expand Down
1 change: 1 addition & 0 deletions errno/errname.go
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,7 @@ var MySQLErrName = map[uint16]*mysql.ErrMessage{
ErrLockExpire: mysql.Message("TTL manager has timed out, pessimistic locks may expire, please commit or rollback this transaction", nil),
ErrTableOptionUnionUnsupported: mysql.Message("CREATE/ALTER table with union option is not supported", nil),
ErrTableOptionInsertMethodUnsupported: mysql.Message("CREATE/ALTER table with insert method option is not supported", nil),
ErrUserLockDeadlock: mysql.Message("Deadlock found when trying to get user-level lock; try rolling back transaction/releasing locks and restarting lock acquisition.", nil),

ErrBRIEBackupFailed: mysql.Message("Backup failed: %s", nil),
ErrBRIERestoreFailed: mysql.Message("Restore failed: %s", nil),
Expand Down
6 changes: 3 additions & 3 deletions executor/infoschema_cluster_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ func (s *infosSchemaClusterTableSuite) TestTableStorageStats() {
"test 2",
))
rows := tk.MustQuery("select TABLE_NAME from information_schema.TABLE_STORAGE_STATS where TABLE_SCHEMA = 'mysql';").Rows()
s.Require().Len(rows, 30)
s.Require().Len(rows, 31)

// More tests about the privileges.
tk.MustExec("create user 'testuser'@'localhost'")
Expand All @@ -345,12 +345,12 @@ func (s *infosSchemaClusterTableSuite) TestTableStorageStats() {
Hostname: "localhost",
}, nil, nil))

tk.MustQuery("select count(1) from information_schema.TABLE_STORAGE_STATS where TABLE_SCHEMA = 'mysql'").Check(testkit.Rows("30"))
tk.MustQuery("select count(1) from information_schema.TABLE_STORAGE_STATS where TABLE_SCHEMA = 'mysql'").Check(testkit.Rows("31"))

s.Require().True(tk.Session().Auth(&auth.UserIdentity{
Username: "testuser3",
Hostname: "localhost",
}, nil, nil))

tk.MustQuery("select count(1) from information_schema.TABLE_STORAGE_STATS where TABLE_SCHEMA = 'mysql'").Check(testkit.Rows("30"))
tk.MustQuery("select count(1) from information_schema.TABLE_STORAGE_STATS where TABLE_SCHEMA = 'mysql'").Check(testkit.Rows("31"))
}
14 changes: 1 addition & 13 deletions executor/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1292,28 +1292,16 @@ func TestEnableNoopFunctionsVar(t *testing.T) {
tk.MustQuery(`select @@global.tidb_enable_noop_functions;`).Check(testkit.Rows("OFF"))
tk.MustQuery(`select @@tidb_enable_noop_functions;`).Check(testkit.Rows("OFF"))

err := tk.ExecToErr(`select get_lock('lock1', 2);`)
require.True(t, terror.ErrorEqual(err, expression.ErrFunctionsNoopImpl), fmt.Sprintf("err %v", err))
err = tk.ExecToErr(`select release_lock('lock1');`)
require.True(t, terror.ErrorEqual(err, expression.ErrFunctionsNoopImpl), fmt.Sprintf("err %v", err))

// change session var to 1
tk.MustExec(`set tidb_enable_noop_functions=1;`)
tk.MustQuery(`select @@tidb_enable_noop_functions;`).Check(testkit.Rows("ON"))
tk.MustQuery(`select @@global.tidb_enable_noop_functions;`).Check(testkit.Rows("OFF"))
tk.MustQuery(`select get_lock("lock", 10)`).Check(testkit.Rows("1"))
tk.MustQuery(`select release_lock("lock")`).Check(testkit.Rows("1"))

// restore to 0
tk.MustExec(`set tidb_enable_noop_functions=0;`)
tk.MustQuery(`select @@tidb_enable_noop_functions;`).Check(testkit.Rows("OFF"))
tk.MustQuery(`select @@global.tidb_enable_noop_functions;`).Check(testkit.Rows("OFF"))

err = tk.ExecToErr(`select get_lock('lock2', 10);`)
require.True(t, terror.ErrorEqual(err, expression.ErrFunctionsNoopImpl), fmt.Sprintf("err %v", err))
err = tk.ExecToErr(`select release_lock('lock2');`)
require.True(t, terror.ErrorEqual(err, expression.ErrFunctionsNoopImpl), fmt.Sprintf("err %v", err))

// set test
require.Error(t, tk.ExecToErr(`set tidb_enable_noop_functions='abc'`))
require.Error(t, tk.ExecToErr(`set tidb_enable_noop_functions=11`))
Expand All @@ -1324,7 +1312,7 @@ func TestEnableNoopFunctionsVar(t *testing.T) {
tk.MustExec(`set tidb_enable_noop_functions=0;`)
tk.MustQuery(`select @@tidb_enable_noop_functions;`).Check(testkit.Rows("OFF"))

err = tk.ExecToErr("SET SESSION tx_read_only = 1")
err := tk.ExecToErr("SET SESSION tx_read_only = 1")
require.True(t, terror.ErrorEqual(err, variable.ErrFunctionsNoopImpl), fmt.Sprintf("err %v", err))

tk.MustExec("SET SESSION tx_read_only = 0")
Expand Down
2 changes: 0 additions & 2 deletions expression/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -783,8 +783,6 @@ var funcs = map[string]functionClass{
ast.BinToUUID: &binToUUIDFunctionClass{baseFunctionClass{ast.BinToUUID, 1, 2}},
ast.TiDBShard: &tidbShardFunctionClass{baseFunctionClass{ast.TiDBShard, 1, 1}},

// get_lock() and release_lock() are parsed but do nothing.
// It is used for preventing error in Ruby's activerecord migrations.
ast.GetLock: &lockFunctionClass{baseFunctionClass{ast.GetLock, 2, 2}},
ast.ReleaseLock: &releaseLockFunctionClass{baseFunctionClass{ast.ReleaseLock, 1, 1}},

Expand Down
88 changes: 80 additions & 8 deletions expression/builtin_miscellaneous.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import (

"github.com/google/uuid"
"github.com/pingcap/tidb/parser/mysql"
"github.com/pingcap/tidb/parser/terror"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/types/json"
"github.com/pingcap/tidb/util/chunk"
Expand Down Expand Up @@ -65,6 +67,7 @@ var (
_ builtinFunc = &builtinSleepSig{}
_ builtinFunc = &builtinLockSig{}
_ builtinFunc = &builtinReleaseLockSig{}
_ builtinFunc = &builtinReleaseAllLocksSig{}
_ builtinFunc = &builtinDecimalAnyValueSig{}
_ builtinFunc = &builtinDurationAnyValueSig{}
_ builtinFunc = &builtinIntAnyValueSig{}
Expand Down Expand Up @@ -186,9 +189,43 @@ func (b *builtinLockSig) Clone() builtinFunc {

// evalInt evals a builtinLockSig.
// See https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
// The lock function will do nothing.
// Warning: get_lock() function is parsed but ignored.
func (b *builtinLockSig) evalInt(_ chunk.Row) (int64, bool, error) {
func (b *builtinLockSig) evalInt(row chunk.Row) (int64, bool, error) {
lockName, isNull, err := b.args[0].EvalString(b.ctx, row)
if err != nil {
return 0, isNull, err
}
timeout, isNullTimeout, err := b.args[1].EvalInt(b.ctx, row)
if err != nil {
return 0, isNull, err
}
// Validate that neither argument is NULL and there is a lockName
if isNull || isNullTimeout || lockName == "" {
return 0, false, errIncorrectArgs.GenWithStackByArgs("get_lock")
}
// A timeout less than zero is expected to be treated as unlimited.
// Because of our implementation being based on pessimistic locks,
// We can't have a timeout greater than innodb_lock_wait_timeout.
maxTimeout := int64(variable.GetSysVar("innodb_lock_wait_timeout").MaxValue)
if timeout < 0 || timeout > maxTimeout {
timeout = maxTimeout
}
// Lock names are case insensitive. Because we can't rely on collations
// being enabled on the internal table, we have to lower it.
lockName = strings.ToLower(lockName)
err = b.ctx.GetAdvisoryLock(lockName, timeout)
if err != nil {
switch err.(*terror.Error).Code() {
case mysql.ErrLockWaitTimeout:
return 0, false, nil // Another user has the lock
case mysql.ErrLockDeadlock:
// TODO: currently this code is not reachable because each Advisory Lock
// Uses a separate session. Deadlock detection does not work across
// independent sessions.
return 0, false, errUserLockDeadlock
default:
return 0, false, err
}
}
return 1, false, nil
}

Expand Down Expand Up @@ -221,10 +258,19 @@ func (b *builtinReleaseLockSig) Clone() builtinFunc {

// evalInt evals a builtinReleaseLockSig.
// See https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_release-lock
// The release lock function will do nothing.
// Warning: release_lock() function is parsed but ignored.
func (b *builtinReleaseLockSig) evalInt(_ chunk.Row) (int64, bool, error) {
return 1, false, nil
func (b *builtinReleaseLockSig) evalInt(row chunk.Row) (int64, bool, error) {
lockName, isNull, err := b.args[0].EvalString(b.ctx, row)
if err != nil {
return 0, isNull, err
}
// Lock names are case insensitive. Because we can't rely on collations
// being enabled on the internal table, we have to lower it.
lockName = strings.ToLower(lockName)
released := int64(0)
if b.ctx.ReleaseAdvisoryLock(lockName) {
released = 1
}
return released, false, nil
}

type anyValueFunctionClass struct {
Expand Down Expand Up @@ -1062,7 +1108,33 @@ type releaseAllLocksFunctionClass struct {
}

func (c *releaseAllLocksFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
return nil, errFunctionNotExists.GenWithStackByArgs("FUNCTION", "RELEASE_ALL_LOCKS")
if err := c.verifyArgs(args); err != nil {
return nil, err
}
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETInt)
if err != nil {
return nil, err
}
sig := &builtinReleaseAllLocksSig{bf}
bf.tp.Flen = 1
return sig, nil
}

type builtinReleaseAllLocksSig struct {
baseBuiltinFunc
}

func (b *builtinReleaseAllLocksSig) Clone() builtinFunc {
newSig := &builtinReleaseAllLocksSig{}
newSig.cloneFrom(&b.baseBuiltinFunc)
return newSig
}

// evalInt evals a builtinReleaseAllLocksSig.
// See https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_release-all-locks
func (b *builtinReleaseAllLocksSig) evalInt(_ chunk.Row) (int64, bool, error) {
count := b.ctx.ReleaseAllAdvisoryLocks()
return int64(count), false, nil
}

type uuidFunctionClass struct {
Expand Down
30 changes: 2 additions & 28 deletions expression/builtin_miscellaneous_vec.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,20 +228,7 @@ func (b *builtinNameConstDurationSig) vecEvalDuration(input *chunk.Chunk, result
}

func (b *builtinLockSig) vectorized() bool {
return true
}

// See https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
// The lock function will do nothing.
// Warning: get_lock() function is parsed but ignored.
func (b *builtinLockSig) vecEvalInt(input *chunk.Chunk, result *chunk.Column) error {
n := input.NumRows()
result.ResizeInt64(n, false)
i64s := result.Int64s()
for i := range i64s {
i64s[i] = 1
}
return nil
return false
}

func (b *builtinDurationAnyValueSig) vectorized() bool {
Expand Down Expand Up @@ -634,20 +621,7 @@ func (b *builtinNameConstRealSig) vecEvalReal(input *chunk.Chunk, result *chunk.
}

func (b *builtinReleaseLockSig) vectorized() bool {
return true
}

// See https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_release-lock
// The release lock function will do nothing.
// Warning: release_lock() function is parsed but ignored.
func (b *builtinReleaseLockSig) vecEvalInt(input *chunk.Chunk, result *chunk.Column) error {
n := input.NumRows()
result.ResizeInt64(n, false)
i64s := result.Int64s()
for i := range i64s {
i64s[i] = 1
}
return nil
return false
}

func (b *builtinVitessHashSig) vectorized() bool {
Expand Down
4 changes: 2 additions & 2 deletions expression/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,14 @@ func TestIsNullFunc(t *testing.T) {
func TestLock(t *testing.T) {
ctx := createContext(t)
lock := funcs[ast.GetLock]
f, err := lock.getFunction(ctx, datumsToConstants(types.MakeDatums(nil, 1)))
f, err := lock.getFunction(ctx, datumsToConstants(types.MakeDatums("mylock", 1)))
require.NoError(t, err)
v, err := evalBuiltinFunc(f, chunk.Row{})
require.NoError(t, err)
require.Equal(t, int64(1), v.GetInt64())

releaseLock := funcs[ast.ReleaseLock]
f, err = releaseLock.getFunction(ctx, datumsToConstants(types.MakeDatums(1)))
f, err = releaseLock.getFunction(ctx, datumsToConstants(types.MakeDatums("mylock")))
require.NoError(t, err)
v, err = evalBuiltinFunc(f, chunk.Row{})
require.NoError(t, err)
Expand Down
1 change: 1 addition & 0 deletions expression/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ var (
errWrongValueForType = dbterror.ClassExpression.NewStd(mysql.ErrWrongValueForType)
errUnknown = dbterror.ClassExpression.NewStd(mysql.ErrUnknown)
errSpecificAccessDenied = dbterror.ClassExpression.NewStd(mysql.ErrSpecificAccessDenied)
errUserLockDeadlock = dbterror.ClassExpression.NewStd(mysql.ErrUserLockDeadlock)

// Sequence usage privilege check.
errSequenceAccessDenied = dbterror.ClassExpression.NewStd(mysql.ErrTableaccessDenied)
Expand Down
8 changes: 2 additions & 6 deletions expression/function_traits.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,9 @@ var mutableEffectsFunctions = map[string]struct{}{
ast.AnyValue: {},
}

// some functions like "get_lock" and "release_lock" currently do NOT have
// right implementations, but may have noop ones(like with any inputs, always return 1)
// some functions do NOT have right implementations, but may have noop ones(like with any inputs, always return 1)
// if apps really need these "funcs" to run, we offer sys var(tidb_enable_noop_functions) to enable noop usage
var noopFuncs = map[string]struct{}{
ast.GetLock: {},
ast.ReleaseLock: {},
}
var noopFuncs = map[string]struct{}{}

// booleanFunctions stores boolean functions
var booleanFunctions = map[string]struct{}{
Expand Down
2 changes: 0 additions & 2 deletions expression/integration_serial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4183,8 +4183,6 @@ func TestNoopFunctions(t *testing.T) {
"SELECT * FROM t1 LOCK IN SHARE MODE",
"SELECT * FROM t1 GROUP BY a DESC",
"SELECT * FROM t1 GROUP BY a ASC",
"SELECT GET_LOCK('acdc', 10)",
"SELECT RELEASE_LOCK('acdc')",
}

for _, stmt := range stmts {
Expand Down
3 changes: 2 additions & 1 deletion expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,6 @@ func TestMiscellaneousBuiltin(t *testing.T) {
tk.MustQuery("select a,any_value(b),sum(c) from t1 group by a order by a;").Check(testkit.Rows("1 10 0", "2 30 0"))

// for locks
tk.MustExec(`set tidb_enable_noop_functions=1;`)
result := tk.MustQuery(`SELECT GET_LOCK('test_lock1', 10);`)
result.Check(testkit.Rows("1"))
result = tk.MustQuery(`SELECT GET_LOCK('test_lock2', 10);`)
Expand All @@ -330,6 +329,8 @@ func TestMiscellaneousBuiltin(t *testing.T) {
result.Check(testkit.Rows("1"))
result = tk.MustQuery(`SELECT RELEASE_LOCK('test_lock1');`)
result.Check(testkit.Rows("1"))
result = tk.MustQuery(`SELECT RELEASE_LOCK('test_lock3');`) // not acquired
result.Check(testkit.Rows("0"))
}

func TestConvertToBit(t *testing.T) {
Expand Down
6 changes: 2 additions & 4 deletions parser/ast/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,8 @@ const (
BinToUUID = "bin_to_uuid"
VitessHash = "vitess_hash"
TiDBShard = "tidb_shard"
// get_lock() and release_lock() is parsed but do nothing.
// It is used for preventing error in Ruby's activerecord migrations.
GetLock = "get_lock"
ReleaseLock = "release_lock"
GetLock = "get_lock"
ReleaseLock = "release_lock"

// encryption and compression functions
AesDecrypt = "aes_decrypt"
Expand Down
Loading