From 2295f22baffcef8888e87ccce27c55656d55bc72 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Tue, 4 Aug 2026 20:54:28 +0100 Subject: [PATCH] feat: DuckDB supports AUTO INCREMENT stanza. --- storage/duckdb/CLAUDE.md | 2 +- storage/duckdb/CMakeLists.txt | 4 +- storage/duckdb/cmake/duckdb.cmake | 12 ++ storage/duckdb/convertor/ddl_convertor.cc | 88 +++++++- storage/duckdb/convertor/ddl_convertor.h | 10 + storage/duckdb/ha_duckdb.cc | 156 +++++++++++++- storage/duckdb/ha_duckdb.h | 30 ++- .../duckdb/include/alter_duckdb_column.inc | 9 +- .../duckdb/r/alter_duckdb_column.result | 8 +- .../duckdb/r/alter_duckdb_column_copy.result | 4 +- .../duckdb/r/duckdb_auto_increment.result | 204 ++++++++++++++++++ .../duckdb/t/duckdb_auto_increment.test | 84 ++++++++ .../duckdb/patches/duckdb-pr24061-setval.diff | 179 +++++++++++++++ 13 files changed, 767 insertions(+), 23 deletions(-) create mode 100644 storage/duckdb/mysql-test/duckdb/r/duckdb_auto_increment.result create mode 100644 storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test create mode 100644 storage/duckdb/patches/duckdb-pr24061-setval.diff diff --git a/storage/duckdb/CLAUDE.md b/storage/duckdb/CLAUDE.md index bccdb4bf834..b6cea1a72ef 100644 --- a/storage/duckdb/CLAUDE.md +++ b/storage/duckdb/CLAUDE.md @@ -70,7 +70,7 @@ All generated SQL must use **double quotes** for identifiers (DuckDB follows SQL ### DuckDB source and patches -DuckDB source is at `third_parties/duckdb/` (git submodule). No patches are applied — all compatibility is handled at runtime via `duckdb_mysql_compat.cc`. The build produces a static library; `_GLIBCXX_DEBUG` is explicitly undefined in CMakeLists.txt to avoid ABI mismatch with MariaDB's debug build. +DuckDB source is at `third_parties/duckdb/` (git submodule). One local patch is applied on top of the pinned submodule commit — `patches/duckdb-pr24061-setval.diff` (backport of upstream PR #24061 adding `setval()`, needed for AUTO_INCREMENT counter repositioning), applied via `PATCH_COMMAND` in `cmake/duckdb.cmake` and to be dropped when the submodule is bumped to a release containing the PR. Other compatibility is handled at runtime via `duckdb_mysql_compat.cc`. The build produces a static library; `_GLIBCXX_DEBUG` is explicitly undefined in CMakeLists.txt to avoid ABI mismatch with MariaDB's debug build. ### Configuration diff --git a/storage/duckdb/CMakeLists.txt b/storage/duckdb/CMakeLists.txt index f3a2242f96e..cf4e06a7f8e 100644 --- a/storage/duckdb/CMakeLists.txt +++ b/storage/duckdb/CMakeLists.txt @@ -63,8 +63,10 @@ IF(TARGET duckdb) ADD_SUBDIRECTORY(convertor) ADD_SUBDIRECTORY(runtime) + # atomic: GCC lowers the 16-byte atomic ops of the AUTO_INCREMENT block + # cache (Duckdb_share::autoinc_range) to libatomic calls. target_link_libraries(duckdb duckdb_runtime duckdb_convertor duckdb_common - ${DUCKDB_LIBRARY} + ${DUCKDB_LIBRARY} atomic ) duckdb_setup_target(duckdb) diff --git a/storage/duckdb/cmake/duckdb.cmake b/storage/duckdb/cmake/duckdb.cmake index 9da9909940b..1eb9924924d 100644 --- a/storage/duckdb/cmake/duckdb.cmake +++ b/storage/duckdb/cmake/duckdb.cmake @@ -51,6 +51,16 @@ SET(_DUCKDB_STATIC_LIBS MESSAGE(STATUS "=== Building DuckDB from submodule (${DUCKDB_SUBMODULE_DIR}) ===") ADD_SUBMODULE(third_parties/duckdb) +# Local patches applied on top of the pinned submodule commit. +# +# duckdb-pr24061-setval.diff: backport of upstream PR #24061 ("Add SetVal", +# merged 2026-07-23, first release after v1.5.2) rewritten against the v1.5.2 +# API. Provides setval('seq', value[, is_called]) needed for AUTO_INCREMENT +# counter repositioning (TRUNCATE, ALTER ... AUTO_INCREMENT=N, explicit-id +# reconciliation). Drop when the submodule is bumped to a release containing +# the PR: the apply below will fail loudly on an already-present setval. +SET(_DUCKDB_PATCH "${CMAKE_CURRENT_SOURCE_DIR}/patches/duckdb-pr24061-setval.diff") + # Upstream sets DUCKDB_EXTENSION_JEMALLOC_LINKED via add_extension_definitions(), # which runs in extension/ but NOT in src/, so allocator.cpp (in duckdb_static) # compiles the glibc malloc() path even though libjemalloc_extension.a is linked. @@ -79,6 +89,8 @@ ExternalProject_Add(duckdb_build PREFIX "${_DUCKDB_BUILD_DIR}" SOURCE_DIR "${DUCKDB_SUBMODULE_DIR}" BINARY_DIR "${_DUCKDB_BUILD_DIR}" + # Idempotent: skip when the working tree already carries the patch. + PATCH_COMMAND bash -c "git apply --reverse --check '${_DUCKDB_PATCH}' 2>/dev/null || git apply --verbose '${_DUCKDB_PATCH}'" CMAKE_ARGS -DCMAKE_BUILD_TYPE=${_DUCKDB_BUILD_TYPE} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} diff --git a/storage/duckdb/convertor/ddl_convertor.cc b/storage/duckdb/convertor/ddl_convertor.cc index 3a77991d4c6..f349d1fd700 100644 --- a/storage/duckdb/convertor/ddl_convertor.cc +++ b/storage/duckdb/convertor/ddl_convertor.cc @@ -216,6 +216,29 @@ static void note_sequence_default_omitted(const char *column) column); } +std::string autoinc_sequence_name(const std::string &table_name) +{ + return "mdb_autoinc_" + table_name; +} + +Field *find_autoinc_field(const TABLE *table) +{ + for (Field **ptr= table->field; *ptr; ptr++) + { + if ((*ptr)->flags & AUTO_INCREMENT_FLAG) + return *ptr; + } + return nullptr; +} + +/** Reference to the auto-increment sequence usable as a nextval() argument. */ +static std::string autoinc_nextval_expr(const std::string &schema_name, + const std::string &table_name) +{ + return "nextval('\"" + schema_name + "\".\"" + + autoinc_sequence_name(table_name) + "\"')"; +} + /** Read the literal default value of a field from the default record and return it as a string suitable for DuckDB SQL. @@ -419,11 +442,21 @@ static void append_stmt_table_rename(std::ostringstream &output, bool FieldConvertor::check() { - /* not support auto_increment */ - if (m_field->flags & AUTO_INCREMENT_FLAG) - return report_duckdb_table_struct_error( - "AUTO_INCREMENT", "removing AUTO_INCREMENT from column", - m_field->field_name.str, m_ctx); + /* + AUTO_INCREMENT is supported by CREATE TABLE only. Adding it to an + existing table would require seeding the sequence from the current + column maximum, which is not implemented yet. + */ + if ((m_field->flags & AUTO_INCREMENT_FLAG) && + m_ctx != ddl_error_context::CREATE) + { + /* + Same error the server raised while HA_NO_AUTO_INCREMENT was set, so + that the rejection stays indistinguishable from before. + */ + my_error(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT, MYF(0), "DUCKDB"); + return true; + } /* No support for INVISIBLE columns. */ if (m_field->invisible >= INVISIBLE_USER) @@ -503,8 +536,6 @@ std::string FieldConvertor::translate() } } - assert(!(field->flags & AUTO_INCREMENT_FLAG)); - return result.str(); } @@ -633,6 +664,20 @@ bool CreateTableConvertor::check() return true; } + /* + An ALTER using ALGORITHM=COPY builds a shadow table through create(), so + it arrives here rather than through the ALTER convertors. Reject + AUTO_INCREMENT on that path: the new sequence would start at 1 while the + copied rows already hold larger ids, silently handing out duplicates. + Seeding from the current column maximum is not implemented. + */ + if (m_thd->lex->sql_command != SQLCOM_CREATE_TABLE && + find_autoinc_field(m_table)) + { + my_error(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT, MYF(0), "DUCKDB"); + return true; + } + /* Check PK. */ TABLE_SHARE *share= m_table->s; @@ -679,6 +724,24 @@ std::string CreateTableConvertor::translate() result << "USE " << '"' << m_schema_name << '"' << ";"; + /* + The sequence must exist before the table, because the AUTO_INCREMENT + column default resolves it at CREATE TABLE bind time. + */ + if (find_autoinc_field(m_table)) + { + ulonglong start= m_create_info->auto_increment_value + ? m_create_info->auto_increment_value + : 1; + /* A DuckDB sequence counter is int64; never emit an unrepresentable start. */ + if (start > (ulonglong) INT64_MAX) + start= (ulonglong) INT64_MAX; + + result << "CREATE SEQUENCE IF NOT EXISTS " << '"' << m_schema_name << '"' + << "." << '"' << autoinc_sequence_name(m_table_name) << '"' + << " START WITH " << start << ";"; + } + result << CREATE_TABLE_STR; /* MariaDB: IF NOT EXISTS is handled at the SQL layer, not in HA_CREATE_INFO. Always use IF NOT EXISTS for safety in DuckDB. */ @@ -701,7 +764,16 @@ void CreateTableConvertor::append_column_definition(std::ostringstream &output) { if (ptr != first_field) output << ","; - output << FieldConvertor(field).translate(); + output << FieldConvertor(field, ddl_error_context::CREATE).translate(); + + /* + The AUTO_INCREMENT column draws from a DuckDB sequence. Keeping the + default in DuckDB lets writes that bypass MariaDB still obtain a + non-colliding id from the same counter. + */ + if (field->flags & AUTO_INCREMENT_FLAG) + output << " DEFAULT " + << autoinc_nextval_expr(m_schema_name, m_table_name); } } diff --git a/storage/duckdb/convertor/ddl_convertor.h b/storage/duckdb/convertor/ddl_convertor.h index a3a084ade88..75871fc066a 100644 --- a/storage/duckdb/convertor/ddl_convertor.h +++ b/storage/duckdb/convertor/ddl_convertor.h @@ -49,6 +49,16 @@ static inline bool is_uuid_field(const Field *field) field->type_handler()->type_collection() == uuid_coll; } +/** + Name of the DuckDB sequence backing the AUTO_INCREMENT column of a table. + MariaDB allows at most one AUTO_INCREMENT column per table, so the table + name alone identifies the sequence. +*/ +std::string autoinc_sequence_name(const std::string &table_name); + +/** The AUTO_INCREMENT field of a table, or nullptr if there is none. */ +Field *find_autoinc_field(const TABLE *table); + class BaseConvertor { public: diff --git a/storage/duckdb/ha_duckdb.cc b/storage/duckdb/ha_duckdb.cc index 79f5b8c3222..34400e647de 100644 --- a/storage/duckdb/ha_duckdb.cc +++ b/storage/duckdb/ha_duckdb.cc @@ -61,6 +61,7 @@ static duckdb_status_t srv_duckdb_status; static my_bool copy_ddl_in_batch= TRUE; static my_bool dml_in_batch= TRUE; static my_bool update_modified_column_only= TRUE; +static ulonglong autoinc_cache_size= 1000; static handler *duckdb_create_handler(handlerton *hton, TABLE_SHARE *table, MEM_ROOT *mem_root); @@ -262,7 +263,12 @@ static int duckdb_deinit_func(void *p) /* ----- Share management ----- */ -Duckdb_share::Duckdb_share() { thr_lock_init(&lock); } +Duckdb_share::Duckdb_share() +{ + thr_lock_init(&lock); + mysql_mutex_init(PSI_INSTRUMENT_ME, &autoinc_refill_mutex, + MY_MUTEX_INIT_FAST); +} Duckdb_share *ha_duckdb::get_share() { @@ -458,7 +464,7 @@ static void build_duckdb_blob_map(Field **field_list, MY_BITMAP *map) /* ----- DML operations ----- */ -int ha_duckdb::write_row(const uchar *) +int ha_duckdb::write_row(const uchar *buf) { DBUG_ENTER("ha_duckdb::write_row"); int ret= 0; @@ -474,6 +480,19 @@ int ha_duckdb::write_row(const uchar *) DBUG_RETURN(ret); } + /* + Fill the AUTO_INCREMENT field. Every row reaches DuckDB with all columns + materialized (both the Appender and the SQL insert paths), so the value + must be assigned here; the sequence-backed column default in DuckDB is + never consulted on this path. + */ + if (table->next_number_field && buf == table->record[0] && + (ret= update_auto_increment())) + { + dbug_tmp_restore_column_map(&table->read_set, org_bitmap); + DBUG_RETURN(ret); + } + myduck::BatchState batch_state= get_batch_state(thd); if (batch_state == myduck::BatchState::NOT_IN_BATCH) @@ -510,6 +529,119 @@ int ha_duckdb::write_row(const uchar *) DBUG_RETURN(ret); } +/* + Consume `want` ids from the table's AUTO_INCREMENT sequence in one query + and return the first id of the block, or 0 on failure (a sequence value is + never 0: CREATE SEQUENCE is emitted with START WITH >= 1). + + nextval() calls of a concurrent DuckDB-side writer (batch ingestion through + run_in_duckdb relying on the column default) may interleave with ours + inside this query. The ids missing from our span belong to that writer, so + such a block must not be handed out; detect it by comparing the span width + with the block size and retry. MariaDB-side callers cannot cause this: they + are serialized on the share's autoinc_refill_mutex. +*/ +static ulonglong reserve_autoinc_block(THD *thd, const char *db_name, + const char *table_name, ulonglong want) +{ + std::string query= + "SELECT min(x), max(x) FROM (SELECT nextval('\"" + + std::string(db_name) + "\".\"" + autoinc_sequence_name(table_name) + + "\"') AS x FROM range(" + std::to_string(want) + ")) t"; + + auto *ctx= get_duckdb_context(thd); + for (int attempt= 0; attempt < 3; attempt++) + { + auto result= myduck::duckdb_query(ctx->get_connection(), query); + if (result == nullptr || result->HasError()) + return 0; + auto chunk= result->Fetch(); + if (chunk == nullptr || chunk->size() == 0) + return 0; + int64_t lo= chunk->GetValue(0, 0).GetValue(); + int64_t hi= chunk->GetValue(1, 0).GetValue(); + if ((ulonglong) (hi - lo) + 1 == want) + return (ulonglong) lo; + } + return 0; +} + +void ha_duckdb::get_auto_increment(ulonglong offset, ulonglong increment, + ulonglong nb_desired_values, + ulonglong *first_value, + ulonglong *nb_reserved_values) +{ + DBUG_ENTER("ha_duckdb::get_auto_increment"); + + /* + The server rounds the first value up to the offset/increment grid + (handler.cc:4389: at most first + increment - 1) and then steps + `increment` at a time, so it uses ids <= first + nb_desired * increment + - 1. Consume exactly that many from the cache to keep the server inside + our block while leaving no unused ids behind. + */ + ulonglong need= nb_desired_values * increment; + if (need / increment != nb_desired_values) /* multiplication overflow */ + { + *first_value= ULONGLONG_MAX; + *nb_reserved_values= 0; + DBUG_VOID_RETURN; + } + + Duckdb_share::Autoinc_range cur= share->autoinc_range.load(); + for (;;) + { + /* Fast path: claim [cur.next, cur.next + need) with one CAS. */ + while (cur.end - cur.next >= need) + { + Duckdb_share::Autoinc_range claimed= {cur.next + need, cur.end}; + if (share->autoinc_range.compare_exchange_weak(cur, claimed)) + { + *first_value= cur.next; + *nb_reserved_values= nb_desired_values; + DBUG_VOID_RETURN; + } + /* CAS failure reloaded cur. */ + } + + mysql_mutex_lock(&share->autoinc_refill_mutex); + cur= share->autoinc_range.load(); + if (cur.end - cur.next >= need) + { + /* Someone refilled while we waited; back to the fast path. */ + mysql_mutex_unlock(&share->autoinc_refill_mutex); + continue; + } + + ulonglong want= std::max(need, autoinc_cache_size); + DatabaseTableNames dt(table->s->normalized_path.str); + ulonglong first= reserve_autoinc_block(ha_thd(), dt.db_name.c_str(), + dt.table_name.c_str(), want); + if (first == 0) + { + mysql_mutex_unlock(&share->autoinc_refill_mutex); + /* The only failure signal the server understands. */ + *first_value= ULONGLONG_MAX; + *nb_reserved_values= 0; + DBUG_VOID_RETURN; + } + /* + Publish the new block minus our own share. A plain store cannot clash + with concurrent fast-path claims: those still come from the old range, + which lies entirely below `first` because the sequence is monotonic. + The remainder of the old block is dropped, not merged: ids between the + two blocks may already belong to a DuckDB-side writer. The unused ids + become gaps, which AUTO_INCREMENT permits. + */ + share->autoinc_range.store({first + need, first + want}); + mysql_mutex_unlock(&share->autoinc_refill_mutex); + + *first_value= first; + *nb_reserved_values= nb_desired_values; + DBUG_VOID_RETURN; + } +} + int ha_duckdb::update_row(const uchar *old_row, const uchar *new_row) { DBUG_ENTER("ha_duckdb::update_row"); @@ -1012,6 +1144,19 @@ int ha_duckdb::delete_table(const char *name) if (query_result == nullptr || query_result->HasError()) DBUG_RETURN(HA_DUCKDB_DROP_TABLE_ERROR); + /* + Drop the AUTO_INCREMENT sequence, if any. It must go after the table: + while the column default exists the catalog dependency blocks the drop. + The name is derived from the table alone, so no lookup is needed and + the statement is harmless for tables without AUTO_INCREMENT. + */ + std::string seq_query= "DROP SEQUENCE IF EXISTS \"" + dt.db_name + "\".\"" + + autoinc_sequence_name(dt.table_name) + "\""; + auto seq_result= myduck::duckdb_query(ctx->get_connection(), seq_query); + + if (seq_result == nullptr || seq_result->HasError()) + DBUG_RETURN(HA_DUCKDB_DROP_TABLE_ERROR); + DBUG_RETURN(0); } @@ -1229,6 +1374,12 @@ static MYSQL_SYSVAR_BOOL(update_modified_column_only, "Whether to only update modified columns", NULL, NULL, TRUE); +static MYSQL_SYSVAR_ULONGLONG(autoinc_cache_size, autoinc_cache_size, + PLUGIN_VAR_RQCMDARG, + "Number of AUTO_INCREMENT ids reserved from the " + "backing DuckDB sequence per query", + NULL, NULL, 1000, 1, 1048576, 0); + /* ---- Global proxy variables (pushed into DuckDB) ---- */ static MYSQL_SYSVAR_ULONGLONG(memory_limit, myduck::global_memory_limit, @@ -1355,6 +1506,7 @@ static struct st_mysql_sys_var *duckdb_system_variables[]= { MYSQL_SYSVAR(allow_run_in_duckdb), MYSQL_SYSVAR(copy_ddl_in_batch), MYSQL_SYSVAR(dml_in_batch), MYSQL_SYSVAR(update_modified_column_only), + MYSQL_SYSVAR(autoinc_cache_size), /* Global proxy */ MYSQL_SYSVAR(memory_limit), MYSQL_SYSVAR(temp_directory), MYSQL_SYSVAR(max_temp_directory_size), MYSQL_SYSVAR(max_threads), diff --git a/storage/duckdb/ha_duckdb.h b/storage/duckdb/ha_duckdb.h index b0ff93d2471..4f32bbebe10 100644 --- a/storage/duckdb/ha_duckdb.h +++ b/storage/duckdb/ha_duckdb.h @@ -21,6 +21,7 @@ #define HA_DUCKDB_H #include +#include #include #include "my_global.h" @@ -47,8 +48,30 @@ class Duckdb_share : public Handler_share { public: THR_LOCK lock; + /* + AUTO_INCREMENT block cache. Blocks are drawn from the table's DuckDB + sequence in get_auto_increment(); the cache spreads the cost of that + query over duckdb_autoinc_cache_size ids. + + The fast path claims ids with one CAS on the {next, end} pair. Both + fields must move together: with independent atomics a reader could pair + a stale bound with a fresh one and claim ids of a dropped block gap, + which may belong to a DuckDB-side writer. The mutex only serializes + block refills. + */ + struct Autoinc_range + { + ulonglong next; ///< next unused id of the cached block + ulonglong end; ///< one past the last id of the cached block + }; + alignas(16) std::atomic autoinc_range{{0, 0}}; + mysql_mutex_t autoinc_refill_mutex; Duckdb_share(); - ~Duckdb_share() { thr_lock_delete(&lock); } + ~Duckdb_share() + { + thr_lock_delete(&lock); + mysql_mutex_destroy(&autoinc_refill_mutex); + } }; /** @brief @@ -69,7 +92,7 @@ public: ulonglong table_flags() const override { return (HA_BINLOG_STMT_CAPABLE | HA_BINLOG_ROW_CAPABLE | - HA_NO_AUTO_INCREMENT | HA_NULL_IN_KEY | HA_CAN_INDEX_BLOBS | + HA_NULL_IN_KEY | HA_CAN_INDEX_BLOBS | HA_CAN_DIRECT_UPDATE_AND_DELETE); } @@ -111,6 +134,9 @@ public: int close(void) override; int write_row(const uchar *buf) override; + void get_auto_increment(ulonglong offset, ulonglong increment, + ulonglong nb_desired_values, ulonglong *first_value, + ulonglong *nb_reserved_values) override; int update_row(const uchar *old_data, const uchar *new_data) override; int delete_row(const uchar *buf) override; diff --git a/storage/duckdb/mysql-test/duckdb/include/alter_duckdb_column.inc b/storage/duckdb/mysql-test/duckdb/include/alter_duckdb_column.inc index 0883ef37454..4e28aa30087 100644 --- a/storage/duckdb/mysql-test/duckdb/include/alter_duckdb_column.inc +++ b/storage/duckdb/mysql-test/duckdb/include/alter_duckdb_column.inc @@ -292,11 +292,14 @@ eval ALTER TABLE t ADD COLUMN d INT INVISIBLE, ALGORITHM = $algorithm; --error ER_PARSE_ERROR eval ALTER TABLE t ALTER COLUMN b SET INVISIBLE, ALGORITHM = $algorithm; -# AUTO_INCREMENT +# AUTO_INCREMENT is supported by CREATE TABLE only. +# 'c INT AUTO_INCREMENT KEY' would be rejected by the parser instead (bare KEY +# means PRIMARY KEY and t already has one), and AUTO_INCREMENT without any key +# is rejected by the server, so neither would reach the engine. --error ER_TABLE_CANT_HANDLE_AUTO_INCREMENT -eval ALTER TABLE t MODIFY COLUMN c INT AUTO_INCREMENT KEY, ALGORITHM = $algorithm; +eval ALTER TABLE t MODIFY COLUMN id INT AUTO_INCREMENT, ALGORITHM = $algorithm; --error ER_TABLE_CANT_HANDLE_AUTO_INCREMENT -eval ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT, ALGORITHM = $algorithm; +eval ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT UNIQUE, ALGORITHM = $algorithm; # ENGINE_ATTRIBUTE --error ER_UNKNOWN_OPTION diff --git a/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result b/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result index 67b63d9d662..0e3040ddcc7 100644 --- a/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result +++ b/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column.result @@ -718,9 +718,9 @@ ALTER TABLE t ADD COLUMN d INT INVISIBLE, ALGORITHM = INSTANT; ERROR HY000: Got error 168 'INVISIBLE column is not supported. Try removing INVISIBLE from column 'd'' from DuckDB ALTER TABLE t ALTER COLUMN b SET INVISIBLE, ALGORITHM = INSTANT; 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 'INVISIBLE, ALGORITHM = INSTANT' at line 1 -ALTER TABLE t MODIFY COLUMN c INT AUTO_INCREMENT KEY, ALGORITHM = INSTANT; +ALTER TABLE t MODIFY COLUMN id INT AUTO_INCREMENT, ALGORITHM = INSTANT; ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns -ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT, ALGORITHM = INSTANT; +ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT UNIQUE, ALGORITHM = INSTANT; ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns CREATE TABLE t1(id INT PRIMARY KEY, a INT) ENGINE = DuckDB ENGINE_ATTRIBUTE='{"KEY":"VALUE"}'; ERROR HY000: Unknown option 'ENGINE_ATTRIBUTE' @@ -1596,9 +1596,9 @@ ALTER TABLE t ADD COLUMN d INT INVISIBLE, ALGORITHM = INPLACE; ERROR HY000: Got error 168 'INVISIBLE column is not supported. Try removing INVISIBLE from column 'd'' from DuckDB ALTER TABLE t ALTER COLUMN b SET INVISIBLE, ALGORITHM = INPLACE; 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 'INVISIBLE, ALGORITHM = INPLACE' at line 1 -ALTER TABLE t MODIFY COLUMN c INT AUTO_INCREMENT KEY, ALGORITHM = INPLACE; +ALTER TABLE t MODIFY COLUMN id INT AUTO_INCREMENT, ALGORITHM = INPLACE; ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns -ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT, ALGORITHM = INPLACE; +ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT UNIQUE, ALGORITHM = INPLACE; ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns CREATE TABLE t1(id INT PRIMARY KEY, a INT) ENGINE = DuckDB ENGINE_ATTRIBUTE='{"KEY":"VALUE"}'; ERROR HY000: Unknown option 'ENGINE_ATTRIBUTE' diff --git a/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column_copy.result b/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column_copy.result index a8158a2afb0..3dc59f19a2d 100644 --- a/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column_copy.result +++ b/storage/duckdb/mysql-test/duckdb/r/alter_duckdb_column_copy.result @@ -718,9 +718,9 @@ ALTER TABLE t ADD COLUMN d INT INVISIBLE, ALGORITHM = COPY; ERROR HY000: Table storage engine 'DuckDB' does not support the create option 'INVISIBLE column' ALTER TABLE t ALTER COLUMN b SET INVISIBLE, ALGORITHM = COPY; 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 'INVISIBLE, ALGORITHM = COPY' at line 1 -ALTER TABLE t MODIFY COLUMN c INT AUTO_INCREMENT KEY, ALGORITHM = COPY; +ALTER TABLE t MODIFY COLUMN id INT AUTO_INCREMENT, ALGORITHM = COPY; ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns -ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT, ALGORITHM = COPY; +ALTER TABLE t ADD COLUMN d INT AUTO_INCREMENT UNIQUE, ALGORITHM = COPY; ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns CREATE TABLE t1(id INT PRIMARY KEY, a INT) ENGINE = DuckDB ENGINE_ATTRIBUTE='{"KEY":"VALUE"}'; ERROR HY000: Unknown option 'ENGINE_ATTRIBUTE' diff --git a/storage/duckdb/mysql-test/duckdb/r/duckdb_auto_increment.result b/storage/duckdb/mysql-test/duckdb/r/duckdb_auto_increment.result new file mode 100644 index 00000000000..20faf8a00c1 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/duckdb_auto_increment.result @@ -0,0 +1,204 @@ +SET NAMES utf8mb4; +# +# MDEV-40266: AUTO_INCREMENT backed by a DuckDB sequence +# +# --- CREATE TABLE creates the sequence ------------------------------- +CREATE TABLE t (id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, a INT) ENGINE=DuckDB; +SHOW CREATE TABLE t; +Table Create Table +t CREATE TABLE `t` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `a` int(11) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=DUCKDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +# The sequence must exist in DuckDB, named after the table. +SELECT run_in_duckdb("SELECT schema_name, sequence_name, start_value FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' ORDER BY sequence_name"); +run_in_duckdb("SELECT schema_name, sequence_name, start_value FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' ORDER BY sequence_name") +schema_name sequence_name start_value +VARCHAR VARCHAR BIGINT +[ Rows: 1] +db_autoinc mdb_autoinc_t 1 + + +# The column default must reference that sequence, so that writes which +# bypass MariaDB still draw a non-colliding id from the same counter. +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_autoinc' AND table_name = 't' ORDER BY column_name"); +run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_autoinc' AND table_name = 't' ORDER BY column_name") +column_name column_default +VARCHAR VARCHAR +[ Rows: 2] +a NULL +id nextval('"db_autoinc"."mdb_autoinc_t"') + + +# --- an insert bypassing MariaDB uses the sequence -------------------- +SELECT run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(10)"); +run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(10)") +Count +BIGINT +[ Rows: 1] +1 + + +SELECT run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(20)"); +run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(20)") +Count +BIGINT +[ Rows: 1] +1 + + +SELECT id, a FROM t ORDER BY id; +id a +1 10 +2 20 +# --- MariaDB-side inserts draw from the same sequence ------------------ +INSERT INTO t(a) VALUES(30); +SELECT LAST_INSERT_ID(); +LAST_INSERT_ID() +3 +INSERT INTO t(a) VALUES(40); +SELECT LAST_INSERT_ID(); +LAST_INSERT_ID() +4 +# A multi-row insert reserves its ids in one go. +INSERT INTO t(a) VALUES(50),(60),(70); +SELECT LAST_INSERT_ID(); +LAST_INSERT_ID() +5 +SELECT id, a FROM t ORDER BY id; +id a +1 10 +2 20 +3 30 +4 40 +5 50 +6 60 +7 70 +# A DuckDB-side insert after MariaDB ones must not collide: the block +# reservation advanced the shared counter past the cached ids. +SELECT run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(80)"); +run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(80)") +Count +BIGINT +[ Rows: 1] +1 + + +SELECT count(*), count(DISTINCT id) FROM t; +count(*) count(DISTINCT id) +8 8 +# --- START WITH honours CREATE TABLE ... AUTO_INCREMENT=N ------------- +CREATE TABLE t2 (id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, a INT) ENGINE=DuckDB AUTO_INCREMENT=100; +SELECT run_in_duckdb("SELECT sequence_name, start_value FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t2'"); +run_in_duckdb("SELECT sequence_name, start_value FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t2'") +sequence_name start_value +VARCHAR BIGINT +[ Rows: 1] +mdb_autoinc_t2 100 + + +SELECT run_in_duckdb("INSERT INTO db_autoinc.t2(a) VALUES(1)"); +run_in_duckdb("INSERT INTO db_autoinc.t2(a) VALUES(1)") +Count +BIGINT +[ Rows: 1] +1 + + +INSERT INTO t2(a) VALUES(2); +SELECT id, a FROM t2 ORDER BY id; +id a +100 1 +101 2 +# --- a table without AUTO_INCREMENT gets no sequence ------------------ +CREATE TABLE t3 (id INT PRIMARY KEY, a INT) ENGINE=DuckDB; +SELECT run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t3'"); +run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t3'") +count_star() +BIGINT +[ Rows: 1] +0 + + +# AUTO_INCREMENT is accepted by CREATE TABLE only; seeding an existing +# column from its current maximum is not implemented. +ALTER TABLE t3 MODIFY COLUMN id INT AUTO_INCREMENT; +ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns +ALTER TABLE t3 ADD COLUMN b INT AUTO_INCREMENT UNIQUE; +ERROR 42000: Storage engine DUCKDB doesn't support AUTO_INCREMENT columns +# The rejected ALTER must leave the table untouched. +SHOW CREATE TABLE t3; +Table Create Table +t3 CREATE TABLE `t3` ( + `id` int(11) NOT NULL, + `a` int(11) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=DUCKDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +# --- setval() is available (local backport of DuckDB PR #24061) -------- +SELECT run_in_duckdb("CREATE SEQUENCE db_autoinc.check_setval"); +run_in_duckdb("CREATE SEQUENCE db_autoinc.check_setval") +Count +BIGINT +[ Rows: 0] + + +SELECT run_in_duckdb("SELECT setval('db_autoinc.check_setval', 42)"); +run_in_duckdb("SELECT setval('db_autoinc.check_setval', 42)") +setval('db_autoinc.check_setval', 42) +BIGINT +[ Rows: 1] +42 + + +SELECT run_in_duckdb("SELECT nextval('db_autoinc.check_setval')"); +run_in_duckdb("SELECT nextval('db_autoinc.check_setval')") +nextval('db_autoinc.check_setval') +BIGINT +[ Rows: 1] +43 + + +SELECT run_in_duckdb("SELECT setval('db_autoinc.check_setval', 100, false)"); +run_in_duckdb("SELECT setval('db_autoinc.check_setval', 100, false)") +setval('db_autoinc.check_setval', 100, CAST('f' AS BOOLEAN)) +BIGINT +[ Rows: 1] +100 + + +SELECT run_in_duckdb("SELECT nextval('db_autoinc.check_setval')"); +run_in_duckdb("SELECT nextval('db_autoinc.check_setval')") +nextval('db_autoinc.check_setval') +BIGINT +[ Rows: 1] +100 + + +SELECT run_in_duckdb("DROP SEQUENCE db_autoinc.check_setval"); +run_in_duckdb("DROP SEQUENCE db_autoinc.check_setval") +Success +BOOLEAN +[ Rows: 0] + + +# --- DROP TABLE removes the sequence ---------------------------------- +DROP TABLE t; +SELECT run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t'"); +run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t'") +count_star() +BIGINT +[ Rows: 1] +0 + + +DROP TABLE t2; +DROP TABLE t3; +SELECT run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc'"); +run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc'") +count_star() +BIGINT +[ Rows: 1] +0 + + diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test new file mode 100644 index 00000000000..b0614b1992e --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test @@ -0,0 +1,84 @@ +--source ../include/have_duckdb.inc + +--disable_query_log +CREATE DATABASE IF NOT EXISTS db_autoinc CHARACTER SET utf8mb4; +USE db_autoinc; +--enable_query_log +SET NAMES utf8mb4; + +--echo # +--echo # MDEV-40266: AUTO_INCREMENT backed by a DuckDB sequence +--echo # + +--echo # --- CREATE TABLE creates the sequence ------------------------------- +CREATE TABLE t (id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, a INT) ENGINE=DuckDB; + +SHOW CREATE TABLE t; + +--echo # The sequence must exist in DuckDB, named after the table. +SELECT run_in_duckdb("SELECT schema_name, sequence_name, start_value FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' ORDER BY sequence_name"); + +--echo # The column default must reference that sequence, so that writes which +--echo # bypass MariaDB still draw a non-colliding id from the same counter. +SELECT run_in_duckdb("SELECT column_name, column_default FROM information_schema.columns WHERE table_schema = 'db_autoinc' AND table_name = 't' ORDER BY column_name"); + +--echo # --- an insert bypassing MariaDB uses the sequence -------------------- +SELECT run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(10)"); +SELECT run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(20)"); +SELECT id, a FROM t ORDER BY id; + +--echo # --- MariaDB-side inserts draw from the same sequence ------------------ +INSERT INTO t(a) VALUES(30); +SELECT LAST_INSERT_ID(); +INSERT INTO t(a) VALUES(40); +SELECT LAST_INSERT_ID(); + +--echo # A multi-row insert reserves its ids in one go. +INSERT INTO t(a) VALUES(50),(60),(70); +SELECT LAST_INSERT_ID(); +SELECT id, a FROM t ORDER BY id; + +--echo # A DuckDB-side insert after MariaDB ones must not collide: the block +--echo # reservation advanced the shared counter past the cached ids. +SELECT run_in_duckdb("INSERT INTO db_autoinc.t(a) VALUES(80)"); +SELECT count(*), count(DISTINCT id) FROM t; + +--echo # --- START WITH honours CREATE TABLE ... AUTO_INCREMENT=N ------------- +CREATE TABLE t2 (id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, a INT) ENGINE=DuckDB AUTO_INCREMENT=100; +SELECT run_in_duckdb("SELECT sequence_name, start_value FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t2'"); +SELECT run_in_duckdb("INSERT INTO db_autoinc.t2(a) VALUES(1)"); +INSERT INTO t2(a) VALUES(2); +SELECT id, a FROM t2 ORDER BY id; + +--echo # --- a table without AUTO_INCREMENT gets no sequence ------------------ +CREATE TABLE t3 (id INT PRIMARY KEY, a INT) ENGINE=DuckDB; +SELECT run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t3'"); + +--echo # AUTO_INCREMENT is accepted by CREATE TABLE only; seeding an existing +--echo # column from its current maximum is not implemented. +--error ER_TABLE_CANT_HANDLE_AUTO_INCREMENT +ALTER TABLE t3 MODIFY COLUMN id INT AUTO_INCREMENT; +--error ER_TABLE_CANT_HANDLE_AUTO_INCREMENT +ALTER TABLE t3 ADD COLUMN b INT AUTO_INCREMENT UNIQUE; +--echo # The rejected ALTER must leave the table untouched. +SHOW CREATE TABLE t3; + +--echo # --- setval() is available (local backport of DuckDB PR #24061) -------- +SELECT run_in_duckdb("CREATE SEQUENCE db_autoinc.check_setval"); +SELECT run_in_duckdb("SELECT setval('db_autoinc.check_setval', 42)"); +SELECT run_in_duckdb("SELECT nextval('db_autoinc.check_setval')"); +SELECT run_in_duckdb("SELECT setval('db_autoinc.check_setval', 100, false)"); +SELECT run_in_duckdb("SELECT nextval('db_autoinc.check_setval')"); +SELECT run_in_duckdb("DROP SEQUENCE db_autoinc.check_setval"); + +--echo # --- DROP TABLE removes the sequence ---------------------------------- +DROP TABLE t; +SELECT run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc' AND sequence_name = 'mdb_autoinc_t'"); + +DROP TABLE t2; +DROP TABLE t3; +SELECT run_in_duckdb("SELECT count(*) FROM duckdb_sequences() WHERE schema_name = 'db_autoinc'"); + +--disable_query_log +DROP DATABASE db_autoinc; +--enable_query_log diff --git a/storage/duckdb/patches/duckdb-pr24061-setval.diff b/storage/duckdb/patches/duckdb-pr24061-setval.diff new file mode 100644 index 00000000000..68965fa3504 --- /dev/null +++ b/storage/duckdb/patches/duckdb-pr24061-setval.diff @@ -0,0 +1,179 @@ +diff --git a/src/catalog/catalog_entry/sequence_catalog_entry.cpp b/src/catalog/catalog_entry/sequence_catalog_entry.cpp +index d6a548a267..dbec7a91ac 100644 +--- a/src/catalog/catalog_entry/sequence_catalog_entry.cpp ++++ b/src/catalog/catalog_entry/sequence_catalog_entry.cpp +@@ -81,6 +81,28 @@ int64_t SequenceCatalogEntry::NextValue(DuckTransaction &transaction) { + return result; + } + ++int64_t SequenceCatalogEntry::SetValue(DuckTransaction &transaction, int64_t value, bool is_called) { ++ { ++ lock_guard seqlock(lock); ++ if (value < data.min_value || value > data.max_value) { ++ throw SequenceException("setval: value %lld is out of bounds for sequence \"%s\" (%lld..%lld)", value, name, ++ data.min_value, data.max_value); ++ } ++ ++ data.counter = value; ++ if (!is_called) { ++ data.usage_count++; ++ if (!temporary) { ++ transaction.PushSequenceUsage(*this, data); ++ } ++ return value; ++ } ++ } ++ ++ // is_called: behave as if nextval() was just invoked and returned `value`. ++ return NextValue(transaction); ++} ++ + void SequenceCatalogEntry::ReplayValue(uint64_t v_usage_count, int64_t v_counter) { + if (v_usage_count > data.usage_count) { + data.usage_count = v_usage_count; +diff --git a/src/function/function_list.cpp b/src/function/function_list.cpp +index 4e269f0604..58bf369e01 100644 +--- a/src/function/function_list.cpp ++++ b/src/function/function_list.cpp +@@ -153,6 +153,7 @@ static const StaticFunctionDefinition function[] = { + DUCKDB_SCALAR_FUNCTION_SET_ALIAS(RegexpSplitToArrayFun), + DUCKDB_SCALAR_FUNCTION(RemapStructFun), + DUCKDB_SCALAR_FUNCTION(RowFun), ++ DUCKDB_SCALAR_FUNCTION_SET(SetvalFun), + DUCKDB_SCALAR_FUNCTION_SET(SHA1Fun), + DUCKDB_SCALAR_FUNCTION_SET(SHA256Fun), + DUCKDB_SCALAR_FUNCTION_ALIAS(SplitFun), +diff --git a/src/function/scalar/sequence/nextval.cpp b/src/function/scalar/sequence/nextval.cpp +index 8a53af7bbb..366afcbc3d 100644 +--- a/src/function/scalar/sequence/nextval.cpp ++++ b/src/function/scalar/sequence/nextval.cpp +@@ -20,17 +20,24 @@ namespace duckdb { + namespace { + + struct CurrentSequenceValueOperator { +- static int64_t Operation(DuckTransaction &, SequenceCatalogEntry &seq) { ++ static int64_t Operation(DuckTransaction &, SequenceCatalogEntry &seq, const int64_t, const bool) { + return seq.CurrentValue(); + } + }; + + struct NextSequenceValueOperator { +- static int64_t Operation(DuckTransaction &transaction, SequenceCatalogEntry &seq) { ++ static int64_t Operation(DuckTransaction &transaction, SequenceCatalogEntry &seq, const int64_t, const bool) { + return seq.NextValue(transaction); + } + }; + ++struct SetValValueOperator { ++ static int64_t Operation(DuckTransaction &transaction, SequenceCatalogEntry &seq, const int64_t value, ++ const bool is_called) { ++ return seq.SetValue(transaction, value, is_called); ++ } ++}; ++ + SequenceCatalogEntry &BindSequence(Binder &binder, string &catalog, string &schema, const string &name) { + // fetch the sequence from the catalog + Binder::BindSchemaOrCatalog(binder.context, catalog, schema); +@@ -83,10 +90,44 @@ void NextValFunction(DataChunk &args, ExpressionState &state, Vector &result) { + // sequence to use is hard coded + // increment the sequence + result.SetVectorType(VectorType::FLAT_VECTOR); ++ ++ // setval takes the new value (and optionally is_called) as extra arguments ++ UnifiedVectorFormat new_val_data; ++ UnifiedVectorFormat is_called_data; ++ bool has_new_val = false; ++ bool has_is_called = false; ++ if (std::is_same::value) { ++ args.data[1].ToUnifiedFormat(args.size(), new_val_data); ++ has_new_val = true; ++ if (args.ColumnCount() == 3) { ++ args.data[2].ToUnifiedFormat(args.size(), is_called_data); ++ has_is_called = true; ++ } ++ } ++ + auto result_data = FlatVector::GetData(result); ++ auto &result_validity = FlatVector::Validity(result); + for (idx_t i = 0; i < args.size(); i++) { ++ int64_t value = 0; ++ bool is_called = true; ++ if (has_new_val) { ++ auto idx = new_val_data.sel->get_index(i); ++ if (!new_val_data.validity.RowIsValid(idx)) { ++ result_validity.SetInvalid(i); ++ continue; ++ } ++ value = UnifiedVectorFormat::GetData(new_val_data)[idx]; ++ } ++ if (has_is_called) { ++ auto idx = is_called_data.sel->get_index(i); ++ if (!is_called_data.validity.RowIsValid(idx)) { ++ result_validity.SetInvalid(i); ++ continue; ++ } ++ is_called = UnifiedVectorFormat::GetData(is_called_data)[idx]; ++ } + // get the next value from the sequence +- result_data[i] = OP::Operation(lstate.transaction, lstate.sequence); ++ result_data[i] = OP::Operation(lstate.transaction, lstate.sequence, value, is_called); + } + } + +@@ -162,4 +203,25 @@ ScalarFunction CurrvalFun::GetFunction() { + return curr_val; + } + ++ScalarFunctionSet SetvalFun::GetFunctions() { ++ ScalarFunction set_val("setval", {LogicalType::VARCHAR, LogicalType::BIGINT}, LogicalType::BIGINT, ++ NextValFunction, nullptr, nullptr); ++ set_val.SetBindExtendedCallback(NextValBind); ++ set_val.SetSerializeCallback(Serialize); ++ set_val.SetDeserializeCallback(Deserialize); ++ set_val.SetModifiedDatabasesCallback(NextValModifiedDatabases); ++ set_val.SetInitStateCallback(NextValLocalFunction); ++ set_val.SetVolatile(); ++ set_val.SetFallible(); ++ ++ ScalarFunctionSet set_val_set; ++ set_val_set.AddFunction(set_val); ++ ++ // Add an overload that takes an additional boolean parameter ++ set_val.arguments.push_back(LogicalType::BOOLEAN); ++ set_val_set.AddFunction(set_val); ++ ++ return set_val_set; ++} ++ + } // namespace duckdb +diff --git a/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp b/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp +index ed12b77a1c..19d3737fb8 100644 +--- a/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp ++++ b/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp +@@ -61,6 +61,7 @@ public: + SequenceData GetData() const; + int64_t CurrentValue(); + int64_t NextValue(DuckTransaction &transaction); ++ int64_t SetValue(DuckTransaction &transaction, int64_t value, bool is_called); + void ReplayValue(uint64_t usage_count, int64_t counter); + + string ToSQL() const override; +diff --git a/src/include/duckdb/function/scalar/sequence_functions.hpp b/src/include/duckdb/function/scalar/sequence_functions.hpp +index 99766a3326..44b8224006 100644 +--- a/src/include/duckdb/function/scalar/sequence_functions.hpp ++++ b/src/include/duckdb/function/scalar/sequence_functions.hpp +@@ -35,4 +35,14 @@ struct NextvalFun { + static ScalarFunction GetFunction(); + }; + ++struct SetvalFun { ++ static constexpr const char *Name = "setval"; ++ static constexpr const char *Parameters = "sequence_name,value[,is_called]"; ++ static constexpr const char *Description = "Set the value of the sequence to the specified value. If is_called is true, the sequence is called. If is_called is false, the sequence is not called."; ++ static constexpr const char *Example = "setval('my_sequence_name', 100, true)"; ++ static constexpr const char *Categories = ""; ++ ++ static ScalarFunctionSet GetFunctions(); ++}; ++ + } // namespace duckdb