@@ -45,6 +45,21 @@ pub trait VScalar: Sized {
4545 /// These will result in DuckDB scalar function overloads.
4646 /// The invoke method should be able to handle all of these signatures.
4747 fn signatures ( ) -> Vec < ScalarFunctionSignature > ;
48+
49+ /// Whether the scalar function is volatile.
50+ ///
51+ /// Volatile functions are re-evaluated for each row, even if they have no parameters.
52+ /// This is useful for functions that generate random or unique values, such as random
53+ /// number generators, UUID generators, or fake data generators.
54+ ///
55+ /// By default, DuckDB optimizes zero-argument scalar functions as constants, evaluating
56+ /// them only once. Returning true from this method prevents this optimization.
57+ ///
58+ /// # Default
59+ /// Returns `false` by default, meaning the function is not volatile.
60+ fn volatile ( ) -> bool {
61+ false
62+ }
4863}
4964
5065/// Duckdb scalar function parameters
@@ -144,6 +159,9 @@ impl Connection {
144159 let scalar_function = ScalarFunction :: new ( name) ?;
145160 signature. register_with_scalar ( & scalar_function) ;
146161 scalar_function. set_function ( Some ( scalar_func :: < S > ) ) ;
162+ if S :: volatile ( ) {
163+ scalar_function. set_volatile ( ) ;
164+ }
147165 scalar_function. set_extra_info ( S :: State :: default ( ) ) ;
148166 set. add_function ( scalar_function) ?;
149167 }
@@ -163,73 +181,9 @@ impl Connection {
163181 let scalar_function = ScalarFunction :: new ( name) ?;
164182 signature. register_with_scalar ( & scalar_function) ;
165183 scalar_function. set_function ( Some ( scalar_func :: < S > ) ) ;
166- scalar_function. set_extra_info ( state. clone ( ) ) ;
167- set. add_function ( scalar_function) ?;
168- }
169- self . db . borrow_mut ( ) . register_scalar_function_set ( set)
170- }
171-
172- /// Register the given ScalarFunction with default state, marked as volatile.
173- ///
174- /// Volatile functions are re-evaluated for each row, even if they have no parameters.
175- /// This is useful for functions that generate random or unique values per row, such as:
176- /// - Random number generators
177- /// - UUID generators
178- /// - Fake data generators
179- /// - Current timestamp functions
180- ///
181- /// By default, DuckDB optimizes zero-argument scalar functions as constants.
182- /// Use this method when you need the function to be evaluated independently for each row.
183- ///
184- /// # Example
185- /// ```no_run
186- /// use duckdb::Connection;
187- /// // Assume RandomUUID implements VScalar
188- /// let conn = Connection::open_in_memory()?;
189- /// conn.register_volatile_scalar_function::<RandomUUID>("random_uuid")?;
190- ///
191- /// // Each row gets a unique UUID
192- /// let mut stmt = conn.prepare("SELECT random_uuid() FROM generate_series(1, 10)")?;
193- /// # Ok::<(), duckdb::Error>(())
194- /// ```
195- #[ inline]
196- pub fn register_volatile_scalar_function < S : VScalar > ( & self , name : & str ) -> crate :: Result < ( ) >
197- where
198- S :: State : Default ,
199- {
200- let set = ScalarFunctionSet :: new ( name) ;
201- for signature in S :: signatures ( ) {
202- let scalar_function = ScalarFunction :: new ( name) ?;
203- signature. register_with_scalar ( & scalar_function) ;
204- scalar_function. set_function ( Some ( scalar_func :: < S > ) ) ;
205- scalar_function. set_volatile ( ) ; // Mark as volatile
206- scalar_function. set_extra_info ( S :: State :: default ( ) ) ;
207- set. add_function ( scalar_function) ?;
208- }
209- self . db . borrow_mut ( ) . register_scalar_function_set ( set)
210- }
211-
212- /// Register the given ScalarFunction with custom state, marked as volatile.
213- ///
214- /// Volatile functions are re-evaluated for each row, even if they have no parameters.
215- /// This is the volatile variant of `register_scalar_function_with_state`.
216- ///
217- /// See [`register_volatile_scalar_function`](Self::register_volatile_scalar_function) for more details on volatile functions.
218- #[ inline]
219- pub fn register_volatile_scalar_function_with_state < S : VScalar > (
220- & self ,
221- name : & str ,
222- state : & S :: State ,
223- ) -> crate :: Result < ( ) >
224- where
225- S :: State : Clone ,
226- {
227- let set = ScalarFunctionSet :: new ( name) ;
228- for signature in S :: signatures ( ) {
229- let scalar_function = ScalarFunction :: new ( name) ?;
230- signature. register_with_scalar ( & scalar_function) ;
231- scalar_function. set_function ( Some ( scalar_func :: < S > ) ) ;
232- scalar_function. set_volatile ( ) ; // Mark as volatile
184+ if S :: volatile ( ) {
185+ scalar_function. set_volatile ( ) ;
186+ }
233187 scalar_function. set_extra_info ( state. clone ( ) ) ;
234188 set. add_function ( scalar_function) ?;
235189 }
@@ -442,9 +396,10 @@ mod test {
442396 Ok ( ( ) )
443397 }
444398
445- // Counter for testing volatile functions
399+ // Counters for testing volatile functions
446400 use std:: sync:: atomic:: { AtomicU64 , Ordering } ;
447- static COUNTER : AtomicU64 = AtomicU64 :: new ( 0 ) ;
401+ static VOLATILE_COUNTER : AtomicU64 = AtomicU64 :: new ( 0 ) ;
402+ static NON_VOLATILE_COUNTER : AtomicU64 = AtomicU64 :: new ( 0 ) ;
448403
449404 struct CounterScalar { }
450405
@@ -461,7 +416,7 @@ mod test {
461416 let data = output_vec. as_mut_slice :: < i64 > ( ) ;
462417
463418 for i in 0 ..len {
464- let count = COUNTER . fetch_add ( 1 , Ordering :: SeqCst ) ;
419+ let count = NON_VOLATILE_COUNTER . fetch_add ( 1 , Ordering :: SeqCst ) ;
465420 data[ i] = count as i64 ;
466421 }
467422 Ok ( ( ) )
@@ -475,15 +430,48 @@ mod test {
475430 }
476431 }
477432
433+ struct VolatileCounterScalar { }
434+
435+ impl VScalar for VolatileCounterScalar {
436+ type State = ( ) ;
437+
438+ unsafe fn invoke (
439+ _: & Self :: State ,
440+ input : & mut DataChunkHandle ,
441+ output : & mut dyn WritableVector ,
442+ ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
443+ let len = input. len ( ) ;
444+ let mut output_vec = output. flat_vector ( ) ;
445+ let data = output_vec. as_mut_slice :: < i64 > ( ) ;
446+
447+ for i in 0 ..len {
448+ let count = VOLATILE_COUNTER . fetch_add ( 1 , Ordering :: SeqCst ) ;
449+ data[ i] = count as i64 ;
450+ }
451+ Ok ( ( ) )
452+ }
453+
454+ fn signatures ( ) -> Vec < ScalarFunctionSignature > {
455+ vec ! [ ScalarFunctionSignature :: exact(
456+ vec![ ] ,
457+ LogicalTypeHandle :: from( LogicalTypeId :: Bigint ) ,
458+ ) ]
459+ }
460+
461+ fn volatile ( ) -> bool {
462+ true
463+ }
464+ }
465+
478466 #[ test]
479467 fn test_volatile_scalar ( ) -> Result < ( ) , Box < dyn Error > > {
480468 let conn = Connection :: open_in_memory ( ) ?;
481469
482470 // Reset counter
483- COUNTER . store ( 0 , Ordering :: SeqCst ) ;
471+ VOLATILE_COUNTER . store ( 0 , Ordering :: SeqCst ) ;
484472
485- // Register as volatile
486- conn. register_volatile_scalar_function :: < CounterScalar > ( "volatile_counter" ) ?;
473+ // Register volatile counter
474+ conn. register_scalar_function :: < VolatileCounterScalar > ( "volatile_counter" ) ?;
487475
488476 // Query should get different values for each row
489477 let mut stmt = conn. prepare ( "SELECT volatile_counter() FROM generate_series(1, 5)" ) ?;
@@ -509,9 +497,9 @@ mod test {
509497 let conn = Connection :: open_in_memory ( ) ?;
510498
511499 // Reset counter
512- COUNTER . store ( 100 , Ordering :: SeqCst ) ;
500+ NON_VOLATILE_COUNTER . store ( 0 , Ordering :: SeqCst ) ;
513501
514- // Register WITHOUT volatile flag
502+ // Register non- volatile counter
515503 conn. register_scalar_function :: < CounterScalar > ( "non_volatile_counter" ) ?;
516504
517505 // Query should get the SAME value for all rows (optimized as constant)
0 commit comments