@@ -59,6 +59,12 @@ public class RedisNetworkExecutor extends Thread {
5959 private final Map <String , Object > defaultBucket = new ConcurrentHashMap <>();
6060 private DatabaseInternal selectedDatabase = null ;
6161
62+ /**
63+ * Holds the resolved key and database from key resolution.
64+ */
65+ private record ResolvedKey (String key , DatabaseInternal database ) {
66+ }
67+
6268 public RedisNetworkExecutor (final ArcadeDBServer server , final Socket socket ) throws IOException {
6369 setName (Constants .PRODUCT + "-redis/" + socket .getInetAddress ());
6470 this .server = server ;
@@ -255,18 +261,21 @@ private void hDel(final List<Object> list) {
255261 final String bucketName = (String ) list .get (1 );
256262
257263 final int pos = bucketName .indexOf ("." );
258- int deleted = 0 ;
264+ final int [] deleted = { 0 } ;
259265
260266 if (pos < 0 ) {
261- // Transient mode: delete from globalVariables
267+ // Transient mode: delete from globalVariables atomically
262268 final DatabaseInternal database = (DatabaseInternal ) server .getDatabase (bucketName );
263- for (int i = 2 ; i < list .size (); i ++) {
264- final String key = (String ) list .get (i );
265- if (database .getGlobalVariable (key ) != null ) {
266- database .setGlobalVariable (key , null );
267- ++deleted ;
269+ database .transaction (() -> {
270+ for (int i = 2 ; i < list .size (); i ++) {
271+ final String key = (String ) list .get (i );
272+ // Use setGlobalVariable which atomically returns the previous value
273+ final Object previous = database .setGlobalVariable (key , null );
274+ if (previous != null ) {
275+ deleted [0 ]++;
276+ }
268277 }
269- }
278+ });
270279 } else {
271280 // Persistent mode: delete from database
272281 final String databaseName = bucketName .substring (0 , pos );
@@ -276,7 +285,7 @@ private void hDel(final List<Object> list) {
276285
277286 if (keyType .startsWith ("#" )) {
278287 new RID (database , keyType ).getRecord ().delete ();
279- ++ deleted ;
288+ deleted [ 0 ]++ ;
280289 } else {
281290 final Index index = database .getSchema ().getIndexByName (keyType );
282291
@@ -294,13 +303,13 @@ private void hDel(final List<Object> list) {
294303 final IndexCursor cursor = index .get (keys );
295304 if (cursor .hasNext ()) {
296305 cursor .next ().getRecord ().delete ();
297- ++ deleted ;
306+ deleted [ 0 ]++ ;
298307 }
299308 }
300309 }
301310 }
302311 value .append (":" );
303- value .append (deleted );
312+ value .append (deleted [ 0 ] );
304313 }
305314
306315 private void hExists (final List <Object > list ) {
@@ -383,20 +392,22 @@ private void hSet(final List<Object> list) {
383392
384393 // Check if transient mode: second argument is JSON (starts with '{')
385394 if (secondArg .startsWith ("{" )) {
386- // Transient mode: store JSON objects in globalVariables
395+ // Transient mode: store JSON objects in globalVariables atomically
387396 final DatabaseInternal database = (DatabaseInternal ) server .getDatabase (databaseName );
388- int stored = 0 ;
389- for (int i = 2 ; i < list .size (); i ++) {
390- final JSONObject json = new JSONObject ((String ) list .get (i ));
391- if (!json .has ("id" )) {
392- throw new RedisException ("JSON object must have an 'id' field for transient storage" );
397+ final int [] stored = {0 };
398+ database .transaction (() -> {
399+ for (int i = 2 ; i < list .size (); i ++) {
400+ final JSONObject json = new JSONObject ((String ) list .get (i ));
401+ if (!json .has ("id" )) {
402+ throw new RedisException ("JSON object must have an 'id' field for transient storage" );
403+ }
404+ final String key = json .get ("id" ).toString ();
405+ database .setGlobalVariable (key , json .toString ());
406+ stored [0 ]++;
393407 }
394- final String key = json .get ("id" ).toString ();
395- database .setGlobalVariable (key , json .toString ());
396- stored ++;
397- }
408+ });
398409 value .append (":" );
399- value .append (stored );
410+ value .append (stored [ 0 ] );
400411 } else {
401412 // Persistent mode: store documents in database type
402413 final String typeName = secondArg ;
@@ -469,80 +480,78 @@ private void ping(final List<Object> list) {
469480
470481 private void select (final List <Object > list ) {
471482 final String dbName = (String ) list .get (1 );
472- this .selectedDatabase = (DatabaseInternal ) server .getDatabase (dbName );
473- value .append ("+" );
474- value .append ("OK" );
483+ try {
484+ this .selectedDatabase = (DatabaseInternal ) server .getDatabase (dbName );
485+ value .append ("+" );
486+ value .append ("OK" );
487+ } catch (final Exception e ) {
488+ throw new RedisException ("Database '" + dbName + "' not found" );
489+ }
475490 }
476491
477492 /**
478493 * Resolves the database and actual key from a potentially prefixed key.
479494 * Priority: key prefix (dbname.key) > SELECT > default config > connection-local bucket.
480495 *
481496 * @param key the key which may contain a database prefix (e.g., "mydb.mykey")
482- * @return array of [resolvedKey, database] where database may be null if using local bucket
497+ * @return ResolvedKey containing the resolved key and database (database may be null if using local bucket)
483498 */
484- private Object [] resolveKeyAndDatabase (final String key ) {
499+ private ResolvedKey resolveKeyAndDatabase (final String key ) {
485500 // Check for database prefix (dbname.key)
486501 final int dotPos = key .indexOf ('.' );
487502 if (dotPos > 0 ) {
488503 final String dbName = key .substring (0 , dotPos );
489504 final String actualKey = key .substring (dotPos + 1 );
490505 try {
491- return new Object []{ actualKey , (DatabaseInternal ) server .getDatabase (dbName )} ;
506+ return new ResolvedKey ( actualKey , (DatabaseInternal ) server .getDatabase (dbName )) ;
492507 } catch (final Exception e ) {
508+ LogManager .instance ().log (this , Level .FINE ,
509+ "Could not resolve database '%s' from key '%s'. Treating as a regular key. Error: %s" , e , dbName , key ,
510+ e .getMessage ());
493511 // Not a valid database prefix, treat as regular key
494512 }
495513 }
496514
497515 // Use selected database (from SELECT command or default config)
498- return new Object []{ key , selectedDatabase } ;
516+ return new ResolvedKey ( key , selectedDatabase ) ;
499517 }
500518
501519 private Object getVariable (final String key ) {
502- final Object [] resolved = resolveKeyAndDatabase (key );
503- final String actualKey = (String ) resolved [0 ];
504- final DatabaseInternal db = (DatabaseInternal ) resolved [1 ];
520+ final ResolvedKey resolved = resolveKeyAndDatabase (key );
505521
506- if (db != null ) {
507- return db . getGlobalVariable (actualKey );
522+ if (resolved . database () != null ) {
523+ return resolved . database (). getGlobalVariable (resolved . key () );
508524 }
509- return defaultBucket .get (actualKey );
525+ return defaultBucket .get (resolved . key () );
510526 }
511527
512528 private void setVariable (final String key , final Object value ) {
513- final Object [] resolved = resolveKeyAndDatabase (key );
514- final String actualKey = (String ) resolved [0 ];
515- final DatabaseInternal db = (DatabaseInternal ) resolved [1 ];
529+ final ResolvedKey resolved = resolveKeyAndDatabase (key );
516530
517- if (db != null ) {
518- db . setGlobalVariable (actualKey , value );
531+ if (resolved . database () != null ) {
532+ resolved . database (). setGlobalVariable (resolved . key () , value );
519533 } else {
520- defaultBucket .put (actualKey , value );
534+ defaultBucket .put (resolved . key () , value );
521535 }
522536 }
523537
524538 private Object removeVariable (final String key ) {
525- final Object [] resolved = resolveKeyAndDatabase (key );
526- final String actualKey = (String ) resolved [0 ];
527- final DatabaseInternal db = (DatabaseInternal ) resolved [1 ];
528-
529- if (db != null ) {
530- final Object oldValue = db .getGlobalVariable (actualKey );
531- db .setGlobalVariable (actualKey , null );
532- return oldValue ;
539+ final ResolvedKey resolved = resolveKeyAndDatabase (key );
540+
541+ if (resolved .database () != null ) {
542+ // Use setGlobalVariable which atomically returns the previous value
543+ return resolved .database ().setGlobalVariable (resolved .key (), null );
533544 }
534- return defaultBucket .remove (actualKey );
545+ return defaultBucket .remove (resolved . key () );
535546 }
536547
537548 private boolean containsVariable (final String key ) {
538- final Object [] resolved = resolveKeyAndDatabase (key );
539- final String actualKey = (String ) resolved [0 ];
540- final DatabaseInternal db = (DatabaseInternal ) resolved [1 ];
549+ final ResolvedKey resolved = resolveKeyAndDatabase (key );
541550
542- if (db != null ) {
543- return db . getGlobalVariable (actualKey ) != null ;
551+ if (resolved . database () != null ) {
552+ return resolved . database (). getGlobalVariable (resolved . key () ) != null ;
544553 }
545- return defaultBucket .containsKey (actualKey );
554+ return defaultBucket .containsKey (resolved . key () );
546555 }
547556
548557 private Object parseNext () throws IOException {
@@ -663,14 +672,13 @@ private byte readNext() throws IOException {
663672 }
664673
665674 /**
666- * Gets a record by RID, index, or from globalVariables (transient) .
675+ * Gets a record by RID or index .
667676 * Formats:
668677 * - bucketName = database, key = #rid -> get by RID
669- * - bucketName = database, key = id (not starting with #) -> get from globalVariables (transient)
670678 * - bucketName = database.indexName, key = value -> get by index
671679 *
672- * @return the record, or null if not found. For transient mode, returns null but the value
673- * can be retrieved via getTransientValue() if needed for special handling.
680+ * @return the record, or null if not found
681+ * @throws RedisException if key is not a RID when bucketName has no dot
674682 */
675683 private Record getRecord (final String bucketName , final String key ) {
676684 final Record record ;
@@ -682,9 +690,8 @@ private Record getRecord(final String bucketName, final String key) {
682690 // BY RID
683691 record = new RID (database , key ).asDocument ();
684692 } else {
685- // TRANSIENT MODE: get from globalVariables
686- // Return null here - caller should use getTransientValue for the actual value
687- record = null ;
693+ throw new RedisException (
694+ "Retrieving a record by RID, the key must be as #<bucket-id>:<bucket-position>. Example: #13:432" );
688695 }
689696 } else {
690697 // BY INDEX
0 commit comments