mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
feat: DuckDB supports AUTO INCREMENT stanza.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>();
|
||||
int64_t hi= chunk->GetValue(1, 0).GetValue<int64_t>();
|
||||
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),
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#define HA_DUCKDB_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#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> 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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
204
storage/duckdb/mysql-test/duckdb/r/duckdb_auto_increment.result
Normal file
204
storage/duckdb/mysql-test/duckdb/r/duckdb_auto_increment.result
Normal file
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
179
storage/duckdb/patches/duckdb-pr24061-setval.diff
Normal file
179
storage/duckdb/patches/duckdb-pr24061-setval.diff
Normal file
@@ -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<mutex> 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<OP, SetValValueOperator>::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<int64_t>(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<int64_t>(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<bool>(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<SetValValueOperator>, 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
|
||||
Reference in New Issue
Block a user