@@ -101,6 +101,18 @@ class RedisProtocol {
101
101
return cmd;
102
102
}
103
103
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
+
104
116
static std::vector<std::string> parseArrayResponse (const std::string& response) {
105
117
std::vector<std::string> result;
106
118
if (response.empty () || response[0 ] != ' *' ) return result;
@@ -489,6 +501,53 @@ static void RedisScanFunction(DataChunk &args, ExpressionState &state, Vector &r
489
501
});
490
502
}
491
503
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
+
492
551
static void LoadInternal (DatabaseInstance &instance) {
493
552
// Register the secret functions first!
494
553
CreateRedisSecretFunctions::Register (instance);
@@ -581,6 +640,21 @@ static void LoadInternal(DatabaseInstance &instance) {
581
640
RedisScanFunction
582
641
);
583
642
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);
584
658
}
585
659
586
660
void RedisExtension::Load (DuckDB &db) {
0 commit comments