Skip to content
Open
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
138 changes: 138 additions & 0 deletions mysql-test/main/insert_update_alias.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#
# MDEV-29919: Support INSERT ... VALUES AS alias ON DUPLICATE KEY UPDATE
#
#
# Test setup
#
CREATE TABLE t1 (
a INT PRIMARY KEY,
b INT,
c INT
);
#
# Basic INSERT AS alias ON DUPLICATE KEY UPDATE
#
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 20, 200) AS new
ON DUPLICATE KEY UPDATE b = new.b, c = new.c;
SELECT * FROM t1;
a b c
1 20 200
#
# Multiple rows with AS alias
#
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 20, 200), (2, 30, 300) AS new
ON DUPLICATE KEY UPDATE b = new.b;
SELECT * FROM t1 ORDER BY a;
a b c
1 20 100
2 30 300
#
# Expression using alias columns
#
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 5, 50) AS new
ON DUPLICATE KEY UPDATE b = new.b + new.c, c = new.a * 10;
SELECT * FROM t1;
a b c
1 55 10
#
# Mix of alias and table column references
#
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 20, 200) AS new
ON DUPLICATE KEY UPDATE b = new.b, c = t1.c + new.c;
SELECT * FROM t1;
a b c
1 20 300
#
# INSERT without ON DUPLICATE KEY (alias should be ignored)
#
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100) AS new;
SELECT * FROM t1;
a b c
1 10 100
#
# Different alias names
#
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 99, 999) AS inserted_row
ON DUPLICATE KEY UPDATE b = inserted_row.b, c = inserted_row.c;
SELECT * FROM t1;
a b c
1 99 999
#
# The AS keyword is required for the row alias
#
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 20, 200) new
ON DUPLICATE KEY UPDATE b = new.b;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'new
ON DUPLICATE KEY UPDATE b = new.b' at line 1
INSERT INTO t1 VALUES (1, 20, 200) AS 'new'
ON DUPLICATE KEY UPDATE b = new.b;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''new'
ON DUPLICATE KEY UPDATE b = new.b' at line 1
INSERT INTO t1 VALUES (1, 20, 200) 'new'
ON DUPLICATE KEY UPDATE b = new.b;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''new'
ON DUPLICATE KEY UPDATE b = new.b' at line 1
SELECT * FROM t1;
a b c
1 10 100
#
# Alias cannot be the same as the target table name
#
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 50, 500) AS t1
ON DUPLICATE KEY UPDATE b = t1.b;
ERROR 42000: Not unique table/alias: 't1'
#
# A VALUES alias inside a derived table is not an INSERT row alias
#
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 SELECT * FROM (VALUES (1, 2, 3) AS new) AS t2
ON DUPLICATE KEY UPDATE b = new.c;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use
SELECT * FROM t1;
a b c
1 10 100
#
# A VALUES constructor in an ON DUPLICATE KEY UPDATE subquery must not
# clobber the row alias of the outer INSERT
#
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 20, 200) AS new
ON DUPLICATE KEY UPDATE
b = new.b + (SELECT COUNT(*) FROM (VALUES (5),(9)) AS dt),
c = new.c;
SELECT * FROM t1;
a b c
1 22 200
#
# Verify that AS alias is NOT allowed in REPLACE
#
REPLACE INTO t1 VALUES (1, 50, 500) AS new;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use
#
# Verify that AS alias is NOT allowed in CREATE ... VALUES
#
CREATE TABLE t2 AS VALUES (1, 10, 100) AS new;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use
#
# Cleanup
#
DROP TABLE t1;
#
# End of tests
#
136 changes: 136 additions & 0 deletions mysql-test/main/insert_update_alias.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
--echo #
--echo # MDEV-29919: Support INSERT ... VALUES AS alias ON DUPLICATE KEY UPDATE
--echo #

#
# This test validates the row alias syntax for INSERT ON DUPLICATE KEY UPDATE
# which allows referencing inserted values using alias.column instead of VALUES(column)
#

--echo #
--echo # Test setup
--echo #
CREATE TABLE t1 (
a INT PRIMARY KEY,
b INT,
c INT
);
Comment thread
vuvova marked this conversation as resolved.

