github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/locks.go (about)

     1  // Copyright 2020-2021 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package function
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  	"time"
    21  
    22  	"gopkg.in/src-d/go-errors.v1"
    23  
    24  	"github.com/dolthub/go-mysql-server/sql"
    25  	"github.com/dolthub/go-mysql-server/sql/expression"
    26  	"github.com/dolthub/go-mysql-server/sql/types"
    27  )
    28  
    29  // ErrIllegalLockNameArgType is a kind of error that is thrown when the parameter passed as a lock name is not a string.
    30  var ErrIllegalLockNameArgType = errors.NewKind("Illegal parameter data type %s for operation '%s'")
    31  
    32  // lockFuncLogic is the logic executed when one of the single argument named lock functions is executed
    33  type lockFuncLogic func(ctx *sql.Context, ls *sql.LockSubsystem, lockName string) (interface{}, error)
    34  
    35  func (nl *NamedLockFunction) evalLockLogic(ctx *sql.Context, fn lockFuncLogic, row sql.Row) (interface{}, error) {
    36  	lock, err := nl.GetLockName(ctx, row)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	if lock == nil {
    41  		return nil, nil
    42  	}
    43  
    44  	return fn(ctx, nl.ls, *lock)
    45  }
    46  
    47  // NamedLockFunction is a sql function that takes just the name of a lock as an argument
    48  type NamedLockFunction struct {
    49  	expression.UnaryExpression
    50  	ls       *sql.LockSubsystem
    51  	funcName string
    52  	retType  sql.Type
    53  }
    54  
    55  // FunctionName implements sql.FunctionExpression
    56  func (nl *NamedLockFunction) FunctionName() string {
    57  	return nl.funcName
    58  }
    59  
    60  // Eval implements the Expression interface.
    61  func (nl *NamedLockFunction) GetLockName(ctx *sql.Context, row sql.Row) (*string, error) {
    62  	if nl.Child == nil {
    63  		return nil, nil
    64  	}
    65  
    66  	val, err := nl.Child.Eval(ctx, row)
    67  
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	if val == nil {
    73  		return nil, nil
    74  	}
    75  
    76  	s, ok := nl.Child.Type().(sql.StringType)
    77  	if !ok {
    78  		return nil, ErrIllegalLockNameArgType.New(nl.Child.Type().String(), nl.funcName)
    79  	}
    80  	lockName, err := types.ConvertToString(val, s)
    81  	if err != nil {
    82  		return nil, fmt.Errorf("%w; %s", ErrIllegalLockNameArgType.New(nl.Child.Type().String(), nl.funcName), err)
    83  	}
    84  
    85  	return &lockName, nil
    86  }
    87  
    88  // String implements the fmt.Stringer interface.
    89  func (nl *NamedLockFunction) String() string {
    90  	return fmt.Sprintf("%s(%s)", strings.ToLower(nl.funcName), nl.Child.String())
    91  }
    92  
    93  // IsNullable implements the Expression interface.
    94  func (nl *NamedLockFunction) IsNullable() bool {
    95  	return nl.Child.IsNullable()
    96  }
    97  
    98  // Type implements the Expression interface.
    99  func (nl *NamedLockFunction) Type() sql.Type {
   100  	return nl.retType
   101  }
   102  
   103  // ReleaseLockFunc is the function logic that is executed when the release_lock function is called.
   104  func ReleaseLockFunc(ctx *sql.Context, ls *sql.LockSubsystem, lockName string) (interface{}, error) {
   105  	err := ls.Unlock(ctx, lockName)
   106  
   107  	if err != nil {
   108  		if sql.ErrLockDoesNotExist.Is(err) {
   109  			return nil, nil
   110  		} else if sql.ErrLockNotOwned.Is(err) {
   111  			return int8(0), nil
   112  		}
   113  
   114  		return nil, err
   115  	}
   116  
   117  	return int8(1), nil
   118  }
   119  
   120  type IsFreeLock struct {
   121  	NamedLockFunction
   122  }
   123  
   124  var _ sql.FunctionExpression = &IsFreeLock{}
   125  var _ sql.CollationCoercible = &IsFreeLock{}
   126  
   127  func NewIsFreeLock(ls *sql.LockSubsystem) sql.CreateFunc1Args {
   128  	return func(e sql.Expression) sql.Expression {
   129  		return &IsFreeLock{
   130  			NamedLockFunction: NamedLockFunction{
   131  				UnaryExpression: expression.UnaryExpression{e},
   132  				ls:              ls,
   133  				funcName:        "is_free_lock",
   134  				retType:         types.Int8,
   135  			},
   136  		}
   137  	}
   138  }
   139  
   140  // Description implements sql.FunctionExpression
   141  func (i *IsFreeLock) Description() string {
   142  	return "returns whether the named lock is free."
   143  }
   144  
   145  // CollationCoercibility implements the interface sql.CollationCoercible.
   146  func (*IsFreeLock) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
   147  	return sql.Collation_binary, 5
   148  }
   149  
   150  func (i *IsFreeLock) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
   151  	return i.evalLockLogic(ctx, IsFreeLockFunc, row)
   152  }
   153  
   154  func (i *IsFreeLock) WithChildren(children ...sql.Expression) (sql.Expression, error) {
   155  	if len(children) != 1 {
   156  		return nil, sql.ErrInvalidChildrenNumber.New(i, len(children), 1)
   157  	}
   158  
   159  	return NewIsFreeLock(i.ls)(children[0]), nil
   160  }
   161  
   162  type IsUsedLock struct {
   163  	NamedLockFunction
   164  }
   165  
   166  var _ sql.FunctionExpression = &IsUsedLock{}
   167  var _ sql.CollationCoercible = &IsUsedLock{}
   168  
   169  func NewIsUsedLock(ls *sql.LockSubsystem) sql.CreateFunc1Args {
   170  	return func(e sql.Expression) sql.Expression {
   171  		return &IsUsedLock{
   172  			NamedLockFunction: NamedLockFunction{
   173  				UnaryExpression: expression.UnaryExpression{e},
   174  				ls:              ls,
   175  				funcName:        "is_used_lock",
   176  				retType:         types.Uint32,
   177  			},
   178  		}
   179  	}
   180  }
   181  
   182  // Description implements sql.FunctionExpression
   183  func (i *IsUsedLock) Description() string {
   184  	return "returns whether the named lock is in use; return connection identifier if true."
   185  }
   186  
   187  // CollationCoercibility implements the interface sql.CollationCoercible.
   188  func (*IsUsedLock) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
   189  	return sql.Collation_binary, 5
   190  }
   191  
   192  func (i *IsUsedLock) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
   193  	return i.evalLockLogic(ctx, IsUsedLockFunc, row)
   194  }
   195  
   196  func (i *IsUsedLock) WithChildren(children ...sql.Expression) (sql.Expression, error) {
   197  	if len(children) != 1 {
   198  		return nil, sql.ErrInvalidChildrenNumber.New(i, len(children), 1)
   199  	}
   200  
   201  	return NewIsUsedLock(i.ls)(children[0]), nil
   202  }
   203  
   204  type ReleaseLock struct {
   205  	NamedLockFunction
   206  }
   207  
   208  var _ sql.FunctionExpression = &ReleaseLock{}
   209  var _ sql.CollationCoercible = &ReleaseLock{}
   210  
   211  func NewReleaseLock(ls *sql.LockSubsystem) sql.CreateFunc1Args {
   212  	return func(e sql.Expression) sql.Expression {
   213  		return &ReleaseLock{
   214  			NamedLockFunction: NamedLockFunction{
   215  				UnaryExpression: expression.UnaryExpression{e},
   216  				ls:              ls,
   217  				funcName:        "release_lock",
   218  				retType:         types.Int8,
   219  			},
   220  		}
   221  	}
   222  }
   223  
   224  // Description implements sql.FunctionExpression
   225  func (i *ReleaseLock) Description() string {
   226  	return "release the named lock."
   227  }
   228  
   229  // CollationCoercibility implements the interface sql.CollationCoercible.
   230  func (*ReleaseLock) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
   231  	return sql.Collation_binary, 5
   232  }
   233  
   234  func (i *ReleaseLock) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
   235  	return i.evalLockLogic(ctx, ReleaseLockFunc, row)
   236  }
   237  
   238  func (i *ReleaseLock) WithChildren(children ...sql.Expression) (sql.Expression, error) {
   239  	if len(children) != 1 {
   240  		return nil, sql.ErrInvalidChildrenNumber.New(i, len(children), 1)
   241  	}
   242  
   243  	return NewReleaseLock(i.ls)(children[0]), nil
   244  }
   245  
   246  // IsFreeLockFunc is the function logic that is executed when the is_free_lock function is called.
   247  func IsFreeLockFunc(_ *sql.Context, ls *sql.LockSubsystem, lockName string) (interface{}, error) {
   248  	state, _ := ls.GetLockState(lockName)
   249  
   250  	switch state {
   251  	case sql.LockInUse:
   252  		return int8(0), nil
   253  	default: // return 1 if the lock is free.  If the lock doesn't exist yet it is free
   254  		return int8(1), nil
   255  	}
   256  }
   257  
   258  // IsUsedLockFunc is the function logic that is executed when the is_used_lock function is called.
   259  func IsUsedLockFunc(ctx *sql.Context, ls *sql.LockSubsystem, lockName string) (interface{}, error) {
   260  	state, owner := ls.GetLockState(lockName)
   261  
   262  	switch state {
   263  	case sql.LockInUse:
   264  		return owner, nil
   265  	default:
   266  		return nil, nil
   267  	}
   268  }
   269  
   270  // GetLock is a SQL function implementing get_lock
   271  type GetLock struct {
   272  	expression.BinaryExpressionStub
   273  	ls *sql.LockSubsystem
   274  }
   275  
   276  var _ sql.FunctionExpression = (*GetLock)(nil)
   277  var _ sql.CollationCoercible = (*GetLock)(nil)
   278  
   279  // CreateNewGetLock returns a new GetLock object
   280  func CreateNewGetLock(ls *sql.LockSubsystem) func(e1, e2 sql.Expression) sql.Expression {
   281  	return func(e1, e2 sql.Expression) sql.Expression {
   282  		return &GetLock{expression.BinaryExpressionStub{e1, e2}, ls}
   283  	}
   284  }
   285  
   286  // FunctionName implements sql.FunctionExpression
   287  func (gl *GetLock) FunctionName() string {
   288  	return "get_lock"
   289  }
   290  
   291  // Description implements sql.FunctionExpression
   292  func (gl *GetLock) Description() string {
   293  	return "gets a named lock."
   294  }
   295  
   296  // Eval implements the Expression interface.
   297  func (gl *GetLock) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
   298  	if gl.LeftChild == nil {
   299  		return nil, nil
   300  	}
   301  
   302  	leftVal, err := gl.LeftChild.Eval(ctx, row)
   303  
   304  	if err != nil {
   305  		return nil, err
   306  	}
   307  
   308  	if leftVal == nil {
   309  		return nil, nil
   310  	}
   311  
   312  	if gl.RightChild == nil {
   313  		return nil, nil
   314  	}
   315  
   316  	rightVal, err := gl.RightChild.Eval(ctx, row)
   317  
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  
   322  	if rightVal == nil {
   323  		return nil, nil
   324  	}
   325  
   326  	s, ok := gl.LeftChild.Type().(sql.StringType)
   327  	if !ok {
   328  		return nil, ErrIllegalLockNameArgType.New(gl.LeftChild.Type().String(), gl.FunctionName())
   329  	}
   330  
   331  	lockName, err := types.ConvertToString(leftVal, s)
   332  	if err != nil {
   333  		return nil, fmt.Errorf("%w; %s", ErrIllegalLockNameArgType.New(gl.LeftChild.Type().String(), gl.FunctionName()), err)
   334  	}
   335  
   336  	timeout, _, err := types.Int64.Convert(rightVal)
   337  
   338  	if err != nil {
   339  		return nil, fmt.Errorf("illegal value for timeout %v", timeout)
   340  	}
   341  
   342  	err = gl.ls.Lock(ctx, lockName, time.Second*time.Duration(timeout.(int64)))
   343  
   344  	if err != nil {
   345  		if sql.ErrLockTimeout.Is(err) {
   346  			return int8(0), nil
   347  		}
   348  
   349  		return nil, err
   350  	}
   351  
   352  	return int8(1), nil
   353  }
   354  
   355  // String implements the fmt.Stringer interface.
   356  func (gl *GetLock) String() string {
   357  	return fmt.Sprintf("get_lock(%s, %s)", gl.LeftChild.String(), gl.RightChild.String())
   358  }
   359  
   360  // IsNullable implements the Expression interface.
   361  func (gl *GetLock) IsNullable() bool {
   362  	return false
   363  }
   364  
   365  // WithChildren implements the Expression interface.
   366  func (gl *GetLock) WithChildren(children ...sql.Expression) (sql.Expression, error) {
   367  	if len(children) != 2 {
   368  		return nil, sql.ErrInvalidChildrenNumber.New(gl, len(children), 1)
   369  	}
   370  
   371  	return &GetLock{expression.BinaryExpressionStub{LeftChild: children[0], RightChild: children[1]}, gl.ls}, nil
   372  }
   373  
   374  // Type implements the Expression interface.
   375  func (gl *GetLock) Type() sql.Type {
   376  	return types.Int8
   377  }
   378  
   379  // CollationCoercibility implements the interface sql.CollationCoercible.
   380  func (*GetLock) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
   381  	return sql.Collation_binary, 5
   382  }
   383  
   384  type ReleaseAllLocks struct {
   385  	NoArgFunc
   386  	ls *sql.LockSubsystem
   387  }
   388  
   389  var _ sql.FunctionExpression = ReleaseAllLocks{}
   390  var _ sql.CollationCoercible = ReleaseAllLocks{}
   391  
   392  func NewReleaseAllLocks(ls *sql.LockSubsystem) func() sql.Expression {
   393  	return func() sql.Expression {
   394  		return ReleaseAllLocks{
   395  			NoArgFunc: NoArgFunc{"release_all_locks", types.Int32},
   396  			ls:        ls,
   397  		}
   398  	}
   399  }
   400  
   401  // Description implements sql.FunctionExpression
   402  func (r ReleaseAllLocks) Description() string {
   403  	return "release all current named locks."
   404  }
   405  
   406  // CollationCoercibility implements the interface sql.CollationCoercible.
   407  func (ReleaseAllLocks) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
   408  	return sql.Collation_binary, 5
   409  }
   410  
   411  func (r ReleaseAllLocks) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
   412  	return r.ls.ReleaseAll(ctx)
   413  }
   414  
   415  func (r ReleaseAllLocks) WithChildren(children ...sql.Expression) (sql.Expression, error) {
   416  	return NoArgFuncWithChildren(r, children)
   417  }