Skip to content

Commit 042eab3

Browse files
authored
Merge pull request #6 from ShoanJ/feature/support_lock_clause
support locking clause in select statement
2 parents c95d80e + a2ade22 commit 042eab3

5 files changed

Lines changed: 192 additions & 0 deletions

File tree

ast.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,60 @@ func (t *OrderingTerm) String() string {
611611
return buf.String()
612612
}
613613

614+
type LockStrength int
615+
616+
const (
617+
Update LockStrength = iota
618+
NoKeyUpdate
619+
Share
620+
KeyShare
621+
)
622+
623+
type LockOption int
624+
625+
func (l LockOption) ToPtr() *LockOption {
626+
return &l
627+
}
628+
629+
const (
630+
Nowait LockOption = iota
631+
SkipLocked
632+
)
633+
634+
type LockingClause struct {
635+
Strength LockStrength
636+
637+
Option *LockOption
638+
}
639+
640+
func (c *LockingClause) String() string {
641+
var buf bytes.Buffer
642+
buf.Grow(30)
643+
buf.WriteString("FOR")
644+
645+
switch c.Strength {
646+
case Update:
647+
buf.WriteString(" UPDATE")
648+
case NoKeyUpdate:
649+
buf.WriteString(" NO KEY UPDATE")
650+
case Share:
651+
buf.WriteString(" SHARE")
652+
case KeyShare:
653+
buf.WriteString(" KEY SHARE")
654+
}
655+
656+
if c.Option != nil {
657+
switch *c.Option {
658+
case Nowait:
659+
buf.WriteString(" NOWAIT")
660+
case SkipLocked:
661+
buf.WriteString(" SKIP LOCKED")
662+
}
663+
}
664+
665+
return buf.String()
666+
}
667+
614668
type ColumnArg interface {
615669
Node
616670
columnArg()
@@ -928,6 +982,8 @@ type SelectStatement struct {
928982
Limit Expr
929983
Offset Expr // offset expression
930984

985+
Locking *LockingClause
986+
931987
Hint *Hint
932988
}
933989

@@ -1004,6 +1060,10 @@ func (s *SelectStatement) String() string {
10041060
}
10051061
}
10061062

1063+
if s.Locking != nil {
1064+
fmt.Fprintf(&buf, " %s", s.Locking.String())
1065+
}
1066+
10071067
return buf.String()
10081068
}
10091069

ast_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,42 @@ func TestSelectStatement_String(t *testing.T) {
312312
Y: &sqlparser.TableName{Name: &sqlparser.Ident{Name: "y"}},
313313
},
314314
}, `SELECT * FROM x CROSS JOIN y`)
315+
316+
AssertStatementStringer(t, &sqlparser.SelectStatement{
317+
Distinct: true,
318+
Columns: &sqlparser.OutputNames{&sqlparser.ResultColumn{
319+
Star: true,
320+
}},
321+
Condition: &sqlparser.BinaryExpr{
322+
X: &sqlparser.Ident{Name: "ID"},
323+
Op: sqlparser.EQ,
324+
Y: &sqlparser.NumberLit{Value: "1"},
325+
},
326+
FromItems: &sqlparser.TableName{
327+
Name: &sqlparser.Ident{Name: "tbl"},
328+
},
329+
Locking: &sqlparser.LockingClause{
330+
Strength: sqlparser.Update,
331+
Option: sqlparser.Nowait.ToPtr(),
332+
}}, `SELECT DISTINCT * FROM tbl WHERE ID = 1 FOR UPDATE NOWAIT`)
333+
334+
AssertStatementStringer(t, &sqlparser.SelectStatement{
335+
Distinct: true,
336+
Columns: &sqlparser.OutputNames{&sqlparser.ResultColumn{
337+
Star: true,
338+
}},
339+
Condition: &sqlparser.BinaryExpr{
340+
X: &sqlparser.Ident{Name: "ID"},
341+
Op: sqlparser.EQ,
342+
Y: &sqlparser.NumberLit{Value: "1"},
343+
},
344+
FromItems: &sqlparser.TableName{
345+
Name: &sqlparser.Ident{Name: "tbl"},
346+
},
347+
Locking: &sqlparser.LockingClause{
348+
Strength: sqlparser.NoKeyUpdate,
349+
Option: sqlparser.SkipLocked.ToPtr(),
350+
}}, `SELECT DISTINCT * FROM tbl WHERE ID = 1 FOR NO KEY UPDATE SKIP LOCKED`)
315351
}
316352

317353
func TestUpdateStatement_String(t *testing.T) {

parser.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,56 @@ func (p *Parser) parseSelectStatement(compounded bool) (_ *SelectStatement, err
616616
}
617617
}
618618

