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 }