--echo #
--echo # Basic INSERT AS alias ON DUPLICATE KEY UPDATE
--echo #
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 20, 200) AS new
ON DUPLICATE KEY UPDATE b = new.b, c = new.c;
SELECT * FROM t1;

--echo #
--echo # Multiple rows with AS alias
--echo #
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 20, 200), (2, 30, 300) AS new
ON DUPLICATE KEY UPDATE b = new.b;
SELECT * FROM t1 ORDER BY a;

--echo #
--echo # Expression using alias columns
--echo #
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 5, 50) AS new
ON DUPLICATE KEY UPDATE b = new.b + new.c, c = new.a * 10;
SELECT * FROM t1;

--echo #
--echo # Mix of alias and table column references
--echo #
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 20, 200) AS new
ON DUPLICATE KEY UPDATE b = new.b, c = t1.c + new.c;
SELECT * FROM t1;

--echo #
--echo # INSERT without ON DUPLICATE KEY (alias should be ignored)
--echo #
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100) AS new;
SELECT * FROM t1;

--echo #
--echo # Different alias names
--echo #
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 99, 999) AS inserted_row
ON DUPLICATE KEY UPDATE b = inserted_row.b, c = inserted_row.c;
SELECT * FROM t1;

--echo #
--echo # The AS keyword is required for the row alias
--echo #
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
--error ER_PARSE_ERROR
INSERT INTO t1 VALUES (1, 20, 200) new
ON DUPLICATE KEY UPDATE b = new.b;
--error ER_PARSE_ERROR
INSERT INTO t1 VALUES (1, 20, 200) AS 'new'
ON DUPLICATE KEY UPDATE b = new.b;
--error ER_PARSE_ERROR
INSERT INTO t1 VALUES (1, 20, 200) 'new'
ON DUPLICATE KEY UPDATE b = new.b;
SELECT * FROM t1;

--echo #
--echo # Alias cannot be the same as the target table name
--echo #
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
--error ER_NONUNIQ_TABLE
Comment thread
FooBarrior marked this conversation as resolved.
INSERT INTO t1 VALUES (1, 50, 500) AS t1
ON DUPLICATE KEY UPDATE b = t1.b;

--echo #
--echo # A VALUES alias inside a derived table is not an INSERT row alias
--echo #
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
--error ER_SYNTAX_ERROR
INSERT INTO t1 SELECT * FROM (VALUES (1, 2, 3) AS new) AS t2
ON DUPLICATE KEY UPDATE b = new.c;
SELECT * FROM t1;

--echo #
--echo # A VALUES constructor in an ON DUPLICATE KEY UPDATE subquery must not
--echo # clobber the row alias of the outer INSERT
--echo #
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
INSERT INTO t1 VALUES (1, 20, 200) AS new
ON DUPLICATE KEY UPDATE
b = new.b + (SELECT COUNT(*) FROM (VALUES (5),(9)) AS dt),
c = new.c;
SELECT * FROM t1;

--echo #
--echo # Verify that AS alias is NOT allowed in REPLACE
--echo #
--error ER_SYNTAX_ERROR
REPLACE INTO t1 VALUES (1, 50, 500) AS new;

--echo #
--echo # Verify that AS alias is NOT allowed in CREATE ... VALUES
--echo #
--error ER_SYNTAX_ERROR
CREATE TABLE t2 AS VALUES (1, 10, 100) AS new;

--echo #
--echo # Cleanup
--echo #
DROP TABLE t1;