619+
if !compounded && p.peek() == FOR {
620+
locking := &LockingClause{}
621+
p.lex()
622+
switch p.peek() {
623+
case UPDATE:
624+
locking.Strength = Update
625+
p.lex()
626+
case NO:
627+
p.lex()
628+
if p.peek() != KEY {
629+
return &stmt, p.errorExpected(p.pos, p.tok, "KEY")
630+
}
631+
p.lex()
632+
if p.peek() != UPDATE {
633+
return &stmt, p.errorExpected(p.pos, p.tok, "UPDATE")
634+
}
635+
locking.Strength = NoKeyUpdate
636+
p.lex()
637+
case SHARE:
638+
locking.Strength = Share
639+
p.lex()
640+
case KEY:
641+
p.lex()
642+
if p.peek() != SHARE {
643+
return &stmt, p.errorExpected(p.pos, p.tok, "SHARE")
644+
}
645+
locking.Strength = KeyShare
646+
p.lex()
647+
default:
648+
return &stmt, p.errorExpected(p.pos, p.tok, "UPDATE | NO | SHARE | KEY")
649+
}
650+
651+
switch p.peek() {
652+
case NOWAIT:
653+
locking.Option = Nowait.ToPtr()
654+
p.lex()
655+
case SKIP:
656+
p.lex()
657+
if p.peek() != LOCKED {
658+
return &stmt, p.errorExpected(p.pos, p.tok, "LOCKED")
659+
}
660+
locking.Option = SkipLocked.ToPtr()
661+
p.lex()
662+
default:
663+
664+
}
665+
666+
stmt.Locking = locking
667+
}
668+
619669
return &stmt, nil
620670
}
621671

parser_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,44 @@ func TestParser_ParseStatement(t *testing.T) {
343343
},
344344
})
345345

346+
AssertParseStatement(t, `SELECT DISTINCT * FROM tbl WHERE ID = 1 FOR UPDATE NOWAIT`, &sqlparser.SelectStatement{
347+
Distinct: true,
348+
Columns: &sqlparser.OutputNames{&sqlparser.ResultColumn{
349+
Star: true,
350+
}},
351+
Condition: &sqlparser.BinaryExpr{
352+
X: &sqlparser.Ident{Name: "ID"},
353+
Op: sqlparser.EQ,
354+
Y: &sqlparser.NumberLit{Value: "1"},
355+
},
356+
FromItems: &sqlparser.TableName{
357+
Name: &sqlparser.Ident{Name: "tbl"},
358+
},
359+
Locking: &sqlparser.LockingClause{
360+
Strength: sqlparser.Update,
361+
Option: sqlparser.Nowait.ToPtr(),
362+
},
363+
})
364+
365+
AssertParseStatement(t, `SELECT DISTINCT * FROM tbl WHERE ID = 1 FOR NO KEY UPDATE SKIP LOCKED`, &sqlparser.SelectStatement{
366+
Distinct: true,
367+
Columns: &sqlparser.OutputNames{&sqlparser.ResultColumn{
368+
Star: true,
369+
}},
370+
Condition: &sqlparser.BinaryExpr{
371+
X: &sqlparser.Ident{Name: "ID"},
372+
Op: sqlparser.EQ,
373+
Y: &sqlparser.NumberLit{Value: "1"},
374+
},
375+
FromItems: &sqlparser.TableName{
376+
Name: &sqlparser.Ident{Name: "tbl"},
377+
},
378+
Locking: &sqlparser.LockingClause{
379+
Strength: sqlparser.NoKeyUpdate,
380+
Option: sqlparser.SkipLocked.ToPtr(),
381+
},
382+
})
383+
346384
AssertParseStatementError(t, `SELECT `, `1:7: expected expression, found 'EOF'`)
347385
AssertParseStatementError(t, `SELECT 1+`, `1:9: expected expression, found 'EOF'`)
348386
AssertParseStatementError(t, `SELECT foo,`, `1:11: expected expression, found 'EOF'`)

token.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,10 @@ const (
234234
WITH
235235
WITHOUT
236236
DUPLICATE
237+
SHARE
238+
NOWAIT
239+
SKIP
240+
LOCKED
237241
keyword_end
238242

239243
ANY // ???
@@ -446,6 +450,10 @@ var tokens = [...]string{
446450
WITH: "WITH",
447451
WITHOUT: "WITHOUT",
448452
DUPLICATE: "DUPLICATE",
453+
SHARE: "SHARE",
454+
NOWAIT: "NOWAIT",
455+
SKIP: "SKIP",
456+
LOCKED: "LOCKED",
449457
}
450458

451459
func (tok Token) String() string {

0 commit comments

Comments
 (0)