From 218e052f934b6e3a9156819ca41264ef3be221e8 Mon Sep 17 00:00:00 2001 From: Mikhail Kot Date: Mon, 7 Sep 2026 15:26:44 +0100 Subject: [PATCH 1/4] vortex_version() macro Signed-off-by: Mikhail Kot --- vortex-duckdb/cpp/include/table_function.h | 4 ++ vortex-duckdb/cpp/table_function.cpp | 37 +++++++++++++++++++ vortex-duckdb/src/duckdb/database.rs | 25 +++++++++++++ .../src/e2e_test/vortex_scan_test.rs | 19 ++++++++++ vortex-duckdb/src/lib.rs | 12 ++++++ 5 files changed, 97 insertions(+) diff --git a/vortex-duckdb/cpp/include/table_function.h b/vortex-duckdb/cpp/include/table_function.h index 954cae2dec4..8ec6ad22568 100644 --- a/vortex-duckdb/cpp/include/table_function.h +++ b/vortex-duckdb/cpp/include/table_function.h @@ -77,6 +77,10 @@ typedef struct { duckdb_state duckdb_vx_register_table_functions(duckdb_database ffi_db); +duckdb_state duckdb_vx_register_version_function(duckdb_database ffi_db, + const char *const *editions, + size_t editions_count); + typedef struct duckdb_vx_agg_input_ *duckdb_vx_agg_input; idx_t duckdb_vx_aggregate_len(duckdb_vx_agg_input ffi); duckdb_vx_expr duckdb_vx_aggregate_at(duckdb_vx_agg_input ffi, idx_t index, idx_t *proj_idx); diff --git a/vortex-duckdb/cpp/table_function.cpp b/vortex-duckdb/cpp/table_function.cpp index cd3d3e32913..b8468f11f0d 100644 --- a/vortex-duckdb/cpp/table_function.cpp +++ b/vortex-duckdb/cpp/table_function.cpp @@ -12,7 +12,9 @@ #include "duckdb.h" #include "duckdb/catalog/catalog.hpp" +#include "duckdb/catalog/default/default_table_functions.hpp" #include "duckdb/common/insertion_order_preserving_map.hpp" +#include "duckdb/parser/keyword_helper.hpp" #include "duckdb/common/multi_file/multi_file_reader.hpp" #include "duckdb/function/table_function.hpp" #include "duckdb/main/capi/capi_internal.hpp" @@ -220,6 +222,41 @@ duckdb_state register_table_function(DatabaseInstance &db, LogicalType parameter return DuckDBSuccess; } +extern "C" duckdb_state duckdb_vx_register_version_function(duckdb_database ffi_db, + const char *const *editions, + size_t editions_count) { + D_ASSERT(ffi_db); + D_ASSERT(editions || editions_count == 0); + const DatabaseWrapper &wrapper = *reinterpret_cast(ffi_db); + DatabaseInstance &db = *wrapper.database->instance; + + string sql = "SELECT unnest(["; + for (size_t i = 0; i < editions_count; i++) { + if (i > 0) { + sql += ", "; + } + sql += KeywordHelper::WriteQuoted(editions[i]); + } + sql += "]) AS edition"; + + const DefaultTableMacro macro {DEFAULT_SCHEMA, + "vortex_version", + {nullptr}, + {{nullptr, nullptr}}, + sql.c_str()}; + try { + auto info = DefaultTableFunctionGenerator::CreateTableMacroInfo(macro); + auto &system_catalog = Catalog::GetSystemCatalog(db); + auto data = CatalogTransaction::GetSystemTransaction(db); + system_catalog.CreateFunction(data, *info); + } catch (const std::exception &e) { + ErrorData data(e); + DUCKDB_LOG_ERROR(db, "Failed to create vortex_version table macro:\t" + data.Message()); + return DuckDBError; + } + return DuckDBSuccess; +} + extern "C" duckdb_state duckdb_vx_register_table_functions(duckdb_database ffi_db) { D_ASSERT(ffi_db); const DatabaseWrapper &wrapper = *reinterpret_cast(ffi_db); diff --git a/vortex-duckdb/src/duckdb/database.rs b/vortex-duckdb/src/duckdb/database.rs index 1d258f05751..2fbc576217a 100644 --- a/vortex-duckdb/src/duckdb/database.rs +++ b/vortex-duckdb/src/duckdb/database.rs @@ -62,6 +62,31 @@ impl DatabaseRef { Ok(()) } + pub fn register_version_function(&self, editions: &[String]) -> VortexResult<()> { + let editions = editions + .iter() + .map(|edition| { + CString::new(edition.as_str()) + .map_err(|_| vortex_err!("Invalid edition: string contains null bytes")) + }) + .collect::>>()?; + let pointers = editions + .iter() + .map(|edition| edition.as_ptr()) + .collect::>(); + duckdb_try!( + unsafe { + cpp::duckdb_vx_register_version_function( + self.as_ptr(), + pointers.as_ptr(), + pointers.len(), + ) + }, + "Failed to register vortex_version function" + ); + Ok(()) + } + pub fn register_optimizer_extension(&self) -> VortexResult<()> { duckdb_try!( unsafe { cpp::duckdb_vx_optimizer_extension_register(self.as_ptr()) }, diff --git a/vortex-duckdb/src/e2e_test/vortex_scan_test.rs b/vortex-duckdb/src/e2e_test/vortex_scan_test.rs index 0876be1ca4c..8fe7af307da 100644 --- a/vortex-duckdb/src/e2e_test/vortex_scan_test.rs +++ b/vortex-duckdb/src/e2e_test/vortex_scan_test.rs @@ -201,6 +201,25 @@ fn test_scan_function_registration() { assert_eq!(string, "vortex_scan"); } +#[test] +fn test_vortex_version() -> Result<()> { + let conn = database_connection(); + let result = conn.query("SELECT edition FROM vortex_version()")?; + let mut chunk = result.into_iter().next().unwrap(); + let len: usize = chunk.len().as_(); + + let editions = unsafe { chunk.get_vector_mut(0).as_slice_mut::(len) } + .iter_mut() + .map(String::from_duckdb_value) + .collect::>(); + + assert_eq!( + editions, + ["core2026.08.3", "json2026.08.0", "spatial2026.08.0"] + ); + Ok(()) +} + #[test] fn test_vortex_scan_strings() { let file = RUNTIME.block_on(async { diff --git a/vortex-duckdb/src/lib.rs b/vortex-duckdb/src/lib.rs index 0d1455fcfce..048088808af 100644 --- a/vortex-duckdb/src/lib.rs +++ b/vortex-duckdb/src/lib.rs @@ -78,10 +78,22 @@ fn init_tracing() { /// separately (e.g., before creating connections), call `register_extension_options` first. pub fn initialize(db: &DatabaseRef) -> VortexResult<()> { db.register_table_functions()?; + db.register_version_function(&enabled_editions())?; db.register_optimizer_extension()?; db.register_copy_function() } +fn enabled_editions() -> Vec { + let mut editions = SESSION + .enabled_editions() + .editions() + .iter() + .map(|edition| edition.to_string()) + .collect::>(); + editions.sort_unstable(); + editions +} + /// Initialize the DuckDB extension from a raw DuckDB database pointer. pub unsafe fn initialize_extension_from_raw(db: *mut c_void) { init_tracing(); From f50ddfa403cee10c21265a3a1eb6705cbe76e9ad Mon Sep 17 00:00:00 2001 From: Mikhail Kot Date: Mon, 7 Sep 2026 15:32:55 +0100 Subject: [PATCH 2/4] sql test Signed-off-by: Mikhail Kot --- vortex-sqllogictest/slt/duckdb/version.slt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 vortex-sqllogictest/slt/duckdb/version.slt diff --git a/vortex-sqllogictest/slt/duckdb/version.slt b/vortex-sqllogictest/slt/duckdb/version.slt new file mode 100644 index 00000000000..6e7c5da4c22 --- /dev/null +++ b/vortex-sqllogictest/slt/duckdb/version.slt @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright the Vortex contributors + +include ../setup.slt.no + +query T +SELECT edition FROM vortex_version(); +---- +core2026.08.3 +json2026.08.0 +spatial2026.08.0 From 8da8c7b0ca041eb9195eb5e161abc64b39b23602 Mon Sep 17 00:00:00 2001 From: Mikhail Kot Date: Wed, 9 Sep 2026 17:57:45 +0100 Subject: [PATCH 3/4] correctly propagate version instead of editions --- vortex-duckdb/build.rs | 27 +++++++++++++++++++ vortex-duckdb/cpp/include/table_function.h | 5 +--- vortex-duckdb/cpp/table_function.cpp | 15 +++-------- vortex-duckdb/src/duckdb/database.rs | 23 +++------------- .../src/e2e_test/vortex_scan_test.rs | 18 +++++-------- vortex-duckdb/src/lib.rs | 13 +-------- 6 files changed, 42 insertions(+), 59 deletions(-) diff --git a/vortex-duckdb/build.rs b/vortex-duckdb/build.rs index 825772b819e..2e8a1de6d64 100644 --- a/vortex-duckdb/build.rs +++ b/vortex-duckdb/build.rs @@ -627,6 +627,32 @@ fn cbindgen_rust2c(crate_dir: &Path) { } } +fn git(crate_dir: &Path, args: &[&str]) -> Option { + let output = Command::new("git") + .arg("-C") + .arg(crate_dir) + .args(args) + .output() + .ok()?; + if !output.status.success() { + return None; + } + let stdout = String::from_utf8(output.stdout).ok()?; + let stdout = stdout.trim(); + (!stdout.is_empty()).then(|| stdout.to_owned()) +} + +fn vortex_version(crate_dir: &Path) { + println!("cargo:rerun-if-env-changed=VORTEX_VERSION"); + let version = env::var("VORTEX_VERSION") + .ok() + .filter(|version| !version.is_empty()) + .or_else(|| git(crate_dir, &["describe", "--tags", "--exact-match", "HEAD"])) + .or_else(|| git(crate_dir, &["rev-parse", "HEAD"])) + .unwrap_or_else(|| "unknown".to_owned()); + println!("cargo:rustc-env=VORTEX_VERSION={version}"); +} + fn main() { println!("cargo:rerun-if-changed=cpp/include"); println!("cargo:rerun-if-changed=patches"); @@ -653,6 +679,7 @@ fn main() { // in vortex's CI. let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + vortex_version(&crate_dir); if let Some(source_dir) = env::var_os("DUCKDB_SOURCE_DIR") { let source_dir = PathBuf::from(source_dir); let duckdb_include_dir = source_dir.join("src").join("include"); diff --git a/vortex-duckdb/cpp/include/table_function.h b/vortex-duckdb/cpp/include/table_function.h index 8ec6ad22568..82090c26387 100644 --- a/vortex-duckdb/cpp/include/table_function.h +++ b/vortex-duckdb/cpp/include/table_function.h @@ -76,10 +76,7 @@ typedef struct { } duckdb_vx_written_column_statistics; duckdb_state duckdb_vx_register_table_functions(duckdb_database ffi_db); - -duckdb_state duckdb_vx_register_version_function(duckdb_database ffi_db, - const char *const *editions, - size_t editions_count); +duckdb_state duckdb_vx_register_version_function(duckdb_database ffi_db, const char *version); typedef struct duckdb_vx_agg_input_ *duckdb_vx_agg_input; idx_t duckdb_vx_aggregate_len(duckdb_vx_agg_input ffi); diff --git a/vortex-duckdb/cpp/table_function.cpp b/vortex-duckdb/cpp/table_function.cpp index b8468f11f0d..4045f3bd268 100644 --- a/vortex-duckdb/cpp/table_function.cpp +++ b/vortex-duckdb/cpp/table_function.cpp @@ -222,22 +222,13 @@ duckdb_state register_table_function(DatabaseInstance &db, LogicalType parameter return DuckDBSuccess; } -extern "C" duckdb_state duckdb_vx_register_version_function(duckdb_database ffi_db, - const char *const *editions, - size_t editions_count) { +extern "C" duckdb_state duckdb_vx_register_version_function(duckdb_database ffi_db, const char *version) { D_ASSERT(ffi_db); - D_ASSERT(editions || editions_count == 0); + D_ASSERT(version); const DatabaseWrapper &wrapper = *reinterpret_cast(ffi_db); DatabaseInstance &db = *wrapper.database->instance; - string sql = "SELECT unnest(["; - for (size_t i = 0; i < editions_count; i++) { - if (i > 0) { - sql += ", "; - } - sql += KeywordHelper::WriteQuoted(editions[i]); - } - sql += "]) AS edition"; + const string sql = "SELECT " + KeywordHelper::WriteQuoted(version) + " AS version"; const DefaultTableMacro macro {DEFAULT_SCHEMA, "vortex_version", diff --git a/vortex-duckdb/src/duckdb/database.rs b/vortex-duckdb/src/duckdb/database.rs index 2fbc576217a..53967a89d97 100644 --- a/vortex-duckdb/src/duckdb/database.rs +++ b/vortex-duckdb/src/duckdb/database.rs @@ -62,26 +62,11 @@ impl DatabaseRef { Ok(()) } - pub fn register_version_function(&self, editions: &[String]) -> VortexResult<()> { - let editions = editions - .iter() - .map(|edition| { - CString::new(edition.as_str()) - .map_err(|_| vortex_err!("Invalid edition: string contains null bytes")) - }) - .collect::>>()?; - let pointers = editions - .iter() - .map(|edition| edition.as_ptr()) - .collect::>(); + pub fn register_version_function(&self, version: &str) -> VortexResult<()> { + let version = CString::new(version) + .map_err(|_| vortex_err!("Invalid version: string contains null bytes"))?; duckdb_try!( - unsafe { - cpp::duckdb_vx_register_version_function( - self.as_ptr(), - pointers.as_ptr(), - pointers.len(), - ) - }, + unsafe { cpp::duckdb_vx_register_version_function(self.as_ptr(), version.as_ptr()) }, "Failed to register vortex_version function" ); Ok(()) diff --git a/vortex-duckdb/src/e2e_test/vortex_scan_test.rs b/vortex-duckdb/src/e2e_test/vortex_scan_test.rs index 8fe7af307da..1d655e5c87c 100644 --- a/vortex-duckdb/src/e2e_test/vortex_scan_test.rs +++ b/vortex-duckdb/src/e2e_test/vortex_scan_test.rs @@ -204,19 +204,13 @@ fn test_scan_function_registration() { #[test] fn test_vortex_version() -> Result<()> { let conn = database_connection(); - let result = conn.query("SELECT edition FROM vortex_version()")?; - let mut chunk = result.into_iter().next().unwrap(); - let len: usize = chunk.len().as_(); - - let editions = unsafe { chunk.get_vector_mut(0).as_slice_mut::(len) } - .iter_mut() - .map(String::from_duckdb_value) - .collect::>(); - - assert_eq!( - editions, - ["core2026.08.3", "json2026.08.0", "spatial2026.08.0"] + let query = format!( + "SELECT count(*) FROM vortex_version() WHERE version = '{}'", + env!("VORTEX_VERSION") ); + let result = conn.query(&query)?; + let chunk = result.into_iter().next().unwrap(); + assert_eq!(chunk.get_vector(0).as_slice_with_len::(1), [1]); Ok(()) } diff --git a/vortex-duckdb/src/lib.rs b/vortex-duckdb/src/lib.rs index 048088808af..eca3cba5a73 100644 --- a/vortex-duckdb/src/lib.rs +++ b/vortex-duckdb/src/lib.rs @@ -78,22 +78,11 @@ fn init_tracing() { /// separately (e.g., before creating connections), call `register_extension_options` first. pub fn initialize(db: &DatabaseRef) -> VortexResult<()> { db.register_table_functions()?; - db.register_version_function(&enabled_editions())?; + db.register_version_function(env!("VORTEX_VERSION"))?; db.register_optimizer_extension()?; db.register_copy_function() } -fn enabled_editions() -> Vec { - let mut editions = SESSION - .enabled_editions() - .editions() - .iter() - .map(|edition| edition.to_string()) - .collect::>(); - editions.sort_unstable(); - editions -} - /// Initialize the DuckDB extension from a raw DuckDB database pointer. pub unsafe fn initialize_extension_from_raw(db: *mut c_void) { init_tracing(); From a0565eac8f0ae6c51ddf208bdea112f4c32474ff Mon Sep 17 00:00:00 2001 From: Mikhail Kot Date: Wed, 9 Sep 2026 18:00:01 +0100 Subject: [PATCH 4/4] fix Signed-off-by: Mikhail Kot --- vortex-sqllogictest/slt/duckdb/version.slt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vortex-sqllogictest/slt/duckdb/version.slt b/vortex-sqllogictest/slt/duckdb/version.slt index 6e7c5da4c22..407f1ab853e 100644 --- a/vortex-sqllogictest/slt/duckdb/version.slt +++ b/vortex-sqllogictest/slt/duckdb/version.slt @@ -3,9 +3,7 @@ include ../setup.slt.no -query T -SELECT edition FROM vortex_version(); +query IB +SELECT count(*), length(min(version)) > 0 FROM vortex_version(); ---- -core2026.08.3 -json2026.08.0 -spatial2026.08.0 +1 true