@@ -440,6 +440,241 @@ pub unsafe extern "C" fn init(level: Option<Level>, file_name: *const c_char) ->
440440 logger_level. into ( )
441441}
442442
443+ #[ repr( C ) ]
444+ pub struct ScriptHashBuffer {
445+ pub ptr : * mut u8 ,
446+ pub len : usize ,
447+ pub capacity : usize ,
448+ }
449+
450+ /// Store a Lua script in the script cache and return its SHA1 hash.
451+ ///
452+ /// # Parameters
453+ ///
454+ /// * `script_bytes`: Pointer to the script bytes.
455+ /// * `script_len`: Length of the script in bytes.
456+ ///
457+ /// # Returns
458+ ///
459+ /// A pointer to a `ScriptHashBuffer` containing the SHA1 hash of the script.
460+ /// The caller is responsible for freeing this memory using [`free_script_hash_buffer`].
461+ ///
462+ /// # Safety
463+ ///
464+ /// * `script_bytes` must point to `script_len` consecutive properly initialized bytes.
465+ /// * The returned buffer must be freed by the caller using [`free_script_hash_buffer`].
466+ #[ unsafe( no_mangle) ]
467+ pub unsafe extern "C" fn store_script (
468+ script_bytes : * const u8 ,
469+ script_len : usize ,
470+ ) -> * mut ScriptHashBuffer {
471+ let script = unsafe { std:: slice:: from_raw_parts ( script_bytes, script_len) } ;
472+ let hash = glide_core:: scripts_container:: add_script ( script) ;
473+ let mut hash = std:: mem:: ManuallyDrop :: new ( hash) ;
474+ let script_hash_buffer = ScriptHashBuffer {
475+ ptr : hash. as_mut_ptr ( ) ,
476+ len : hash. len ( ) ,
477+ capacity : hash. capacity ( ) ,
478+ } ;
479+ Box :: into_raw ( Box :: new ( script_hash_buffer) )
480+ }
481+
482+ /// Free a `ScriptHashBuffer` obtained from [`store_script`].
483+ ///
484+ /// # Parameters
485+ ///
486+ /// * `buffer`: Pointer to the `ScriptHashBuffer`.
487+ ///
488+ /// # Safety
489+ ///
490+ /// * `buffer` must be a pointer returned from [`store_script`].
491+ /// * This function must be called exactly once per buffer.
492+ #[ unsafe( no_mangle) ]
493+ pub unsafe extern "C" fn free_script_hash_buffer ( buffer : * mut ScriptHashBuffer ) {
494+ if buffer. is_null ( ) {
495+ return ;
496+ }
497+ let buffer = unsafe { Box :: from_raw ( buffer) } ;
498+ let _hash = unsafe { String :: from_raw_parts ( buffer. ptr , buffer. len , buffer. capacity ) } ;
499+ }
500+
501+ /// Remove a script from the script cache.
502+ ///
503+ /// Returns a null pointer if it succeeds and a C string error message if it fails.
504+ ///
505+ /// # Parameters
506+ ///
507+ /// * `hash`: The SHA1 hash of the script to remove as a byte array.
508+ /// * `len`: The length of `hash`.
509+ ///
510+ /// # Returns
511+ ///
512+ /// A null pointer on success, or a pointer to a C string error message on failure.
513+ /// The caller is responsible for freeing the error message using [`free_drop_script_error`].
514+ ///
515+ /// # Safety
516+ ///
517+ /// * `hash` must be a valid pointer to a UTF-8 string.
518+ /// * The returned error pointer (if not null) must be freed using [`free_drop_script_error`].
519+ #[ unsafe( no_mangle) ]
520+ pub unsafe extern "C" fn drop_script ( hash : * mut u8 , len : usize ) -> * mut c_char {
521+ if hash. is_null ( ) {
522+ return CString :: new ( "Hash pointer was null." ) . unwrap ( ) . into_raw ( ) ;
523+ }
524+
525+ let slice = std:: ptr:: slice_from_raw_parts_mut ( hash, len) ;
526+ let Ok ( hash_str) = std:: str:: from_utf8 ( unsafe { & * slice } ) else {
527+ return CString :: new ( "Unable to convert hash to UTF-8 string." )
528+ . unwrap ( )
529+ . into_raw ( ) ;
530+ } ;
531+
532+ glide_core:: scripts_container:: remove_script ( hash_str) ;
533+ std:: ptr:: null_mut ( )
534+ }
535+
536+ /// Free an error message from a failed drop_script call.
537+ ///
538+ /// # Parameters
539+ ///
540+ /// * `error`: The error to free.
541+ ///
542+ /// # Safety
543+ ///
544+ /// * `error` must be an error returned by [`drop_script`].
545+ /// * This function must be called exactly once per error.
546+ #[ unsafe( no_mangle) ]
547+ pub unsafe extern "C" fn free_drop_script_error ( error : * mut c_char ) {
548+ if !error. is_null ( ) {
549+ _ = unsafe { CString :: from_raw ( error) } ;
550+ }
551+ }
552+
553+ /// Executes a Lua script using EVALSHA with automatic fallback to EVAL.
554+ ///
555+ /// # Parameters
556+ ///
557+ /// * `client_ptr`: Pointer to a valid `GlideClient` returned from [`create_client`].
558+ /// * `callback_index`: Unique identifier for the callback.
559+ /// * `hash`: SHA1 hash of the script as a null-terminated C string.
560+ /// * `keys_count`: Number of keys in the keys array.
561+ /// * `keys`: Array of pointers to key data.
562+ /// * `keys_len`: Array of key lengths.
563+ /// * `args_count`: Number of arguments in the args array.
564+ /// * `args`: Array of pointers to argument data.
565+ /// * `args_len`: Array of argument lengths.
566+ /// * `route_bytes`: Optional routing information (not used, reserved for future).
567+ /// * `route_bytes_len`: Length of route_bytes.
568+ ///
569+ /// # Safety
570+ ///
571+ /// * `client_ptr` must not be `null` and must be obtained from [`create_client`].
572+ /// * `hash` must be a valid null-terminated C string.
573+ /// * `keys` and `keys_len` must be valid arrays of size `keys_count`, or both null if `keys_count` is 0.
574+ /// * `args` and `args_len` must be valid arrays of size `args_count`, or both null if `args_count` is 0.
575+ #[ unsafe( no_mangle) ]
576+ pub unsafe extern "C-unwind" fn invoke_script (
577+ client_ptr : * const c_void ,
578+ callback_index : usize ,
579+ hash : * const c_char ,
580+ keys_count : usize ,
581+ keys : * const usize ,
582+ keys_len : * const usize ,
583+ args_count : usize ,
584+ args : * const usize ,
585+ args_len : * const usize ,
586+ _route_bytes : * const u8 ,
587+ _route_bytes_len : usize ,
588+ ) {
589+ let client = unsafe {
590+ Arc :: increment_strong_count ( client_ptr) ;
591+ Arc :: from_raw ( client_ptr as * mut Client )
592+ } ;
593+ let core = client. core . clone ( ) ;
594+
595+ let mut panic_guard = PanicGuard {
596+ panicked : true ,
597+ failure_callback : core. failure_callback ,
598+ callback_index,
599+ } ;
600+
601+ // Convert hash to Rust string
602+ let hash_str = match unsafe { CStr :: from_ptr ( hash) . to_str ( ) } {
603+ Ok ( s) => s. to_string ( ) ,
604+ Err ( e) => {
605+ unsafe {
606+ report_error (
607+ core. failure_callback ,
608+ callback_index,
609+ format ! ( "Invalid hash string: {}" , e) ,
610+ RequestErrorType :: Unspecified ,
611+ ) ;
612+ }
613+ return ;
614+ }
615+ } ;
616+
617+ // Convert keys
618+ let keys_vec: Vec < & [ u8 ] > = if !keys. is_null ( ) && !keys_len. is_null ( ) && keys_count > 0 {
619+ unsafe {
620+ ffi:: convert_string_pointer_array_to_vector (
621+ keys as * const * const u8 ,
622+ keys_count,
623+ keys_len,
624+ )
625+ }
626+ } else {
627+ Vec :: new ( )
628+ } ;
629+
630+ // Convert args
631+ let args_vec: Vec < & [ u8 ] > = if !args. is_null ( ) && !args_len. is_null ( ) && args_count > 0 {
632+ unsafe {
633+ ffi:: convert_string_pointer_array_to_vector (
634+ args as * const * const u8 ,
635+ args_count,
636+ args_len,
637+ )
638+ }
639+ } else {
640+ Vec :: new ( )
641+ } ;
642+
643+ client. runtime . spawn ( async move {
644+ let mut panic_guard = PanicGuard {
645+ panicked : true ,
646+ failure_callback : core. failure_callback ,
647+ callback_index,
648+ } ;
649+
650+ let result = core
651+ . client
652+ . clone ( )
653+ . invoke_script ( & hash_str, & keys_vec, & args_vec, None )
654+ . await ;
655+
656+ match result {
657+ Ok ( value) => {
658+ let ptr = Box :: into_raw ( Box :: new ( ResponseValue :: from_value ( value) ) ) ;
659+ unsafe { ( core. success_callback ) ( callback_index, ptr) } ;
660+ }
661+ Err ( err) => unsafe {
662+ report_error (
663+ core. failure_callback ,
664+ callback_index,
665+ error_message ( & err) ,
666+ error_type ( & err) ,
667+ ) ;
668+ } ,
669+ } ;
670+ panic_guard. panicked = false ;
671+ drop ( panic_guard) ;
672+ } ) ;
673+
674+ panic_guard. panicked = false ;
675+ drop ( panic_guard) ;
676+ }
677+
443678/// Execute a cluster scan request.
444679///
445680/// # Safety
0 commit comments