Skip to content

MDEV-29919: Support INSERT ... AS alias ON DUPLICATE KEY UPDATE#4544

Open
MooSayed1 wants to merge 1 commit intoMariaDB:mainfrom
MooSayed1:MDEV-29919-insert-as-alias
Open

MDEV-29919: Support INSERT ... AS alias ON DUPLICATE KEY UPDATE#4544
MooSayed1 wants to merge 1 commit intoMariaDB:mainfrom
MooSayed1:MDEV-29919-insert-as-alias

Conversation

@MooSayed1
Copy link
Copy Markdown
Contributor

MySQL 8.0.20 introduced a new syntax for INSERT ... ON DUPLICATE KEY UPDATE that allows referencing the inserted row using an alias instead of the VALUES() function:

-- Old syntax (still supported)
INSERT INTO t1 (a, b) VALUES (1, 2)
  ON DUPLICATE KEY UPDATE b = VALUES(b);

-- New syntax (this patch adds support)
INSERT INTO t1 (a, b) VALUES (1, 2) AS new
  ON DUPLICATE KEY UPDATE b = new.b;

The alias syntax is cleaner and more readable, especially when referencing multiple columns or using expressions like new.a + new.b.


Implementation

  • Parser: Added opt_values_row_alias rule to accept AS alias after VALUES
  • LEX: Added insert_values_alias field to store the alias
  • Name resolution: In Item_field::fix_fields(), when we're in ON DUPLICATE KEY UPDATE and the table qualifier matches the alias, we convert the reference to an Item_insert_value (equivalent to VALUES())

The traditional VALUES() function continues to work unchanged.

Tests

Added mysql-test/main/insert_update_alias.test covering:

  • Basic INSERT ... AS alias ON DUPLICATE KEY UPDATE
  • Multiple rows with alias
  • Expressions using alias columns (new.a + new.b)
  • Backward compatibility with VALUES() function
  • Mix of alias and table column references
  • INSERT without ON DUPLICATE KEY (alias ignored)
  • Different alias names

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Jan 15, 2026

CLA assistant check
All committers have signed the CLA.

@MooSayed1 MooSayed1 force-pushed the MDEV-29919-insert-as-alias branch 2 times, most recently from d724e69 to ff235ab Compare January 15, 2026 21:20
@github-actions github-actions Bot added the External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements. label Jan 17, 2026
Copy link
Copy Markdown
Contributor

@FooBarrior FooBarrior left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @MooSayed1! Thanks for your contribution.

I think this needs a better high-level detalization. What happens if a table has a field with the same name? This is a MySQL compatibility task -- so it's important to know what MySQL does in this case.

These details on the approach to name resolution will be important to be mentioned in the commit comment.

