Skip to content

Commit 4258d9e

Browse files
bsboddensazzad16
andauthored
implement array and string commands (#34)
* feat: adds static factory method to Path * feat: implements Array and String query/manipulation commands * test: test for Array and String query/manipulation commands * Modify Path hashCode Co-authored-by: M Sazzadul Hoque <[email protected]>
1 parent 42aa25f commit 4258d9e

File tree

4 files changed

+491
-25
lines changed

4 files changed

+491
-25
lines changed

src/main/java/com/redislabs/modules/rejson/JReJSON.java

Lines changed: 262 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
package com.redislabs.modules.rejson;
3030

3131
import java.util.ArrayList;
32-
import java.util.Collections;
32+
import java.util.Arrays;
3333
import java.util.List;
3434
import java.util.stream.Collectors;
3535
import java.util.stream.Stream;
@@ -38,6 +38,7 @@
3838

3939
import redis.clients.jedis.Jedis;
4040
import redis.clients.jedis.JedisPool;
41+
import redis.clients.jedis.Protocol;
4142
import redis.clients.jedis.commands.ProtocolCommand;
4243
import redis.clients.jedis.util.Pool;
4344
import redis.clients.jedis.util.SafeEncoder;
@@ -54,7 +55,15 @@ private enum Command implements ProtocolCommand {
5455
GET("JSON.GET"),
5556
MGET("JSON.MGET"),
5657
SET("JSON.SET"),
57-
TYPE("JSON.TYPE");
58+
TYPE("JSON.TYPE"),
59+
STRAPPEND("JSON.STRAPPEND"),
60+
STRLEN("JSON.STRLEN"),
61+
ARRAPPEND("JSON.ARRAPPEND"),
62+
ARRINDEX("JSON.ARRINDEX"),
63+
ARRINSERT("JSON.ARRINSERT"),
64+
ARRLEN("JSON.ARRLEN"),
65+
ARRPOP("JSON.ARRPOP"),
66+
ARRTRIM("JSON.ARRTRIM");
5867
private final byte[] raw;
5968

6069
Command(String alt) {
@@ -192,6 +201,7 @@ public <T> T get(String key) {
192201
* @return the requested object
193202
* @deprecated use {@link #get(String, Class, Path...)} instead
194203
*/
204+
@SuppressWarnings("unchecked")
195205
@Deprecated
196206
public <T> T get(String key, Path... paths) {
197207
return (T)this.get(key, Object.class, paths);
@@ -224,7 +234,7 @@ public <T> T get(String key, Class<T> clazz, Path... paths) {
224234
/**
225235
* Returns the documents from multiple keys. Non-existing keys are reported as
226236
* null.
227-
*
237+
*
228238
* @param <T> target class to serialize results
229239
* @param clazz target class to serialize results
230240
* @param keys keys for the JSON documents
@@ -237,7 +247,7 @@ public <T> List<T> mget(Class<T> clazz, String... keys) {
237247
/**
238248
* Returns the values at path from multiple keys. Non-existing keys and
239249
* non-existing paths are reported as null.
240-
*
250+
*
241251
* @param path common path across all documents to root the results on
242252
* @param <T> target class to serialize results
243253
* @param clazz target class to serialize results
@@ -368,7 +378,6 @@ public Class<?> type(String key, Path path) {
368378
}
369379
}
370380

371-
372381
/**
373382
* Deletes a path
374383
* @param conn the Jedis connection
@@ -508,4 +517,252 @@ public static Class<?> type(Jedis conn, String key, Path... path) {
508517
private Jedis getConnection() {
509518
return this.client.getResource();
510519
}
520+
521+
/**
522+
* Append the value(s) the string at path.
523+
*
524+
* Returns the string's new size.
525+
*
526+
* See <a href="#{@link}">{@link https://oss.redislabs.com/redisjson/commands/#jsonstrappend}</a>.
527+
*
528+
* @param key the key of the value
529+
* @param path the path of the value
530+
* @param objects objects One or more elements to be added to the array
531+
* @return the size of the modified string
532+
*/
533+
public Long strAppend(String key, Path path, Object... objects) {
534+
List<byte[]> args = new ArrayList<>();
535+
args.add(SafeEncoder.encode(key));
536+
args.add(SafeEncoder.encode(getSingleOptionalPath(path).toString()));
537+
538+
args.addAll(Arrays.stream(objects) //
539+
.map(object -> SafeEncoder.encode(gson.toJson(object))) //
540+
.collect(Collectors.toList()));
541+
542+
try (Jedis conn = getConnection()) {
543+
conn.getClient().sendCommand(Command.STRAPPEND, args.toArray(new byte[args.size()][]));
544+
return conn.getClient().getIntegerReply();
545+
}
546+
}
547+
548+
/**
549+
* Report the length of the JSON String at path in key.
550+
* Path defaults to root if not provided.
551+
*
552+
* See <a href="#{@link}">{@link https://oss.redislabs.com/redisjson/commands/#jsonstrlen}</a>.
553+
*
554+
* @param key the key of the value
555+
* @param path the path of the value
556+
* @return the size of string at path. If the key or path do not exist, null is returned.
557+
*/
558+
public Long strLen(String key, Path path) {
559+
byte[][] args = new byte[2][];
560+
args[0] = SafeEncoder.encode(key);
561+
args[1] = SafeEncoder.encode(path.toString());
562+
563+
try (Jedis conn = getConnection()) {
564+
conn.getClient().sendCommand(Command.STRLEN, args);
565+
return conn.getClient().getIntegerReply();
566+
}
567+
}
568+
569+
/**
570+
* Appends elements into the array at path.
571+
*
572+
* Returns the array's new size.
573+
*
574+
* See <a href="#{@link}">{@link https://oss.redislabs.com/redisjson/commands/#jsonarrappend}</a>.
575+
*
576+
* @param key the key of the value
577+
* @param path the path of the value
578+
* @param objects one or more elements to be added to the array
579+
* @return the size of the modified array
580+
*/
581+
public Long arrAppend(String key, Path path, Object... objects) {
582+
List<byte[]> args = new ArrayList<>();
583+
args.add(SafeEncoder.encode(key));
584+
args.add(SafeEncoder.encode(getSingleOptionalPath(path).toString()));
585+
586+
args.addAll(Arrays.stream(objects) //
587+
.map(object -> SafeEncoder.encode(gson.toJson(object))) //
588+
.collect(Collectors.toList()));
589+
590+
try (Jedis conn = getConnection()) {
591+
conn.getClient().sendCommand(Command.ARRAPPEND, args.toArray(new byte[args.size()][]));
592+
return conn.getClient().getIntegerReply();
593+
}
594+
}
595+
596+
/**
597+
* Finds the index of the first occurrence of a scalar JSON value in the array
598+
* at the given path.
599+
*
600+
* If the item is not found, it returns -1. If called on a key path that is not
601+
* an array, it throws an error.
602+
*
603+
* See <a href="#{@link}">{@link https://oss.redislabs.com/redisjson/commands/#jsonarrindex}</a>.
604+
*
605+
* @param key the key of the value
606+
* @param path the path of the value
607+
* @param scalar the JSON scalar to search for
608+
* @return the index of the element if found, -1 if not found
609+
*/
610+
public Long arrIndex(String key, Path path, Object scalar) {
611+
byte[][] args = new byte[3][];
612+
args[0] = SafeEncoder.encode(key);
613+
args[1] = SafeEncoder.encode(path.toString());
614+
args[2] = SafeEncoder.encode(gson.toJson(scalar));
615+
616+
try (Jedis conn = getConnection()) {
617+
conn.getClient().sendCommand(Command.ARRINDEX, args);
618+
return conn.getClient().getIntegerReply();
619+
}
620+
}
621+
622+
/**
623+
* Insert element(s) into the array at path before the index (shifts to the right).
624+
* The index must be in the array's range. Inserting at index 0 prepends to the array.
625+
* Negative index values are interpreted as starting from the end.
626+
*
627+
* Returns the array's new size.
628+
*
629+
* See <a href="#{@link}">{@link https://oss.redislabs.com/redisjson/commands/#jsonarrinsert}</a>.
630+
*
631+
* @param key the key of the value
632+
* @param path the path of the value
633+
* @param index position in the array to insert the value(s)
634+
* @param objects one or more elements to be added to the array
635+
* @return the size of the modified array
636+
*/
637+
public Long arrInsert(String key, Path path, Long index, Object... objects) {
638+
List<byte[]> args = new ArrayList<>();
639+
args.add(SafeEncoder.encode(key));
640+
args.add(SafeEncoder.encode(getSingleOptionalPath(path).toString()));
641+
args.add(Protocol.toByteArray(index));
642+
643+
args.addAll(Arrays.stream(objects) //
644+
.map(object -> SafeEncoder.encode(gson.toJson(object))) //
645+
.collect(Collectors.toList()));
646+
647+
try (Jedis conn = getConnection()) {
648+
conn.getClient().sendCommand(Command.ARRINSERT, args.toArray(new byte[args.size()][]));
649+
return conn.getClient().getIntegerReply();
650+
}
651+
}
652+
653+
/**
654+
* Get the number of elements for an array field (for a given path)
655+
*
656+
* If called on a key path that is not an array, it will throw an error.
657+
*
658+
* See <a href="#{@link}">{@link https://oss.redislabs.com/redisjson/commands/#jsonarrlen}</a>.
659+
*
660+
* @param key the key of the value
661+
* @param path the path of the value
662+
* @return the size of array at path
663+
*/
664+
public Long arrLen(String key, Path path) {
665+
byte[][] args = new byte[2][];
666+
args[0] = SafeEncoder.encode(key);
667+
args[1] = SafeEncoder.encode(path.toString());
668+
669+
try (Jedis conn = getConnection()) {
670+
conn.getClient().sendCommand(Command.ARRLEN, args);
671+
return conn.getClient().getIntegerReply();
672+
}
673+
}
674+
675+
/**
676+
* Remove and return element from the index in the array.
677+
*
678+
* path defaults to root if not provided. index is the position in the array to start
679+
* popping from (defaults to -1, meaning the last element). Out of range indices are
680+
* rounded to their respective array ends. Popping an empty array yields null.
681+
*
682+
* See <a href="#{@link}">{@link https://oss.redislabs.com/redisjson/commands/#jsonarrpop}</a>.
683+
*
684+
* @param key the key of the value
685+
* @param clazz target class to serialize results
686+
* @param path the path of the value
687+
* @param index the position in the array to start popping from
688+
* @return the popped JSON value.
689+
*/
690+
public <T> T arrPop(String key, Class<T> clazz, Path path, Long index) {
691+
List<byte[]> args = new ArrayList<>();
692+
args.add(SafeEncoder.encode(key));
693+
args.add(SafeEncoder.encode(path != null ? path.toString() :Path.ROOT_PATH.toString()));
694+
args.add(Protocol.toByteArray(index != null ? index : -1));
695+
696+
String rep;
697+
try (Jedis conn = getConnection()) {
698+
conn.getClient().sendCommand(Command.ARRPOP, args.toArray(new byte[args.size()][]));
699+
rep = conn.getClient().getBulkReply();
700+
}
701+
assertReplyNotError(rep);
702+
return gson.fromJson(rep, clazz);
703+
}
704+
705+
/**
706+
* Remove and return element from the index in the array.
707+
*
708+
* path defaults to root if not provided. index is the position in the array to start
709+
* popping from (defaults to -1, meaning the last element). Out of range indices are
710+
* rounded to their respective array ends. Popping an empty array yields null.
711+
*
712+
* See <a href="#{@link}">{@link https://oss.redislabs.com/redisjson/commands/#jsonarrpop}</a>.
713+
*
714+
* @param key the key of the value
715+
* @param clazz target class to serialize results
716+
* @param path the path of the value
717+
* @return the popped JSON value.
718+
*/
719+
public <T> T arrPop(String key, Class<T> clazz, Path path) {
720+
return arrPop(key, clazz, path, null);
721+
}
722+
723+
/**
724+
* Remove and return element from the index in the array.
725+
*
726+
* path defaults to root if not provided. index is the position in the array to start
727+
* popping from (defaults to -1, meaning the last element). Out of range indices are
728+
* rounded to their respective array ends. Popping an empty array yields null.
729+
*
730+
* See <a href="#{@link}">{@link https://oss.redislabs.com/redisjson/commands/#jsonarrpop}</a>.
731+
*
732+
* @param key the key of the value
733+
* @param clazz target class to serialize results
734+
* @return the popped JSON value.
735+
*/
736+
public <T> T arrPop(String key, Class<T> clazz) {
737+
return arrPop(key, clazz, null, null);
738+
}
739+
740+
/**
741+
* Trim an array so that it contains only the specified inclusive range of elements.
742+
*
743+
* This command is extremely forgiving and using it with out of range indexes will not
744+
* produce an error. If start is larger than the array's size or start > stop , the result
745+
* will be an empty array. If start is < 0 then it will be treated as 0. If stop is larger
746+
* than the end of the array, it will be treated like the last element in it.
747+
*
748+
* See <a href="#{@link}">{@link https://oss.redislabs.com/redisjson/commands/#jsonarrtrim}</a>.
749+
*
750+
* @param key the key of the value
751+
* @param path the path of the value
752+
* @param start the start of the range
753+
* @param stop the end of the range
754+
* @return the array's new size
755+
*/
756+
public Long arrTrim(String key, Path path, Long start, Long stop) {
757+
List<byte[]> args = new ArrayList<>();
758+
args.add(SafeEncoder.encode(key));
759+
args.add(SafeEncoder.encode(getSingleOptionalPath(path).toString()));
760+
args.add(Protocol.toByteArray(start));
761+
args.add(Protocol.toByteArray(stop));
762+
763+
try (Jedis conn = getConnection()) {
764+
conn.getClient().sendCommand(Command.ARRTRIM, args.toArray(new byte[args.size()][]));
765+
return conn.getClient().getIntegerReply();
766+
}
767+
}
511768
}

src/main/java/com/redislabs/modules/rejson/Path.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,27 @@ public static Path RootPath() {
5555
public String toString() {
5656
return strPath;
5757
}
58+
59+
/**
60+
* Static factory method
61+
*
62+
* @param strPath
63+
* @return
64+
*/
65+
public static Path of(final String strPath) {
66+
return new Path(strPath);
67+
}
68+
69+
@Override
70+
public boolean equals(Object obj) {
71+
if (obj == null) return false;
72+
if (!(obj instanceof Path)) return false;
73+
if (obj == this) return true;
74+
return this.toString().equals(((Path) obj).toString());
75+
}
76+
77+
@Override
78+
public int hashCode() {
79+
return strPath.hashCode();
80+
}
5881
}

0 commit comments

Comments
 (0)