Skip to content

sql: add row lock modes; make FOR UPDATE a no-op #40206

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

Merged
merged 1 commit into from
Aug 27, 2019
Merged
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
2 changes: 1 addition & 1 deletion docs/generated/sql/bnf/select_stmt.bnf
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
select_stmt ::=
( select_clause ( sort_clause | ) ( limit_clause | ) ( offset_clause | ) | ( 'WITH' ( ( common_table_expr ) ( ( ',' common_table_expr ) )* ) ) select_clause ( sort_clause | ) ( limit_clause | ) ( offset_clause | ) )
( simple_select opt_for | select_clause sort_clause opt_for | select_clause ( sort_clause | ) ( limit_clause offset_clause | offset_clause limit_clause | limit_clause | offset_clause ) opt_for | ( 'WITH' ( ( common_table_expr ) ( ( ',' common_table_expr ) )* ) ) select_clause opt_for | ( 'WITH' ( ( common_table_expr ) ( ( ',' common_table_expr ) )* ) ) select_clause sort_clause opt_for | ( 'WITH' ( ( common_table_expr ) ( ( ',' common_table_expr ) )* ) ) select_clause ( sort_clause | ) ( limit_clause offset_clause | offset_clause limit_clause | limit_clause | offset_clause ) opt_for )

19 changes: 13 additions & 6 deletions docs/generated/sql/bnf/stmt_block.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -434,12 +434,12 @@ scrub_database_stmt ::=
'EXPERIMENTAL' 'SCRUB' 'DATABASE' database_name opt_as_of_clause

select_no_parens ::=
simple_select
| select_clause sort_clause
| select_clause opt_sort_clause select_limit
| with_clause select_clause
| with_clause select_clause sort_clause
| with_clause select_clause opt_sort_clause select_limit
simple_select opt_for
| select_clause sort_clause opt_for
| select_clause opt_sort_clause select_limit opt_for
| with_clause select_clause opt_for
| with_clause select_clause sort_clause opt_for
| with_clause select_clause opt_sort_clause select_limit opt_for

select_with_parens ::=
'(' select_no_parens ')'
Expand Down Expand Up @@ -788,6 +788,7 @@ unreserved_keyword ::=
| 'SESSION'
| 'SESSIONS'
| 'SET'
| 'SHARE'
| 'SHOW'
| 'SIMPLE'
| 'SMALLSERIAL'
Expand Down Expand Up @@ -1142,6 +1143,12 @@ simple_select ::=
| table_clause
| set_operation

opt_for ::=
'FOR' 'UPDATE'
| 'FOR' 'NO' 'KEY' 'UPDATE'
| 'FOR' 'SHARE'
| 'FOR' 'KEY' 'SHARE'

select_clause ::=
simple_select
| select_with_parens
Expand Down
6 changes: 3 additions & 3 deletions pkg/sql/logictest/testdata/logic_test/feature_counts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# LogicTest: local

statement error unimplemented
SELECT * FROM system.users FOR UPDATE
SELECT 'a'::INTERVAL(123)

query TI colnames
SELECT *
FROM crdb_internal.feature_usage
WHERE feature_name LIKE '%syntax.#6583%'
WHERE feature_name LIKE '%syntax.#32564%'
----
feature_name usage_count
unimplemented.syntax.#6583 1
unimplemented.syntax.#32564 1
21 changes: 21 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/select_for_update
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Cockroach currently supports all of the row locking modes as no-ops, so just
# test that they parse and run.
query I
SELECT 1 FOR UPDATE
----
1

query I
SELECT 1 FOR NO KEY UPDATE
----
1

query I
SELECT 1 FOR SHARE
----
1