Comment thread sql/item.cc Outdated
When we are in ON DUPLICATE KEY UPDATE and the table qualifier matches
the insert_values_alias, we should resolve this as VALUES(column).
*/
if (select &&
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. No need to specify MDEV in the comments.
  2. It's enough to have a meaningful comment.
  3. In general, better not to refer to an exact sql_command value.
  4. We need to make sure the syntax can't be used in the unsupposed ways, like that REPLACE...AS would fail with a syntax error, and CREATE ... VALUES would.
  5. I'm surprised you have to check select. Why?
  6. What does this code placement mean? it's in if (!field), meaning that it would apply only if field is not early-provided[?], but before find_field_in_tables, meaning that it would supersede a name resolution?

Copy link
Copy Markdown
Contributor Author

@MooSayed1 MooSayed1 Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first thanks for the review
then
Q1, Q2, Q3: Got it, will fix all of these.

Q4: Currently the grammar allows REPLACE ... AS alias to parse (since table_value_constructor is shared), but it's ignored at runtime because duplicates != DUP_UPDATE. but i think it would be better to get a syntax error at parse time instead of silent acceptance? and maybe adding tests for these scenarios

Q5: The select check was me being cautious - ensuring we have a valid query context. It's probably redundant.

Q6: It's placed before find_field_in_tables() so the alias takes priority. If a table and alias share the same name, new.b should resolve to the alias (inserted value), not the table. This matches MySQL behavior.

and should we continue the discussion here or move to Zulip?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches MySQL behavior.

Please show the related MySQL output

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MariaDB> SELECT VERSION();
+----------------------+
| VERSION()            |
+----------------------+
| 12.3.0-MariaDB-debug |
+----------------------+

MariaDB> CREATE TABLE t1 (a INT PRIMARY KEY, b INT, c INT);
MariaDB> INSERT INTO t1 VALUES (1, 10, 100);

MariaDB> SELECT * FROM t1;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |   10 |  100 |
+---+------+------+

MariaDB> INSERT INTO t1 VALUES (1, 20, 200) AS new 
         ON DUPLICATE KEY UPDATE b = new.b, c = new.c;

MariaDB> SELECT * FROM t1;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |   20 |  200 |
+---+------+------+

MariaDB> INSERT INTO t1 VALUES (1, 5, 50) AS new 
         ON DUPLICATE KEY UPDATE b = b + new.b, c = c + new.c;

MariaDB> SELECT * FROM t1;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |   25 |  250 |
+---+------+------+```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was asking about

If a table and alias share the same name, new.b should resolve to the alias (inserted value), not the table.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh okay mb

MySQL 8.0:

mysql> CREATE TABLE new (a INT PRIMARY KEY, b INT);
mysql> INSERT INTO new VALUES (1, 999);

mysql> CREATE TABLE t1 (a INT PRIMARY KEY, b INT);
mysql> INSERT INTO t1 VALUES (1, 10);

mysql> INSERT INTO t1 VALUES (1, 50) AS new ON DUPLICATE KEY UPDATE b = new.b;
mysql> SELECT * FROM t1;
+---+------+
| a | b    |
+---+------+
| 1 |   50 |  -- Alias wins (not 999 from table)
+---+------+

MariaDB the new implementation

MariaDB> -- Same test
MariaDB> INSERT INTO t1 VALUES (1, 50) AS new ON DUPLICATE KEY UPDATE b = new.b;
MariaDB> SELECT * FROM t1;
+---+------+
| a | b    |
+---+------+
| 1 |   50 |  -- Alias wins too Same as MySQL 
+---+------+

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think new couldn't be a table reference in the same request with no/different alias anyway.

Copy link
Copy Markdown
Contributor Author

@MooSayed1 MooSayed1 Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify this further?

From what I understand, my approach is incorrect when it comes to treating this as an alias. For example, consider the following case:

INSERT INTO t1 VALUES (1, 10);

SELECT * FROM t1;

INSERT INTO t1 VALUES (1, 50) AS t1
ON DUPLICATE KEY UPDATE b = t1.b;

In this situation, I should get a syntax error. However, with my current implementation, it continues without any issues and incorrectly treats the newly inserted row as a valid table alias.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FooBarrior do u prefer making a new commit to fix this or u prefer doing only one commit to get the issue done?

@MooSayed1 MooSayed1 force-pushed the MDEV-29919-insert-as-alias branch 2 times, most recently from 43a115c to 4dcef93 Compare January 20, 2026 22:09
Copy link
Copy Markdown
Contributor

@FooBarrior FooBarrior left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add test with CREATE..VALUES AS ident and REPLACE...VALUES AS ident producing a syntax error

);

--echo #
--echo # Test 1: Basic INSERT AS alias ON DUPLICATE KEY UPDATE
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the numbers: if we'll add some test in the middle, we'll have to renumerate everything.
Just:

--echo # Basic INSERT AS alias ON DUPLICATE KEY UPDATE

Comment thread sql/item.cc Outdated
ensures the alias takes priority over any existing table with
the same name, matching MySQL behavior.
*/
if (thd->lex->duplicates == DUP_UPDATE &&
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this line should be rather an assertion inside the if. If we've got to here with lex->insert_values_alias.str, then it should only be a valid case.

SELECT * FROM t1;

--echo #
--echo # Test 4: Backward compatibility - VALUES() function still works
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need in this one. VALUES (a) is tested even in main.insert.

--echo #
--echo # Test 9: Alias takes priority over existing table with same name
--echo #
DROP TABLE IF EXISTS new_values;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

junk drop

Comment thread mysql-test/main/insert_update_alias.test
Comment thread sql/item.cc Outdated
/*
Create a field reference without the alias qualifier,
then wrap it in Item_insert_value to get the inserted value (basically converting alis.column to Values(column)).
Use change_item_tree() for prepared statement compatibility (this's passing ps mode testing).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the comment as it doesn't complement the code with any new information (or change it to do so, without stating what's written in the code).

Comment thread sql/sql_lex.h Outdated

enum enum_duplicates duplicates;
/* Alias for INSERT ... To support the new syntax as MySQL */
LEX_CSTRING insert_values_alias;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it a Lex_ident derivative as a modern alternative.

Comment thread sql/sql_lex.h Outdated
const char *clause_that_disallows_subselect;

enum enum_duplicates duplicates;
/* Alias for INSERT ... To support the new syntax as MySQL */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/* Represents INSERT...VALUES as <alias> */
No need to mention MySQL "new" syntax, available yet in MySQL 8.0.19 dated year 2020 (6 years ago)

Comment thread sql/sql_yacc.yy Outdated
*/
opt_values_row_alias:
{
Lex->insert_values_alias= null_clex_str; // if AS..alias provided after Values()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove junk (and partially false) comments

@@ -0,0 +1,106 @@
#
# Test for INSERT ... VALUES (...) AS alias ON DUPLICATE KEY UPDATE syntax
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test can be moved to main.insert (@vuvova agree?). ODKU is also covered there.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add MDEV and jira title with --echo #

@MooSayed1 MooSayed1 force-pushed the MDEV-29919-insert-as-alias branch from 4dcef93 to cdc6fe5 Compare January 23, 2026 00:58
@MooSayed1
Copy link
Copy Markdown
Contributor Author

@FooBarrior Done.

Copy link
Copy Markdown
Contributor

@FooBarrior FooBarrior left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please be more careful with making changes. MariaDB is used on millions of machines, and we pay extra attention to the code quality. I hope you made that mistake just in a hurry.

Comment thread sql/item.cc Outdated
table_name.str &&
table_name.streq(thd->lex->insert_values_alias))
{
DBUG_ASSERT(thd->lex->insert_values_alias.str);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's... wrong?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm: should I remove the condition (thd->lex->duplicates == DUP_UPDATE) and instead move it into a DBUG_ASSERT inside the if block?

TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 10, 100);
--error ER_PARSE_ERROR
REPLACE INTO t1 VALUES (1, 50, 500) AS new ON DUPLICATE KEY UPDATE b = new.b;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ON DUPLICATE KEY UPDATE was never possible for REPLACE, so that's out of question.

Copy link
Copy Markdown
Contributor Author

@MooSayed1 MooSayed1 Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification. I realized my previous test included ON DUPLICATE KEY UPDATE with REPLACE, which caused a syntax error before even reaching the alias check.

I have updated the test to simply: REPLACE INTO t1 VALUES (...) AS new;

This should correctly verify that the alias syntax is rejected for REPLACE statements. Does this look correct to you?

Comment thread sql/sql_lex.h Outdated

enum enum_duplicates duplicates;
/* Represents INSERT...VALUES as <alias> */
Lex_ident_sys_st insert_values_alias;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you plase explain the reason to choose Lex_ident_sys_st

Copy link
Copy Markdown
Contributor Author

@MooSayed1 MooSayed1 Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this's what i understand from this comment
Make it a Lex_ident derivative as a modern alternative.

when i searched i found this and i though it's the same as u mean
and it was matching what the parser's ident rule returns

@MooSayed1
Copy link
Copy Markdown
Contributor Author

@FooBarrior i used this
if (Lex->sql_command == SQLCOM_REPLACE || Lex->sql_command == SQLCOM_CREATE_TABLE) {
my_yyabort_error((ER_SYNTAX_ERROR, MYF(0)));
}
to make sure that replace and create will get an error and test it
but u told me before In general, better not to refer to an exact sql_command value, so there's a better way?

@MooSayed1 MooSayed1 force-pushed the MDEV-29919-insert-as-alias branch from 6e19710 to a02f27c Compare January 23, 2026 22:45
@MooSayed1 MooSayed1 requested a review from FooBarrior January 23, 2026 22:48
@MooSayed1 MooSayed1 force-pushed the MDEV-29919-insert-as-alias branch 2 times, most recently from a2dba06 to 9a0e367 Compare January 30, 2026 02:44
@MooSayed1 MooSayed1 force-pushed the MDEV-29919-insert-as-alias branch 2 times, most recently from 314e028 to 825d169 Compare February 24, 2026 04:25
@MooSayed1 MooSayed1 force-pushed the MDEV-29919-insert-as-alias branch 2 times, most recently from f809d5f to 910eabc Compare February 28, 2026 23:49
Comment thread sql/sql_yacc.yy
{
if ($1.str)
{
if (Lex->sql_command != SQLCOM_INSERT)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually realized that common INSERT path goes the same way as CREATE, and table_value_constructor is tied deeply into create_select_query_expression, to which insert_values basically equals, so we really cannot avoid referring to Lex->sql_command, as I was requesting before. So, I admit my incorrectness.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, thanks for all the guidance along the way,I learned a lot.
Thank for reviewing too.

Copy link
Copy Markdown
Contributor

@FooBarrior FooBarrior left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes look good and complete in their functionality.

@vuvova vuvova force-pushed the MDEV-29919-insert-as-alias branch from 910eabc to c5bbf25 Compare March 23, 2026 20:19
@vuvova vuvova self-requested a review March 23, 2026 20:19
Copy link
Copy Markdown
Member

@gkodinov gkodinov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, this is a preliminary review.
Can you please make sure that:

  • the buildbot tests are passing or failing in an unrelated way: currently your new test is failing on some platforms.
  • Your commit has a commit message as mandated by the MariaDB coding standards document.

@MooSayed1
Copy link
Copy Markdown
Contributor Author

@gkodinov For the buildbot failure — it's the case sensitivity test in insert_update_alias. The test defines alias nEw but references new.b, expecting ER_BAD_FIELD_ERROR. This works on Linux where table_alias_charset is case-sensitive, but on Windows (lower_case_table_names=1) the comparison is case-insensitive so nEw matches new and the statement succeeds instead of error.
so i was thinking about fix it by removing the case sensitivity test since alias matching follows table_alias_charset.
what do you think

Implement MySQL 8.0.19 compatible row alias syntax for
INSERT ... ON DUPLICATE KEY UPDATE. The alias allows
referencing inserted values by name instead of VALUES():

  INSERT INTO t1 VALUES (1,2) AS new
    ON DUPLICATE KEY UPDATE b = new.b;

Parser: added opt_values_row_alias rule in sql_yacc.yy
to accept AS alias after VALUES clause.

LEX: added insert_values_alias to store the alias name.

Name resolution: in Item_field::fix_fields(), when inside
ON DUPLICATE KEY UPDATE and the table qualifier matches
the alias, the reference is converted to Item_insert_value
(equivalent to VALUES()). The alias must differ from the
target table name to avoid ambiguity (ER_NONUNIQ_TABLE).
Alias matching uses table_alias_charset, consistent with
how table aliases are compared.
@MooSayed1 MooSayed1 force-pushed the MDEV-29919-insert-as-alias branch from c5bbf25 to b048f86 Compare March 24, 2026 16:43
@MooSayed1 MooSayed1 requested a review from gkodinov March 25, 2026 17:51
Copy link
Copy Markdown
Member

@gkodinov gkodinov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Let me ping Serg if he wants to review or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements.

Development

Successfully merging this pull request may close these issues.

4 participants