Skip to content

Commit

Permalink
opt, sql: support UPDATE ... FROM statements
Browse files Browse the repository at this point in the history
Addresses cockroachdb#7841.

This change adds support for `UPDATE ... FROM` statements.
The FROM clause tables are joined together with the target
table and is used as input for the update. Furthermore, the
RETURNING clause can reference any table in the FROM clause.

Release note: None
  • Loading branch information
Ridwan Sharif committed Aug 5, 2019
1 parent 3b9a95b commit 4c6d1f5
Show file tree
Hide file tree
Showing 24 changed files with 1,075 additions and 88 deletions.
140 changes: 72 additions & 68 deletions docs/generated/sql/bnf/stmt_block.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ truncate_stmt ::=
'TRUNCATE' opt_table relation_expr_list opt_drop_behavior

update_stmt ::=
opt_with_clause 'UPDATE' table_name_expr_opt_alias_idx 'SET' set_clause_list opt_where_clause opt_sort_clause opt_limit_clause returning_clause
opt_with_clause 'UPDATE' table_name_expr_opt_alias_idx 'SET' set_clause_list opt_from_list opt_where_clause opt_sort_clause opt_limit_clause returning_clause

upsert_stmt ::=
opt_with_clause 'UPSERT' 'INTO' insert_target insert_rest returning_clause
Expand Down Expand Up @@ -555,6 +555,10 @@ opt_drop_behavior ::=
set_clause_list ::=
( set_clause ) ( ( ',' set_clause ) )*

opt_from_list ::=
'FROM' from_list
|

db_object_name ::=
simple_db_object_name
| complex_db_object_name
Expand Down Expand Up @@ -1185,6 +1189,9 @@ set_clause ::=
single_set_clause
| multiple_set_clause

from_list ::=
( table_ref ) ( ( ',' table_ref ) )*

simple_db_object_name ::=
db_object_name_component

Expand Down Expand Up @@ -1545,6 +1552,16 @@ single_set_clause ::=
multiple_set_clause ::=
'(' insert_column_list ')' '=' in_expr

table_ref ::=
relation_expr opt_index_flags opt_ordinality opt_alias_clause
| select_with_parens opt_ordinality opt_alias_clause
| 'LATERAL' select_with_parens opt_ordinality opt_alias_clause
| joined_table
| '(' joined_table ')' opt_ordinality alias_clause
| func_table opt_ordinality opt_alias_clause
| 'LATERAL' func_table opt_ordinality opt_alias_clause
| '[' row_source_extension_stmt ']' opt_ordinality opt_alias_clause

cockroachdb_extra_type_func_name_keyword ::=
'FAMILY'

Expand Down Expand Up @@ -1847,16 +1864,6 @@ distinct_clause ::=
distinct_on_clause ::=
'DISTINCT' 'ON' '(' expr_list ')'

table_ref ::=
relation_expr opt_index_flags opt_ordinality opt_alias_clause
| select_with_parens opt_ordinality opt_alias_clause
| 'LATERAL' select_with_parens opt_ordinality opt_alias_clause
| joined_table
| '(' joined_table ')' opt_ordinality alias_clause
| func_table opt_ordinality opt_alias_clause
| 'LATERAL' func_table opt_ordinality opt_alias_clause
| '[' row_source_extension_stmt ']' opt_ordinality opt_alias_clause

all_or_distinct ::=
'ALL'
| 'DISTINCT'
Expand All @@ -1865,6 +1872,39 @@ all_or_distinct ::=
var_list ::=
( var_value ) ( ( ',' var_value ) )*

opt_ordinality ::=
'WITH' 'ORDINALITY'
|

opt_alias_clause ::=
alias_clause
|

joined_table ::=
'(' joined_table ')'
| table_ref 'CROSS' opt_join_hint 'JOIN' table_ref
| table_ref join_type opt_join_hint 'JOIN' table_ref join_qual
| table_ref 'JOIN' table_ref join_qual
| table_ref 'NATURAL' join_type opt_join_hint 'JOIN' table_ref
| table_ref 'NATURAL' 'JOIN' table_ref

alias_clause ::=
'AS' table_alias_name opt_column_list
| table_alias_name opt_column_list

func_table ::=
func_expr_windowless
| 'ROWS' 'FROM' '(' rowsfrom_list ')'

row_source_extension_stmt ::=
delete_stmt
| explain_stmt
| insert_stmt
| select_stmt
| show_stmt
| update_stmt
| upsert_stmt

user_priority ::=
'LOW'
| 'NORMAL'
Expand Down Expand Up @@ -2061,44 +2101,31 @@ character_base ::=
| 'VARCHAR'
| 'STRING'

from_list ::=
( table_ref ) ( ( ',' table_ref ) )*

window_definition_list ::=
( window_definition ) ( ( ',' window_definition ) )*

opt_ordinality ::=
'WITH' 'ORDINALITY'
|

opt_alias_clause ::=
alias_clause
opt_join_hint ::=
'HASH'
| 'MERGE'
| 'LOOKUP'
|

