Skip to content

Commit

Permalink
Implement LAST_INSERT_IDS function
Browse files Browse the repository at this point in the history
- Returns a table
- Add tests
  • Loading branch information
laurynas-biveinis committed Jul 8, 2024
1 parent 6783460 commit 5fd1a67
Show file tree
Hide file tree
Showing 10 changed files with 927 additions and 0 deletions.
478 changes: 478 additions & 0 deletions mysql-test/r/last_insert_ids.result

Large diffs are not rendered by default.

316 changes: 316 additions & 0 deletions mysql-test/t/last_insert_ids.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
--echo #
--echo # Tests for LAST_INSERT_IDS function
--echo #

--echo #
--echo # Should not be callable as a regular function
--echo #
--error ER_PARSE_ERROR
SELECT LAST_INSERT_IDS();
--error ER_PARSE_ERROR
SELECT LAST_INSERT_IDS(10);
--error ER_PARSE_ERROR
SELECT LAST_INSERT_IDS("foo", "bar");

--echo #
--echo # Table-returning functions must be aliased
--echo #
--error ER_TF_MUST_HAVE_ALIAS
SELECT * FROM LAST_INSERT_IDS();

--echo #
--echo # When no rows have been inserted yet, should return an empty table
--echo #
SELECT * FROM LAST_INSERT_IDS() AS ids;

--echo #
--echo # EXPLAIN SELECT ... LAST_INSERT_IDS
--echo #
EXPLAIN SELECT * FROM LAST_INSERT_IDS() AS ids;

--echo #
--echo # In the case of a single insert, should match LAST_INSERT_ID
--echo #
CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY);

INSERT INTO t1 VALUES ();
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

INSERT INTO t1 VALUES ();
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

TRUNCATE TABLE t1;
--echo #
--echo # Explicitly-provided value is ignored by LAST_INSERT_IDS
--echo #
INSERT INTO t1 VALUES (5);
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

--echo #
--echo # Explicitly-provided duplicate value
--echo #
--error ER_DUP_ENTRY
INSERT INTO t1 VALUES (5);
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;
TRUNCATE TABLE t1;

--echo #
--echo # Multiple values returned by LAST_INSERT_IDS
--echo #
INSERT INTO t1 VALUES (),(),(),(),();
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

--echo #
--echo # CREATE TABLE ... SELECT FROM LAST_INSERT_IDS
--echo #
INSERT INTO t1 VALUES (),();
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;
CREATE TABLE t2 AS SELECT * FROM LAST_INSERT_IDS() AS ids;
SELECT * FROM t2;
DROP TABLE t2;

--echo #
--echo # INSERT ... SELECT LAST_INSERT_IDS
--echo #
INSERT INTO t1 VALUES (),(),();
TRUNCATE TABLE t1;
INSERT INTO t1 SELECT * FROM LAST_INSERT_IDS() AS ids;
SELECT * FROM t1;
TRUNCATE TABLE t1;

--echo #
--echo # PREPARE / EXECUTE
--echo #
PREPARE s1 FROM 'SELECT * FROM LAST_INSERT_IDS() AS ids';
EXECUTE s1;
INSERT INTO t1 VALUES (),(),();
EXECUTE s1;
DEALLOCATE PREPARE s1;
TRUNCATE TABLE t1;

--echo #
--echo # Statement binlog format
--echo #
SET SESSION binlog_format = 'STATEMENT';
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

INSERT INTO t1 VALUES ();
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

INSERT INTO t1 VALUES (),(),(),(),();
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

--echo #
--echo # Mixed binlog format
--echo #
SET SESSION binlog_format = 'MIXED';
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

INSERT INTO t1 VALUES ();
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

INSERT INTO t1 VALUES (),(),(),(),();
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

TRUNCATE TABLE t1;
SET SESSION binlog_format = 'ROW';

--echo #
--echo # LAST_INSERT_ID(arg) should ignore LAST_INSERT_IDS
--echo #
SELECT LAST_INSERT_ID(100);
SELECT * FROM LAST_INSERT_IDS() AS ids;

SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

--echo #
--echo # Multi-statement transactions
--echo #
BEGIN;
INSERT INTO t1 VALUES ();
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;
INSERT INTO t1 VALUES (),();
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;
COMMIT;
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() as ids;

--echo #
--echo # Rolled back multi-statement transactions
--echo #
BEGIN;
INSERT INTO t1 VALUES (),();
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;
ROLLBACK;
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;
INSERT INTO t1 VALUES (),();
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

TRUNCATE TABLE t1;

--echo #
--echo # Failures to insert
--echo #
CREATE TABLE t2 (a INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
b INT NOT NULL UNIQUE);
INSERT INTO t2 VALUES (NULL, 0), (NULL, 1);
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

--error ER_DUP_ENTRY
INSERT INTO t2 VALUES (NULL, 0);
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

DROP TABLE t2;

--echo #
--echo # Accessing LAST_INSERT_IDS in a triggger: only the 1st value is seen,
--echo # consistent with LAST_INSERT_ID behavior
--echo #
delimiter |;
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
CREATE TRIGGER trg1 AFTER INSERT ON t1 FOR EACH ROW
BEGIN
CREATE TABLE t2 SELECT * FROM LAST_INSERT_IDS() AS ids;
END|

CREATE TABLE t2 (a INT);
INSERT INTO t2 VALUES (0);

CREATE TABLE t3 (a INT);
INSERT INTO t3 VALUES (0);

INSERT INTO t1 VALUES ();

CREATE TRIGGER trg1 AFTER INSERT ON t1 FOR EACH ROW
BEGIN
INSERT INTO t2 SELECT * FROM LAST_INSERT_IDS() AS ids;
INSERT INTO t2 VALUES (NULL);
END|

CREATE TRIGGER trg2 AFTER INSERT ON t1 FOR EACH ROW
BEGIN
INSERT INTO t3 VALUES(LAST_INSERT_ID());
INSERT INTO t3 VALUES (NULL);
END|

delimiter ;|

INSERT INTO t1 VALUES (),(),();

SELECT * FROM t2;
SELECT * FROM t3;

--echo # The triggers will not affect the outer LAST_INSERT_IDS
SELECT * FROM LAST_INSERT_IDS() AS ids;
SELECT LAST_INSERT_ID();

TRUNCATE TABLE t1;
DROP TRIGGER trg1;
DROP TRIGGER trg2;
DROP TABLE t2, t3;

--echo #
--echo # INSERT IGNORE
--echo #
INSERT IGNORE INTO t1 VALUES (),();
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

INSERT IGNORE INTO t1 VALUES (2);
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

DROP TABLE t1;

--echo #
--echo # REPLACE
--echo #
CREATE TABLE t2 (a INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
b INT NOT NULL UNIQUE,
c INT);

--echo # REPLACE that only inserts
REPLACE INTO t2 VALUES (NULL, 0, 0), (NULL, 1, 0);
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

--echo # REPLACE that only replaces
REPLACE INTO t2 VALUES (NULL, 0, 1), (NULL, 1, 1);
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

--echo # REPLACE that inserts, updates, & inserts
REPLACE INTO t2 VALUES (NULL, 2, 0), (NULL, 0, 2), (NULL, 3, 0);
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

TRUNCATE TABLE t2;

--echo #
--echo # INSERT ... ON DUPLICATE KEY UPDATE with some inserts and some updates
--echo #

--echo # Regular INSERT
INSERT INTO t2 VALUES (NULL, 0, 0), (NULL, 1, 0);
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

--echo # INSERT ODKU that inserts
INSERT INTO t2 VALUES (NULL, 2, 0), (NULL, 3, 0)
ON DUPLICATE KEY UPDATE c = c + 1;
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

--echo # INSERT ODKU that updates
INSERT INTO t2 VALUES (NULL, 0, 0), (NULL, 1, 0)
ON DUPLICATE KEY UPDATE c = c + 1;
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

