@@ -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,6 +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 > ) ) ;
184+ if S :: volatile ( ) {
185+ scalar_function. set_volatile ( ) ;
186+ }
166187 scalar_function. set_extra_info ( state. clone ( ) ) ;
167188 set. add_function ( scalar_function) ?;
168189 }
@@ -374,4 +395,103 @@ mod test {
374395
375396 Ok ( ( ) )
376397 }
398+
399+ // Counters for testing volatile functions
400+ use std:: sync:: atomic:: { AtomicU64 , Ordering } ;
401+ static VOLATILE_COUNTER : AtomicU64 = AtomicU64 :: new ( 0 ) ;
402+ static NON_VOLATILE_COUNTER : AtomicU64 = AtomicU64 :: new ( 0 ) ;
403+
404+ struct CounterScalar { }
405+
406+ impl VScalar for CounterScalar {
407+ type State = ( ) ;
408+
409+ unsafe fn invoke (
410+ _: & Self :: State ,
411+ input : & mut DataChunkHandle ,
412+ output : & mut dyn WritableVector ,
413+ ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
414+ let len = input. len ( ) ;
415+ let mut output_vec = output. flat_vector ( ) ;
416+ let data = output_vec. as_mut_slice :: < i64 > ( ) ;
417+
418+ for item in data. iter_mut ( ) . take ( len) {
419+ * item = NON_VOLATILE_COUNTER . fetch_add ( 1 , Ordering :: SeqCst ) as i64 ;
420+ }
421+ Ok ( ( ) )
422+ }
423+
424+ fn signatures ( ) -> Vec < ScalarFunctionSignature > {
425+ vec ! [ ScalarFunctionSignature :: exact(
426+ vec![ ] ,
427+ LogicalTypeHandle :: from( LogicalTypeId :: Bigint ) ,
428+ ) ]
429+ }
430+ }
431+
432+ struct VolatileCounterScalar { }
433+
434+ impl VScalar for VolatileCounterScalar {
435+ type State = ( ) ;
436+
437+ unsafe fn invoke (
438+ _: & Self :: State ,
439+ input : & mut DataChunkHandle ,
440+ output : & mut dyn WritableVector ,
441+ ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
442+ let len = input. len ( ) ;
443+ let mut output_vec = output. flat_vector ( ) ;
444+ let data = output_vec. as_mut_slice :: < i64 > ( ) ;
445+
446+ for item in data. iter_mut ( ) . take ( len) {
447+ * item = VOLATILE_COUNTER . fetch_add ( 1 , Ordering :: SeqCst ) as i64 ;
448+ }
449+ Ok ( ( ) )
450+ }
451+
452+ fn signatures ( ) -> Vec < ScalarFunctionSignature > {
453+ vec ! [ ScalarFunctionSignature :: exact(
454+ vec![ ] ,
455+ LogicalTypeHandle :: from( LogicalTypeId :: Bigint ) ,
456+ ) ]
457+ }
458+
459+ fn volatile ( ) -> bool {
460+ true
461+ }
462+ }
463+
464+ #[ test]
465+ fn test_volatile_scalar ( ) -> Result < ( ) , Box < dyn Error > > {
466+ let conn = Connection :: open_in_memory ( ) ?;
467+
468+ VOLATILE_COUNTER . store ( 0 , Ordering :: SeqCst ) ;
469+ conn. register_scalar_function :: < VolatileCounterScalar > ( "volatile_counter" ) ?;
470+
471+ let values: Vec < i64 > = conn
472+ . prepare ( "SELECT volatile_counter() FROM generate_series(1, 5)" ) ?
473+ . query_map ( [ ] , |row| row. get ( 0 ) ) ?
474+ . collect :: < Result < _ , _ > > ( ) ?;
475+
476+ assert_eq ! ( values, [ 0 , 1 , 2 , 3 , 4 ] ) ;
477+
478+ Ok ( ( ) )
479+ }
480+
481+ #[ test]
482+ fn test_non_volatile_scalar ( ) -> Result < ( ) , Box < dyn Error > > {
483+ let conn = Connection :: open_in_memory ( ) ?;
484+
485+ NON_VOLATILE_COUNTER . store ( 0 , Ordering :: SeqCst ) ;
486+ conn. register_scalar_function :: < CounterScalar > ( "non_volatile_counter" ) ?;
487+
488+ // Constant folding should make every row identical
489+ let distinct_count: i64 = conn
490+ . prepare ( "SELECT COUNT(DISTINCT non_volatile_counter()) FROM generate_series(1, 5)" ) ?
491+ . query_row ( [ ] , |row| row. get ( 0 ) ) ?;
492+
493+ assert_eq ! ( distinct_count, 1 ) ;
494+
495+ Ok ( ( ) )
496+ }
377497}
0 commit comments