joined_table ::=
'(' joined_table ')'
| table_ref 'CROSS' opt_join_hint 'JOIN' table_ref
| table_ref join_type opt_join_hint 'JOIN' table_ref join_qual
| table_ref 'JOIN' table_ref join_qual
| table_ref 'NATURAL' join_type opt_join_hint 'JOIN' table_ref
| table_ref 'NATURAL' 'JOIN' table_ref
join_type ::=
'FULL' join_outer
| 'LEFT' join_outer
| 'RIGHT' join_outer
| 'INNER'

alias_clause ::=
'AS' table_alias_name opt_column_list
| table_alias_name opt_column_list
join_qual ::=
'USING' '(' name_list ')'
| 'ON' a_expr

func_table ::=
func_expr_windowless
| 'ROWS' 'FROM' '(' rowsfrom_list ')'
func_expr_windowless ::=
func_application
| func_expr_common_subexpr

row_source_extension_stmt ::=
delete_stmt
| explain_stmt
| insert_stmt
| select_stmt
| show_stmt
| update_stmt
| upsert_stmt
rowsfrom_list ::=
( rowsfrom_item ) ( ( ',' rowsfrom_item ) )*

opt_column ::=
'COLUMN'
Expand Down Expand Up @@ -2230,28 +2257,12 @@ char_aliases ::=
window_definition ::=
window_name 'AS' window_specification

opt_join_hint ::=
'HASH'
| 'MERGE'
| 'LOOKUP'
join_outer ::=
'OUTER'
|

join_type ::=
'FULL' join_outer
| 'LEFT' join_outer
| 'RIGHT' join_outer
| 'INNER'

join_qual ::=
'USING' '(' name_list ')'
| 'ON' a_expr

func_expr_windowless ::=
func_application
| func_expr_common_subexpr

rowsfrom_list ::=
( rowsfrom_item ) ( ( ',' rowsfrom_item ) )*
rowsfrom_item ::=
func_expr_windowless

create_as_col_qualification_elem ::=
'PRIMARY' 'KEY'
Expand Down Expand Up @@ -2317,13 +2328,6 @@ trim_list ::=
| 'FROM' expr_list
| expr_list

join_outer ::=
'OUTER'
|

rowsfrom_item ::=
func_expr_windowless

create_as_param ::=
column_name

Expand Down
2 changes: 1 addition & 1 deletion docs/generated/sql/bnf/update_stmt.bnf
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
update_stmt ::=
( ( 'WITH' ( ( common_table_expr ) ( ( ',' common_table_expr ) )* ) ) | ) 'UPDATE' ( ( table_name opt_index_flags ) | ( table_name opt_index_flags ) table_alias_name | ( table_name opt_index_flags ) 'AS' table_alias_name ) 'SET' ( ( ( ( column_name '=' a_expr ) | ( '(' ( ( ( column_name ) ) ( ( ',' ( column_name ) ) )* ) ')' '=' ( '(' select_stmt ')' | ( '(' ')' | '(' ( a_expr | a_expr ',' | a_expr ',' ( ( a_expr ) ( ( ',' a_expr ) )* ) ) ')' ) ) ) ) ) ( ( ',' ( ( column_name '=' a_expr ) | ( '(' ( ( ( column_name ) ) ( ( ',' ( column_name ) ) )* ) ')' '=' ( '(' select_stmt ')' | ( '(' ')' | '(' ( a_expr | a_expr ',' | a_expr ',' ( ( a_expr ) ( ( ',' a_expr ) )* ) ) ')' ) ) ) ) ) )* ) ( ( 'WHERE' a_expr ) | ) ( sort_clause | ) ( limit_clause | ) ( 'RETURNING' target_list | 'RETURNING' 'NOTHING' | )
( ( 'WITH' ( ( common_table_expr ) ( ( ',' common_table_expr ) )* ) ) | ) 'UPDATE' ( ( table_name opt_index_flags ) | ( table_name opt_index_flags ) table_alias_name | ( table_name opt_index_flags ) 'AS' table_alias_name ) 'SET' ( ( ( ( column_name '=' a_expr ) | ( '(' ( ( ( column_name ) ) ( ( ',' ( column_name ) ) )* ) ')' '=' ( '(' select_stmt ')' | ( '(' ')' | '(' ( a_expr | a_expr ',' | a_expr ',' ( ( a_expr ) ( ( ',' a_expr ) )* ) ) ')' ) ) ) ) ) ( ( ',' ( ( column_name '=' a_expr ) | ( '(' ( ( ( column_name ) ) ( ( ',' ( column_name ) ) )* ) ')' '=' ( '(' select_stmt ')' | ( '(' ')' | '(' ( a_expr | a_expr ',' | a_expr ',' ( ( a_expr ) ( ( ',' a_expr ) )* ) ) ')' ) ) ) ) ) )* ) opt_from_list ( ( 'WHERE' a_expr ) | ) ( sort_clause | ) ( limit_clause | ) ( 'RETURNING' target_list | 'RETURNING' 'NOTHING' | )
174 changes: 174 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/update_from
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# LogicTest: local-opt fakedist-opt