--echo # INSERT ODKU that inserts, updates, & inserts
INSERT INTO t2 VALUES (NULL, 4, 0), (NULL, 0, 0), (NULL, 5, 0)
ON DUPLICATE KEY UPDATE c = c + 1;
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

--echo # INSERT ODKU that updates, inserts, & updates
INSERT INTO t2 VALUES (NULL, 0, 0), (NULL, 6, 0), (NULL, 1, 0)
ON DUPLICATE KEY UPDATE c = c + 1;
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

DROP TABLE t2;

--echo #
--echo # Composite primary key with an auto_increment part
--echo #
CREATE TABLE t2 (
a INT NOT NULL AUTO_INCREMENT,
b INT,
PRIMARY KEY (a, b));

INSERT INTO t2 VALUES (NULL, 0), (NULL, 0);
SELECT LAST_INSERT_ID();
SELECT * FROM LAST_INSERT_IDS() AS ids;

DROP TABLE t2;
1 change: 1 addition & 0 deletions sql/lex.h
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ static const SYMBOL symbols[] = {
{SYM("LAG", LAG_SYM)},
{SYM("LANGUAGE", LANGUAGE_SYM)},
{SYM("LAST", LAST_SYM)},
{SYM("LAST_INSERT_IDS", LAST_INSERT_IDS_SYM)},
{SYM("LAST_VALUE", LAST_VALUE_SYM)},
{SYM("LATERAL", LATERAL_SYM)},
{SYM("LEAD", LEAD_SYM)},
Expand Down
21 changes: 21 additions & 0 deletions sql/parse_tree_nodes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,27 @@ bool PT_table_factor_function::contextualize(Parse_context *pc) {
return false;
}

bool PT_last_insert_ids_function::contextualize(Parse_context *pc) {
if (super::contextualize(pc)) return true;
auto *const fn = new (pc->mem_root) Table_function_last_insert_ids{};
if (unlikely(fn == nullptr)) return true;

LEX_CSTRING alias;
alias.length = strlen(fn->func_name());
alias.str = sql_strmake(fn->func_name(), alias.length);
if (unlikely(alias.str == nullptr)) return true;

auto *const ti = new (pc->mem_root) Table_ident(alias, fn);
if (ti == nullptr) return true;

m_table_ref = pc->select->add_table_to_list(pc->thd, ti, m_table_alias.str, 0,
TL_READ, MDL_SHARED_READ);
if (m_table_ref == nullptr || pc->select->add_joined_table(m_table_ref))
return true;

return false;
}

PT_derived_table::PT_derived_table(bool lateral, PT_subquery *subquery,
const LEX_CSTRING &table_alias,
Create_col_name_list *column_names)
Expand Down
13 changes: 13 additions & 0 deletions sql/parse_tree_nodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,19 @@ class PT_table_factor_function : public PT_table_reference {
const LEX_STRING m_table_alias;
};

class PT_last_insert_ids_function : public PT_table_reference {
using super = PT_table_reference;

public:
PT_last_insert_ids_function(const LEX_STRING &table_alias)
: m_table_alias{table_alias} {}

bool contextualize(Parse_context *pc) override;

private:
const LEX_STRING m_table_alias;
};

class PT_table_reference_list_parens : public PT_table_reference {
typedef PT_table_reference super;

Expand Down
3 changes: 3 additions & 0 deletions sql/sql_class.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2165,6 +2165,9 @@ void THD::cleanup_after_query() {
first_successful_insert_id_in_prev_stmt =
first_successful_insert_id_in_cur_stmt;
first_successful_insert_id_in_cur_stmt = 0;
successful_insert_ids_in_prev_stmt =
std::move(successful_insert_ids_in_cur_stmt);
successful_insert_ids_in_cur_stmt.clear();
}
arg_of_last_insert_id_function = false;
/* Hack for cleaning up view security contexts */
Expand Down
Loading

0 comments on commit 5fd1a67

Please sign in to comment.