@@ -101,6 +101,18 @@ class RedisProtocol {
101101 return cmd;
102102 }
103103
104+ static std::string formatHScan (const std::string& key, const std::string& cursor, const std::string& pattern = " *" , int64_t count = 10 ) {
105+ std::string cmd = " *6\r\n $5\r\n HSCAN\r\n " ;
106+ cmd += " $" + std::to_string (key.length ()) + " \r\n " + key + " \r\n " ;
107+ cmd += " $" + std::to_string (cursor.length ()) + " \r\n " + cursor + " \r\n " ;
108+ cmd += " $5\r\n MATCH\r\n " ;
109+ cmd += " $" + std::to_string (pattern.length ()) + " \r\n " + pattern + " \r\n " ;
110+ cmd += " $5\r\n COUNT\r\n " ;
111+ auto count_str = std::to_string (count);
112+ cmd += " $" + std::to_string (count_str.length ()) + " \r\n " + count_str + " \r\n " ;
113+ return cmd;
114+ }
115+
104116 static std::vector<std::string> parseArrayResponse (const std::string& response) {
105117 std::vector<std::string> result;
106118 if (response.empty () || response[0 ] != ' *' ) return result;
@@ -489,6 +501,53 @@ static void RedisScanFunction(DataChunk &args, ExpressionState &state, Vector &r
489501 });
490502}
491503
504+ static void RedisHScanFunction (DataChunk &args, ExpressionState &state, Vector &result) {
505+ auto &key_vector = args.data [0 ];
506+ auto &cursor_vector = args.data [1 ];
507+ auto &pattern_vector = args.data [2 ];
508+ auto &count_vector = args.data [3 ];
509+ auto &secret_vector = args.data [4 ];
510+
511+ BinaryExecutor::Execute<string_t , string_t , string_t >(
512+ key_vector, cursor_vector, result, args.size (),
513+ [&](string_t key, string_t cursor) {
514+ try {
515+ string host, port, password;
516+ if (!GetRedisSecret (state.GetContext (), secret_vector.GetValue (0 ).ToString (),
517+ host, port, password)) {
518+ throw InvalidInputException (" Redis secret not found" );
519+ }
520+
521+ auto pattern = pattern_vector.GetValue (0 ).ToString ();
522+ auto count = count_vector.GetValue (0 ).GetValue <int64_t >();
523+ auto conn = ConnectionPool::getInstance ().getConnection (host, port, password);
524+ auto response = conn->execute (RedisProtocol::formatHScan (
525+ key.GetString (),
526+ cursor.GetString (),
527+ pattern,
528+ count
529+ ));
530+
531+ // HSCAN returns [cursor, [field1, value1, field2, value2, ...]]
532+ auto scan_result = RedisProtocol::parseArrayResponse (response);
533+ std::string result_str;
534+ if (scan_result.size () >= 2 ) {
535+ result_str = scan_result[0 ] + " :" ;
536+ auto kvs = RedisProtocol::parseArrayResponse (scan_result[1 ]);
537+ for (size_t i = 0 ; i < kvs.size (); i += 2 ) {
538+ if (i > 0 ) result_str += " ," ;
539+ result_str += kvs[i] + " =" + ((i + 1 ) < kvs.size () ? kvs[i + 1 ] : " " );
540+ }
541+ } else {
542+ result_str = " 0:" ;
543+ }
544+ return StringVector::AddString (result, result_str);
545+ } catch (std::exception &e) {
546+ throw InvalidInputException (" Redis HSCAN error: %s" , e.what ());
547+ }
548+ });
549+ }
550+
492551static void LoadInternal (DatabaseInstance &instance) {
493552 // Register the secret functions first!
494553 CreateRedisSecretFunctions::Register (instance);
@@ -581,6 +640,21 @@ static void LoadInternal(DatabaseInstance &instance) {
581640 RedisScanFunction
582641 );
583642 ExtensionUtil::RegisterFunction (instance, redis_scan_func);
643+
644+ // Register HSCAN
645+ auto redis_hscan_func = ScalarFunction (
646+ " redis_hscan" ,
647+ {
648+ LogicalType::VARCHAR, // key
649+ LogicalType::VARCHAR, // cursor
650+ LogicalType::VARCHAR, // pattern
651+ LogicalType::BIGINT, // count
652+ LogicalType::VARCHAR // secret_name
653+ },
654+ LogicalType::VARCHAR,
655+ RedisHScanFunction
656+ );
657+ ExtensionUtil::RegisterFunction (instance, redis_hscan_func);
584658}
585659
586660void RedisExtension::Load (DuckDB &db) {
0 commit comments