--echo #
--echo # End of tests
--echo #
19 changes: 19 additions & 0 deletions sql/item.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6481,6 +6481,25 @@ bool Item_field::fix_fields(THD *thd, Item **reference)
if (!field) // If field is not checked
{
TABLE_LIST *table_list;

/* Handle INSERT ... VALUES (...) AS alias ON DUPLICATE KEY UPDATE */
if (thd->where == THD_WHERE::UPDATE_CLAUSE &&
thd->lex->duplicates == DUP_UPDATE &&
thd->lex->insert_values_alias.str &&
table_name.str &&
table_name.streq(thd->lex->insert_values_alias))
{
Item_field *field_ref= new (thd->mem_root)
Item_field(thd, context, db_name, Lex_cstring_strlen(NULL), field_name);
if (!field_ref)
return TRUE;
Item_insert_value *ins_val= new (thd->mem_root)
Item_insert_value(thd, context, field_ref);
if (!ins_val)
return TRUE;
thd->change_item_tree(reference, ins_val);
return ins_val->fix_fields(thd, reference);
}
/*
In case of view, find_field_in_tables() write pointer to view field
expression to 'reference', i.e. it substitute that expression instead
Expand Down
14 changes: 13 additions & 1 deletion sql/sql_insert.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1825,6 +1825,17 @@ int mysql_prepare_insert(THD *thd, TABLE_LIST *table_list,

if (duplic == DUP_UPDATE)
{
/*
The row alias for INSERT ... AS alias cannot be the same as the
target table name, as this would make column references ambiguous.
*/
if (thd->lex->insert_values_alias.str &&
table_list->table_name.streq(thd->lex->insert_values_alias))
{
my_error(ER_NONUNIQ_TABLE, MYF(0), thd->lex->insert_values_alias.str);
DBUG_RETURN(1);
}

/* it should be allocated before Item::fix_fields() */
if (table_list->set_insert_values(thd->mem_root))
DBUG_RETURN(1);
Expand Down Expand Up @@ -1862,7 +1873,8 @@ int mysql_prepare_insert(THD *thd, TABLE_LIST *table_list,

if (!res)
res= setup_fields(thd, Ref_ptr_array(),
update_values, MARK_COLUMNS_READ, 0, NULL, 0);
update_values, MARK_COLUMNS_READ, 0, NULL, 0,
THD_WHERE::UPDATE_CLAUSE);

if (!res && duplic == DUP_UPDATE)
{
Expand Down
1 change: 1 addition & 0 deletions sql/sql_lex.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,7 @@ void LEX::start(THD *thd_arg)
part_info= 0;
m_sql_cmd= NULL;
duplicates= DUP_ERROR;
insert_values_alias= Lex_ident_table();
spname= NULL;
spcont= NULL;
proc_list.first= 0;
Expand Down
2 changes: 2 additions & 0 deletions sql/sql_lex.h
Original file line number Diff line number Diff line change
Expand Up @@ -3480,6 +3480,8 @@ struct LEX: public Query_tables_list
const char *clause_that_disallows_subselect;

enum enum_duplicates duplicates;
/* Represents INSERT...VALUES as <alias> */
Lex_ident_table insert_values_alias;
enum enum_tx_isolation tx_isolation;
enum enum_ha_read_modes ha_read_mode;
union {
Expand Down
33 changes: 32 additions & 1 deletion sql/sql_yacc.yy
Original file line number Diff line number Diff line change
Expand Up @@ -9178,13 +9178,44 @@ table_value_constructor:
if (Lex->parsed_TVC_start())
MYSQL_YYABORT;
}
values_list
values_list opt_values_row_alias
{
if (!($$= Lex->parsed_TVC_end()))
MYSQL_YYABORT;
}
;

/*
Optional alias for the row(s) being inserted.
Syntax: INSERT ... VALUES (...) AS alias
Used to reference new values in ON DUPLICATE KEY UPDATE clause.
The AS keyword is required, as in MySQL.

The empty alternative must not touch insert_values_alias: a VALUES
table value constructor without an alias may appear in a subquery of
the same INSERT, and clearing the alias here would clobber the row
alias set earlier. It is reset in LEX::start().
*/
opt_values_row_alias:
/* empty */
| AS ident
{
/*
The row alias is only valid as the direct value source of an
INSERT, i.e. INSERT ... VALUES (...) AS alias. The VALUES select
must sit directly below the INSERT's top select on the parse
stack. This excludes a VALUES table value constructor used as a
derived table, e.g. INSERT ... SELECT * FROM (VALUES (...) AS x).
*/
SELECT_LEX *parent= Lex->select_stack_top >= 2 ?
Lex->select_stack[Lex->select_stack_top - 2] : nullptr;
if (Lex->sql_command != SQLCOM_INSERT ||
parent != Lex->first_select_lex())
my_yyabort_error((ER_SYNTAX_ERROR, MYF(0)));
Lex->insert_values_alias= Lex_ident_table($2);
}
;

opt_hint_comment:
/*empty */ { $$.init(); }
| HINT_COMMENT { $$= $1; }
Expand Down