statement ok
CREATE TABLE abc (a int primary key, b int, c int)

statement ok
INSERT INTO abc VALUES (1, 20, 300), (2, 30, 400)

# Updating using self join.
statement ok
UPDATE abc SET b = other.b + 1, c = other.c + 1 FROM abc AS other WHERE abc.a = other.a

query III rowsort
SELECT * FROM abc
----
1 21 301
2 31 401

# Update only some columns.
statement ok
UPDATE abc SET b = other.b + 1 FROM abc AS other WHERE abc.a = other.a

query III rowsort
SELECT * FROM abc
----
1 22 301
2 32 401

# Update only some rows.
statement ok
UPDATE abc SET b = other.b + 1 FROM abc AS other WHERE abc.a = other.a AND abc.a = 1

query III rowsort
SELECT * FROM abc
----
1 23 301
2 32 401

# Update from another table.
statement ok
CREATE TABLE new_abc (a int, b int, c int)

statement ok
INSERT INTO new_abc VALUES (1, 2, 3), (2, 3, 4)

statement ok
UPDATE abc SET b = new_abc.b, c = new_abc.c FROM new_abc WHERE abc.a = new_abc.a

query III rowsort
SELECT * FROM abc
----
1 2 3
2 3 4

# Multiple matching values for a given row. When this happens, we pick
# the first matching value for the row (this is arbitrary). This behavior
# is consistent with Postgres.
statement ok
INSERT INTO new_abc VALUES (1, 1, 1)

statement ok
UPDATE abc SET b = new_abc.b, c = new_abc.c FROM new_abc WHERE abc.a = new_abc.a

query III rowsort
SELECT * FROM abc
----
1 2 3
2 3 4

# Returning old values.
query IIIII colnames,rowsort
UPDATE abc
SET
b = old.b + 1, c = old.c + 2
FROM
abc AS old
WHERE
abc.a = old.a
RETURNING
abc.a, abc.b AS new_b, old.b as old_b, abc.c as new_c, old.c as old_c
----
a new_b old_b new_c old_c
1 3 2 5 3
2 4 3 6 4

# Check if RETURNING * returns everything
query IIIIII colnames,rowsort
UPDATE abc SET b = old.b + 1, c = old.c + 2 FROM abc AS old WHERE abc.a = old.a RETURNING *
----
a b c a b c
1 4 7 1 3 5
2 5 8 2 4 6

# Make sure UPDATE FROM works properly in the presence of check columns.
statement ok
CREATE TABLE abc_check (a int primary key, b int, c int, check (a > 0), check (b > 0 AND b < 10))

statement ok
INSERT INTO abc_check VALUES (1, 2, 3), (2, 3, 4)

query III colnames,rowsort
UPDATE abc_check
SET
b = other.b, c = other.c
FROM
abc AS other
WHERE
abc_check.a = other.a
RETURNING
abc_check.a, abc_check.b, abc_check.c
----
a b c
1 4 7
2 5 8

query III rowsort
SELECT * FROM abc
----
1 4 7
2 5 8

# Update values of table from values expression
statement ok
UPDATE abc SET b = other.b, c = other.c FROM (values (1, 2, 3), (2, 3, 4)) as other ("a", "b", "c") WHERE abc.a = other.a

query III rowsort
SELECT * FROM abc
----
1 2 3
2 3 4

# Check if UPDATE ... FROM works with multiple tables.
statement ok
CREATE TABLE ab (a INT, b INT)

statement ok
CREATE TABLE ac (a INT, c INT)

statement ok
INSERT INTO ab VALUES (1, 200), (2, 300)

statement ok
INSERT INTO ac VALUES (1, 300), (2, 400)

statement ok
UPDATE abc SET b = ab.b, c = ac.c FROM ab, ac WHERE abc.a = ab.a AND abc.a = ac.a

query III rowsort
SELECT * FROM abc
----
1 200 300
2 300 400

# Make sure UPDATE ... FROM works with LATERAL.
query IIIIIII colnames,rowsort
UPDATE abc
SET
b=ab.b, c = other.c
FROM
ab, LATERAL
(SELECT * FROM ac WHERE ab.a=ac.a) AS other
WHERE
abc.a=ab.a
RETURNING
*
----
a b c a b a c
1 200 300 1 200 1 300
2 300 400 2 300 2 400


# Make sure the FROM clause cannot reference the target table.
statement error no data source matches prefix: abc
UPDATE abc SET a = other.a FROM (SELECT abc.a FROM abc AS x) AS other WHERE abc.a=other.a
1 change: 1 addition & 0 deletions pkg/sql/opt/bench/stub_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ func (f *stubFactory) ConstructUpdate(
updateCols exec.ColumnOrdinalSet,
returnCols exec.ColumnOrdinalSet,
checks exec.CheckOrdinalSet,
passthrough sqlbase.ResultColumns,
) (exec.Node, error) {
return struct{}{}, nil
}
Expand Down
Loading

0 comments on commit 4c6d1f5

Please sign in to comment.