query I
SELECT 1 FOR KEY SHARE
----
1
14 changes: 14 additions & 0 deletions pkg/sql/opt/optbuilder/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,20 @@ func (b *Builder) buildSelect(
orderBy := stmt.OrderBy
limit := stmt.Limit
with := stmt.With
forLocked := stmt.ForLocked

switch forLocked {
case tree.ForNone:
case tree.ForUpdate:
case tree.ForNoKeyUpdate:
case tree.ForShare:
case tree.ForKeyShare:
// CockroachDB treats all of the FOR LOCKED modes as no-ops. Since all
// transactions are serializable in CockroachDB, clients can't observe
// whether or not FOR UPDATE (or any of the other weaker modes) actually
// created a lock. This behavior may improve as the transaction model gains
// more capabilities.
}

for s, ok := wrapped.(*tree.ParenSelect); ok; s, ok = wrapped.(*tree.ParenSelect) {
stmt = s.Select
Expand Down
6 changes: 5 additions & 1 deletion pkg/sql/parser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,11 @@ func TestParse(t *testing.T) {
{`SELECT (i.keys).*`},
{`SELECT (ARRAY['a', 'b', 'c']).name`},

{`SELECT 1 FOR UPDATE`},
{`SELECT 1 FOR NO KEY UPDATE`},
{`SELECT 1 FOR SHARE`},
{`SELECT 1 FOR KEY SHARE`},

{`TABLE a`}, // Shorthand for: SELECT * FROM a; used e.g. in CREATE VIEW v AS TABLE t
{`EXPLAIN TABLE a`},
{`TABLE [123 AS a]`},
Expand Down Expand Up @@ -3025,7 +3030,6 @@ func TestUnimplementedSyntax(t *testing.T) {
{`INSERT INTO foo(a, a.b) VALUES (1,2)`, 27792, ``},
{`INSERT INTO foo VALUES (1,2) ON CONFLICT ON CONSTRAINT a DO NOTHING`, 28161, ``},

{`SELECT * FROM a FOR UPDATE`, 6583, ``},
{`SELECT * FROM ROWS FROM (a(b) AS (d))`, 0, `ROWS FROM with col_def_list`},

{`SELECT 'a'::INTERVAL SECOND`, 0, `interval with unit qualifier`},
Expand Down
26 changes: 17 additions & 9 deletions pkg/sql/parser/sql.y
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ func (u *sqlSymUnion) when() *tree.When {
func (u *sqlSymUnion) whens() []*tree.When {
return u.val.([]*tree.When)
}
func (u *sqlSymUnion) forLocked() tree.ForLocked {
return u.val.(tree.ForLocked)
}
func (u *sqlSymUnion) updateExpr() *tree.UpdateExpr {
return u.val.(*tree.UpdateExpr)
}
Expand Down Expand Up @@ -554,7 +557,7 @@ func newNameFromStr(s string) *tree.Name {
%token <str> SAVEPOINT SCATTER SCHEMA SCHEMAS SCRUB SEARCH SECOND SELECT SEQUENCE SEQUENCES
%token <str> SERIAL SERIAL2 SERIAL4 SERIAL8
%token <str> SERIALIZABLE SERVER SESSION SESSIONS SESSION_USER SET SETTING SETTINGS
%token <str> SHOW SIMILAR SIMPLE SMALLINT SMALLSERIAL SNAPSHOT SOME SPLIT SQL
%token <str> SHARE SHOW SIMILAR SIMPLE SMALLINT SMALLSERIAL SNAPSHOT SOME SPLIT SQL

%token <str> START STATISTICS STATUS STDIN STRICT STRING STORE STORED STORING SUBSTRING
%token <str> SYMMETRIC SYNTAX SYSTEM SUBSCRIPTION
Expand Down Expand Up @@ -773,6 +776,7 @@ func newNameFromStr(s string) *tree.Name {

%type <*tree.Select> select_no_parens
%type <tree.SelectStatement> select_clause select_with_parens simple_select values_clause table_clause simple_select_clause
%type <tree.ForLocked> opt_for
%type <tree.SelectStatement> set_operation

%type <tree.Expr> alter_column_default
Expand Down Expand Up @@ -5750,32 +5754,35 @@ select_with_parens:
select_no_parens:
simple_select opt_for
{
$$.val = &tree.Select{Select: $1.selectStmt()}
$$.val = &tree.Select{Select: $1.selectStmt(), ForLocked: $2.forLocked()}
}
| select_clause sort_clause opt_for
{
$$.val = &tree.Select{Select: $1.selectStmt(), OrderBy: $2.orderBy()}
$$.val = &tree.Select{Select: $1.selectStmt(), OrderBy: $2.orderBy(), ForLocked: $3.forLocked()}
}
| select_clause opt_sort_clause select_limit opt_for
{
$$.val = &tree.Select{Select: $1.selectStmt(), OrderBy: $2.orderBy(), Limit: $3.limit()}
$$.val = &tree.Select{Select: $1.selectStmt(), OrderBy: $2.orderBy(), Limit: $3.limit(), ForLocked: $4.forLocked()}
}
| with_clause select_clause opt_for
{
$$.val = &tree.Select{With: $1.with(), Select: $2.selectStmt()}
$$.val = &tree.Select{With: $1.with(), Select: $2.selectStmt(), ForLocked: $3.forLocked()}
}
| with_clause select_clause sort_clause opt_for
{
$$.val = &tree.Select{With: $1.with(), Select: $2.selectStmt(), OrderBy: $3.orderBy()}
$$.val = &tree.Select{With: $1.with(), Select: $2.selectStmt(), OrderBy: $3.orderBy(), ForLocked: $4.forLocked()}
}
| with_clause select_clause opt_sort_clause select_limit opt_for
{
$$.val = &tree.Select{With: $1.with(), Select: $2.selectStmt(), OrderBy: $3.orderBy(), Limit: $4.limit()}
$$.val = &tree.Select{With: $1.with(), Select: $2.selectStmt(), OrderBy: $3.orderBy(), Limit: $4.limit(), ForLocked: $5.forLocked()}
}

opt_for:
/* EMPTY */ { /* no error */ }
| FOR error { return unimplementedWithIssue(sqllex, 6583) }
/* EMPTY */ { $$.val = tree.ForNone }
| FOR UPDATE { $$.val = tree.ForUpdate }
| FOR NO KEY UPDATE { $$.val = tree.ForNoKeyUpdate }
| FOR SHARE { $$.val = tree.ForShare }
| FOR KEY SHARE { $$.val = tree.ForKeyShare }

select_clause:
// We only provide help if an open parenthesis is provided, because
Expand Down Expand Up @@ -9351,6 +9358,7 @@ unreserved_keyword:
| SESSION
| SESSIONS
| SET
| SHARE
| SHOW
| SIMPLE
| SMALLSERIAL
Expand Down
22 changes: 22 additions & 0 deletions pkg/sql/sem/tree/pretty.go
Original file line number Diff line number Diff line change
Expand Up @@ -538,9 +538,31 @@ func (node *Select) docTable(p *PrettyCfg) []pretty.TableRow {
}
items = append(items, node.OrderBy.docRow(p))
items = append(items, node.Limit.docTable(p)...)
items = append(items, node.ForLocked.docTable(p)...)
return items
}

func (node ForLocked) doc(p *PrettyCfg) pretty.Doc {
return p.rlTable(node.docTable(p)...)
}

func (node ForLocked) docTable(p *PrettyCfg) []pretty.TableRow {
var keyword string
switch node {
case ForNone:
return nil
case ForUpdate:
keyword = "FOR UPDATE"
case ForNoKeyUpdate:
keyword = "FOR NO KEY UPDATE"
case ForShare:
keyword = "FOR SHARE"
case ForKeyShare:
keyword = "FOR KEY SHARE"
}
return []pretty.TableRow{p.row("", pretty.Keyword(keyword))}
}

func (node *SelectClause) doc(p *PrettyCfg) pretty.Doc {
return p.rlTable(node.docTable(p)...)
}
Expand Down
42 changes: 38 additions & 4 deletions pkg/sql/sem/tree/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,43 @@ func (*ValuesClause) selectStatement() {}

// Select represents a SelectStatement with an ORDER and/or LIMIT.
type Select struct {
With *With
Select SelectStatement
OrderBy OrderBy
Limit *Limit
With *With
Select SelectStatement
OrderBy OrderBy
Limit *Limit
ForLocked ForLocked
}

// ForLocked represents the possible row-level lock modes for a SELECT
// statement.
type ForLocked byte

const (
// ForNone represents the default - no for statement at all.
ForNone ForLocked = iota
// ForUpdate represents FOR UPDATE.
ForUpdate
// ForNoKeyUpdate represents FOR NO KEY UPDATE.
ForNoKeyUpdate
// ForShare represents FOR SHARE.
ForShare
// ForKeyShare represents FOR KEY SHARE.
ForKeyShare
)

// Format implements the NodeFormatter interface.
func (f ForLocked) Format(ctx *FmtCtx) {
switch f {
case ForNone:
case ForUpdate:
ctx.WriteString(" FOR UPDATE")
case ForNoKeyUpdate:
ctx.WriteString(" FOR NO KEY UPDATE")
case ForShare:
ctx.WriteString(" FOR SHARE")
case ForKeyShare:
ctx.WriteString(" FOR KEY SHARE")
}
}

// Format implements the NodeFormatter interface.
Expand All @@ -59,6 +92,7 @@ func (node *Select) Format(ctx *FmtCtx) {
ctx.WriteByte(' ')
ctx.FormatNode(node.Limit)
}
ctx.FormatNode(node.ForLocked)
}

// ParenSelect represents a parenthesized SELECT/UNION/VALUES statement.
Expand Down