///|
/// Builder for commands that should run inside one Redis transaction.
///
/// Add commands to a `Multi`, keep the returned tokens, then pass the `Multi`
/// to `Transaction::exec` and read each result with `MultiResult::get`.
///
/// Example:
/// ```moonbit nocheck
/// let multi = @redis.Multi()
///
/// let old_value = multi.get("account:1")
///
/// let wrote = multi.set("account:1", "updated")
/// ```
struct Multi {
commands : Array[ReadOnlyArray[Bytes]]
}
///|
/// Creates an empty transaction command builder.
pub fn Multi::Multi() -> Multi {
{ commands: [] }
}
///|
/// Result handle for one command added to `Multi`.
///
/// Use the token with `MultiResult::get` after `Transaction::exec` succeeds.
struct MultiToken[T] {
index : Int
decode : (RawValue) -> T raise
}
///|
/// Result set returned by a successful `Transaction::exec`.
struct MultiResult {
values : ReadOnlyArray[RawValue]
}
///|
/// Transaction context passed to `Client::transaction`.
///
/// Use it only inside the callback passed to `Client::transaction`.
struct Transaction {
r : @resp.Reader
w : @resp.Writer
version : @resp.Version
lock : @async.Semaphore
mut done : Bool
}
///|
fn copy_bytes_views(args : ArrayView[BytesView]) -> Array[Bytes] {
let data : Array[Bytes] = Array::new(capacity=args.length())
for part in args {
data.push(part.to_owned())
}
data
}
///|
fn response_error(err : Bytes) -> Error {
try decode_utf8(err) catch {
err => err
} noraise {
msg => ServerError(msg)
}
}
///|
async fn read_direct_response(
r : @resp.Reader,
version : @resp.Version,
) -> RawValue {
for ;; {
match r.read_response(version~) {
Reply(value) => return value.raw()
Push(_) => continue
}
}
panic()
}
///|
async fn execute_direct_command(
w : @resp.Writer,
r : @resp.Reader,
version : @resp.Version,
cmd : ReadOnlyArray[Bytes],
) -> RawValue {
w.write_command(cmd)
read_direct_response(r, version)
}
///|
fn[T] Multi::queue(self : Multi, spec : Command[T]) -> MultiToken[T] {
let index = self.commands.length()
self.commands.push(ReadOnlyArray::from_array(spec.args))
{ index, decode: spec.res_decode_fn }
}
///|
/// Adds a custom command to this transaction builder.
///
/// Example:
/// ```moonbit nocheck
/// let multi = @redis.Multi()
///
/// let size = multi.raw_command([b"DBSIZE"], value => value.as_int())
/// ```
pub fn[T] Multi::raw_command(
self : Multi,
args : ArrayView[BytesView],
decode : (RawValue) -> T raise,
) -> MultiToken[T] {
self.queue(Command(copy_bytes_views(args), decode))
}
///|
pub fn Multi::get(self : Multi, key : String) -> MultiToken[String?] {
self.queue(Command::get(key))
}
///|
pub fn Multi::set(
self : Multi,
key : String,
value : String,
ex? : Int,
px? : Int,
nx? : Bool,
xx? : Bool,
) -> MultiToken[Bool] {
self.queue(Command::set(key, value, ex?, px?, nx?, xx?))
}
///|
pub fn Multi::set_get(
self : Multi,
key : String,
value : String,
ex? : Int,
px? : Int,
nx? : Bool,
xx? : Bool,
) -> MultiToken[String?] {
self.queue(Command::set_get(key, value, ex?, px?, nx?, xx?))
}
///|
pub fn Multi::setnx(
self : Multi,
key : String,
value : String,
) -> MultiToken[Bool] {
self.queue(Command::setnx(key, value))
}
///|
pub fn Multi::setex(
self : Multi,
key : String,
seconds : Int,
value : String,
) -> MultiToken[Unit] {
self.queue(Command::setex(key, seconds, value))
}
///|
pub fn Multi::psetex(
self : Multi,
key : String,
milliseconds : Int,
value : String,
) -> MultiToken[Unit] {
self.queue(Command::psetex(key, milliseconds, value))
}
///|
pub fn Multi::mget(
self : Multi,
keys : ArrayView[String],
) -> MultiToken[Array[String?]] {
self.queue(Command::mget(keys))
}
///|
pub fn Multi::mset(
self : Multi,
items : ArrayView[(String, String)],
) -> MultiToken[Unit] {
self.queue(Command::mset(items))
}
///|
pub fn Multi::msetnx(
self : Multi,
items : ArrayView[(String, String)],
) -> MultiToken[Bool] {
self.queue(Command::msetnx(items))
}
///|
pub fn Multi::incr(self : Multi, key : String) -> MultiToken[Int] {
self.queue(Command::incr(key))
}
///|
pub fn Multi::incrby(
self : Multi,
key : String,
increment : Int,
) -> MultiToken[Int] {
self.queue(Command::incrby(key, increment))
}
///|
pub fn Multi::incrbyfloat(
self : Multi,
key : String,
increment : Double,
) -> MultiToken[Double] {
self.queue(Command::incrbyfloat(key, increment))
}
///|
pub fn Multi::decr(self : Multi, key : String) -> MultiToken[Int] {
self.queue(Command::decr(key))
}
///|
pub fn Multi::decrby(
self : Multi,
key : String,
decrement : Int,
) -> MultiToken[Int] {
self.queue(Command::decrby(key, decrement))
}
///|
pub fn Multi::append(
self : Multi,
key : String,
value : String,
) -> MultiToken[Int] {
self.queue(Command::append(key, value))
}
///|
pub fn Multi::strlen(self : Multi, key : String) -> MultiToken[Int] {
self.queue(Command::strlen(key))
}
///|
pub fn Multi::getrange(
self : Multi,
key : String,
start : Int,
end : Int,
) -> MultiToken[String] {
self.queue(Command::getrange(key, start, end))
}
///|
pub fn Multi::setrange(
self : Multi,
key : String,
offset : Int,
value : String,
) -> MultiToken[Int] {
self.queue(Command::setrange(key, offset, value))
}
///|
pub fn Multi::del(self : Multi, keys : ArrayView[String]) -> MultiToken[Int] {
self.queue(Command::del(keys))
}
///|
pub fn Multi::exists(self : Multi, keys : ArrayView[String]) -> MultiToken[Int] {
self.queue(Command::exists(keys))
}
///|
pub fn Multi::expire(
self : Multi,
key : String,
seconds : Int,
) -> MultiToken[Bool] {
self.queue(Command::expire(key, seconds))
}
///|
pub fn Multi::pexpire(
self : Multi,
key : String,
milliseconds : Int,
) -> MultiToken[Bool] {
self.queue(Command::pexpire(key, milliseconds))
}
///|
pub fn Multi::ttl(self : Multi, key : String) -> MultiToken[Int] {
self.queue(Command::ttl(key))
}
///|
pub fn Multi::pttl(self : Multi, key : String) -> MultiToken[Int] {
self.queue(Command::pttl(key))
}
///|
pub fn Multi::persist(self : Multi, key : String) -> MultiToken[Bool] {
self.queue(Command::persist(key))
}
///|
pub fn Multi::rename(
self : Multi,
key : String,
new_key : String,
) -> MultiToken[Unit] {
self.queue(Command::rename(key, new_key))
}
///|
pub fn Multi::renamenx(
self : Multi,
key : String,
new_key : String,
) -> MultiToken[Bool] {
self.queue(Command::renamenx(key, new_key))
}
///|
pub fn Multi::type_(self : Multi, key : String) -> MultiToken[String] {
self.queue(Command::type_(key))
}
///|
pub fn Multi::keys(self : Multi, pattern : String) -> MultiToken[Array[String]] {
self.queue(Command::keys(pattern))
}
///|
pub fn Multi::randomkey(self : Multi) -> MultiToken[String?] {
self.queue(Command::randomkey())
}
///|
pub fn Multi::hget(
self : Multi,
key : String,
field : String,
) -> MultiToken[String?] {
self.queue(Command::hget(key, field))
}
///|
pub fn Multi::hset(
self : Multi,
key : String,
field : String,
value : String,
) -> MultiToken[Int] {
self.queue(Command::hset(key, field, value))
}
///|
pub fn Multi::hmset(
self : Multi,
key : String,
fields : ArrayView[(String, String)],
) -> MultiToken[Unit] {
self.queue(Command::hmset(key, fields))
}
///|
pub fn Multi::hdel(
self : Multi,
key : String,
fields : ArrayView[String],
) -> MultiToken[Int] {
self.queue(Command::hdel(key, fields))
}
///|
pub fn Multi::hexists(
self : Multi,
key : String,
field : String,
) -> MultiToken[Bool] {
self.queue(Command::hexists(key, field))
}
///|
pub fn Multi::hgetall(
self : Multi,
key : String,
) -> MultiToken[Map[String, String]] {
self.queue(Command::hgetall(key))
}
///|
pub fn Multi::hkeys(self : Multi, key : String) -> MultiToken[Array[String]] {
self.queue(Command::hkeys(key))
}
///|
pub fn Multi::hvals(self : Multi, key : String) -> MultiToken[Array[String]] {
self.queue(Command::hvals(key))
}
///|
pub fn Multi::hlen(self : Multi, key : String) -> MultiToken[Int] {
self.queue(Command::hlen(key))
}
///|
pub fn Multi::hmget(
self : Multi,
key : String,
fields : ArrayView[String],
) -> MultiToken[Array[String?]] {
self.queue(Command::hmget(key, fields))
}
///|
pub fn Multi::hincrby(
self : Multi,
key : String,
field : String,
increment : Int,
) -> MultiToken[Int] {
self.queue(Command::hincrby(key, field, increment))
}
///|
pub fn Multi::hincrbyfloat(
self : Multi,
key : String,
field : String,
increment : Double,
) -> MultiToken[Double] {
self.queue(Command::hincrbyfloat(key, field, increment))
}
///|
pub fn Multi::hstrlen(
self : Multi,
key : String,
field : String,
) -> MultiToken[Int] {
self.queue(Command::hstrlen(key, field))
}
///|
pub fn Multi::lpush(
self : Multi,
key : String,
values : ArrayView[String],
) -> MultiToken[Int] {
self.queue(Command::lpush(key, values))
}
///|
pub fn Multi::rpush(
self : Multi,
key : String,
values : ArrayView[String],
) -> MultiToken[Int] {
self.queue(Command::rpush(key, values))
}
///|
pub fn Multi::lpop(self : Multi, key : String) -> MultiToken[String?] {
self.queue(Command::lpop(key))
}
///|
pub fn Multi::rpop(self : Multi, key : String) -> MultiToken[String?] {
self.queue(Command::rpop(key))
}
///|
pub fn Multi::lpop_count(
self : Multi,
key : String,
count : Int,
) -> MultiToken[Array[String]] {
self.queue(Command::lpop_count(key, count))
}
///|
pub fn Multi::rpop_count(
self : Multi,
key : String,
count : Int,
) -> MultiToken[Array[String]] {
self.queue(Command::rpop_count(key, count))
}
///|
pub fn Multi::llen(self : Multi, key : String) -> MultiToken[Int] {
self.queue(Command::llen(key))
}
///|
pub fn Multi::lrange(
self : Multi,
key : String,
start : Int,
stop : Int,
) -> MultiToken[Array[String]] {
self.queue(Command::lrange(key, start, stop))
}
///|
pub fn Multi::lindex(
self : Multi,
key : String,
index : Int,
) -> MultiToken[String?] {
self.queue(Command::lindex(key, index))
}
///|
pub fn Multi::lset(
self : Multi,
key : String,
index : Int,
value : String,
) -> MultiToken[Unit] {
self.queue(Command::lset(key, index, value))
}
///|
pub fn Multi::ltrim(
self : Multi,
key : String,
start : Int,
stop : Int,
) -> MultiToken[Unit] {
self.queue(Command::ltrim(key, start, stop))
}
///|
pub fn Multi::lrem(
self : Multi,
key : String,
count : Int,
value : String,
) -> MultiToken[Int] {
self.queue(Command::lrem(key, count, value))
}
///|
pub fn Multi::linsert_before(
self : Multi,
key : String,
pivot : String,
value : String,
) -> MultiToken[Int] {
self.queue(Command::linsert_before(key, pivot, value))
}
///|
pub fn Multi::linsert_after(
self : Multi,
key : String,
pivot : String,
value : String,
) -> MultiToken[Int] {
self.queue(Command::linsert_after(key, pivot, value))
}
///|
pub fn Multi::ping(self : Multi, message? : String) -> MultiToken[String] {
self.queue(Command::ping(message?))
}
///|
pub fn Multi::echo(self : Multi, message : String) -> MultiToken[String] {
self.queue(Command::echo(message))
}
///|
pub fn Multi::scan(
self : Multi,
cursor : String,
pattern? : String,
count? : Int,
) -> MultiToken[(String, Array[String])] {
self.queue(Command::scan(cursor, pattern?, count?))
}
///|
pub fn Multi::hscan(
self : Multi,
key : String,
cursor : String,
pattern? : String,
count? : Int,
) -> MultiToken[(String, Map[String, String])] {
self.queue(Command::hscan(key, cursor, pattern?, count?))
}
///|
pub fn Multi::sadd(
self : Multi,
key : String,
members : ArrayView[String],
) -> MultiToken[Int] {
self.queue(Command::sadd(key, members))
}
///|
pub fn Multi::srem(
self : Multi,
key : String,
members : ArrayView[String],
) -> MultiToken[Int] {
self.queue(Command::srem(key, members))
}
///|
pub fn Multi::smembers(
self : Multi,
key : String,
) -> MultiToken[@hashset.HashSet[String]] {
self.queue(Command::smembers(key))
}
///|
pub fn Multi::sismember(
self : Multi,
key : String,
value : String,
) -> MultiToken[Bool] {
self.queue(Command::sismember(key, value))
}
///|
pub fn Multi::smismember(
self : Multi,
key : String,
members : ArrayView[String],
) -> MultiToken[Array[Bool]] {
self.queue(Command::smismember(key, members))
}
///|
pub fn Multi::scard(self : Multi, key : String) -> MultiToken[Int] {
self.queue(Command::scard(key))
}
///|
pub fn Multi::spop(self : Multi, key : String) -> MultiToken[String?] {
self.queue(Command::spop(key))
}
///|
pub fn Multi::spop_count(
self : Multi,
key : String,
count : Int,
) -> MultiToken[@hashset.HashSet[String]] {
self.queue(Command::spop_count(key, count))
}
///|
pub fn Multi::srandmember(self : Multi, key : String) -> MultiToken[String?] {
self.queue(Command::srandmember(key))
}
///|
pub fn Multi::srandmember_count(
self : Multi,
key : String,
count : Int,
) -> MultiToken[Array[String]] {
self.queue(Command::srandmember_count(key, count))
}
///|
pub fn Multi::smove(
self : Multi,
source : String,
destination : String,
value : String,
) -> MultiToken[Bool] {
self.queue(Command::smove(source, destination, value))
}
///|
pub fn Multi::sdiff(
self : Multi,
keys : ArrayView[String],
) -> MultiToken[@hashset.HashSet[String]] {
self.queue(Command::sdiff(keys))
}
///|
pub fn Multi::sinter(
self : Multi,
keys : ArrayView[String],
) -> MultiToken[@hashset.HashSet[String]] {
self.queue(Command::sinter(keys))
}
///|
pub fn Multi::sunion(
self : Multi,
keys : ArrayView[String],
) -> MultiToken[@hashset.HashSet[String]] {
self.queue(Command::sunion(keys))
}
///|
pub fn Multi::sdiffstore(
self : Multi,
destination : String,
keys : ArrayView[String],
) -> MultiToken[Int] {
self.queue(Command::sdiffstore(destination, keys))
}
///|
pub fn Multi::sinterstore(
self : Multi,
destination : String,
keys : ArrayView[String],
) -> MultiToken[Int] {
self.queue(Command::sinterstore(destination, keys))
}
///|
pub fn Multi::sunionstore(
self : Multi,
destination : String,
keys : ArrayView[String],
) -> MultiToken[Int] {
self.queue(Command::sunionstore(destination, keys))
}
///|
pub fn Multi::sscan(
self : Multi,
key : String,
cursor : String,
pattern? : String,
count? : Int,
) -> MultiToken[(String, @hashset.HashSet[String])] {
self.queue(Command::sscan(key, cursor, pattern?, count?))
}
///|
pub fn Multi::zadd(
self : Multi,
key : String,
items : ArrayView[(String, Double)],
) -> MultiToken[Int] {
self.queue(Command::zadd(key, items))
}
///|
pub fn Multi::zrem(
self : Multi,
key : String,
members : ArrayView[String],
) -> MultiToken[Int] {
self.queue(Command::zrem(key, members))
}
///|
pub fn Multi::zscore(
self : Multi,
key : String,
value : String,
) -> MultiToken[Double?] {
self.queue(Command::zscore(key, value))
}
///|
pub fn Multi::zcard(self : Multi, key : String) -> MultiToken[Int] {
self.queue(Command::zcard(key))
}
///|
pub fn Multi::zcount(
self : Multi,
key : String,
min : Double,
max : Double,
) -> MultiToken[Int] {
self.queue(Command::zcount(key, min, max))
}
///|
pub fn Multi::zrank(
self : Multi,
key : String,
value : String,
) -> MultiToken[Int?] {
self.queue(Command::zrank(key, value))
}
///|
pub fn Multi::zrevrank(
self : Multi,
key : String,
value : String,
) -> MultiToken[Int?] {
self.queue(Command::zrevrank(key, value))
}
///|
pub fn Multi::zrange(
self : Multi,
key : String,
start : Int,
stop : Int,
) -> MultiToken[Array[String]] {
self.queue(Command::zrange(key, start, stop))
}
///|
pub fn Multi::zrange_with_scores(
self : Multi,
key : String,
start : Int,
stop : Int,
) -> MultiToken[Array[(String, Double)]] {
self.queue(Command::zrange_with_scores(key, start, stop))
}
///|
pub fn Multi::zrevrange(
self : Multi,
key : String,
start : Int,
stop : Int,
) -> MultiToken[Array[String]] {
self.queue(Command::zrevrange(key, start, stop))
}
///|
pub fn Multi::zrevrange_with_scores(
self : Multi,
key : String,
start : Int,
stop : Int,
) -> MultiToken[Array[(String, Double)]] {
self.queue(Command::zrevrange_with_scores(key, start, stop))
}
///|
pub fn Multi::zrangebyscore(
self : Multi,
key : String,
min : Double,
max : Double,
offset? : Int,
count? : Int,
) -> MultiToken[Array[String]] raise {
self.queue(Command::zrangebyscore(key, min, max, offset?, count?))
}
///|
pub fn Multi::zrangebyscore_with_scores(
self : Multi,
key : String,
min : Double,
max : Double,
offset? : Int,
count? : Int,
) -> MultiToken[Array[(String, Double)]] raise {
self.queue(Command::zrangebyscore_with_scores(key, min, max, offset?, count?))
}
///|
pub fn Multi::zincrby(
self : Multi,
key : String,
increment : Double,
value : String,
) -> MultiToken[Double] {
self.queue(Command::zincrby(key, increment, value))
}
///|
pub fn Multi::zremrangebyrank(
self : Multi,
key : String,
start : Int,
stop : Int,
) -> MultiToken[Int] {
self.queue(Command::zremrangebyrank(key, start, stop))
}
///|
pub fn Multi::zremrangebyscore(
self : Multi,
key : String,
min : Double,
max : Double,
) -> MultiToken[Int] {
self.queue(Command::zremrangebyscore(key, min, max))
}
///|
pub fn Multi::zpopmin(
self : Multi,
key : String,
count? : Int,
) -> MultiToken[Array[(String, Double)]] {
self.queue(Command::zpopmin(key, count?))
}
///|
pub fn Multi::zpopmax(
self : Multi,
key : String,
count? : Int,
) -> MultiToken[Array[(String, Double)]] {
self.queue(Command::zpopmax(key, count?))
}
///|
pub fn Multi::zscan(
self : Multi,
key : String,
cursor : String,
pattern? : String,
count? : Int,
) -> MultiToken[(String, Array[(String, Double)])] {
self.queue(Command::zscan(key, cursor, pattern?, count?))
}
///|
/// Reads the result for a queued command.
///
/// Raises if Redis returned an error for that command or if the response cannot
/// be decoded as the token's expected type.
///
/// Example:
/// ```moonbit nocheck
/// let value = result.get(old_value)
/// ```
pub fn[T] MultiResult::get(
self : MultiResult,
token : MultiToken[T],
) -> T raise {
guard token.index < self.values.length() else {
raise UnexpectedResponse("transaction result index out of bounds")
}
(token.decode)(self.values[token.index])
}
///|
/// Runs a callback in a Redis transaction session.
///
/// Use `Transaction::watch` before building a `Multi` when optimistic locking
/// is needed. The returned value is whatever the callback returns.
///
/// Example:
/// ```moonbit nocheck
/// client.transaction(tx => {
/// tx.watch(["account:1"])
/// let multi = @redis.Multi()
/// let wrote = multi.set("account:1", "updated")
///
/// guard tx.exec(multi) is Some(result) else {
/// fail("transaction aborted")
/// }
/// result.get(wrote)
/// })
/// ```
pub async fn[T] Client::transaction(
self : Client,
f : async (Transaction) -> T,
) -> T {
let conn = self.dial()
defer conn.close()
let r = @resp.Reader(
conn,
buffer_size=self.config.read_buffer_size,
max_depth=self.config.resp_max_depth,
)
let w = @resp.Writer(conn, buffer_size=self.config.write_buffer_size)
let version = self.handshake(r, w)
let tx = { r, w, version, lock: @async.Semaphore::new(1), done: false }
f(tx)
}
///|
fn Transaction::ensure_open(self : Transaction) -> Unit raise {
guard !self.done else {
raise UnexpectedResponse("transaction already completed")
}
}
///|
async fn Transaction::execute(
self : Transaction,
cmd : Array[Bytes],
) -> RawValue {
self.ensure_open()
self.lock.acquire()
defer self.lock.release()
self.ensure_open()
execute_direct_command(
self.w,
self.r,
self.version,
ReadOnlyArray::from_array(cmd),
)
}
///|
async fn[T] Transaction::run(self : Transaction, spec : Command[T]) -> T {
(spec.res_decode_fn)(self.execute(spec.args))
}
///|
/// Watches keys for optimistic locking.
///
/// If any watched key changes before `exec`, `Transaction::exec` returns
/// `None`.
pub async fn Transaction::watch(
self : Transaction,
keys : ArrayView[String],
) -> Unit {
self.execute(ArgsBuilder(b"WATCH")..push_strings(keys).build()).expect_ok()
}
///|
/// Runs one custom command immediately on the transaction connection.
///
/// This is useful for commands not covered by typed transaction helpers.
pub async fn Transaction::raw_command(
self : Transaction,
args : ArrayView[BytesView],
) -> RawValue {
self.execute(copy_bytes_views(args))
}
///|
pub async fn Transaction::get(self : Transaction, key : String) -> String? {
self.run(Command::get(key))
}
///|
pub async fn Transaction::set(
self : Transaction,
key : String,
value : String,
ex? : Int,
px? : Int,
nx? : Bool,
xx? : Bool,
) -> Bool {
self.run(Command::set(key, value, ex?, px?, nx?, xx?))
}
///|
pub async fn Transaction::set_get(
self : Transaction,
key : String,
value : String,
ex? : Int,
px? : Int,
nx? : Bool,
xx? : Bool,
) -> String? {
self.run(Command::set_get(key, value, ex?, px?, nx?, xx?))
}
///|
pub async fn Transaction::setnx(
self : Transaction,
key : String,
value : String,
) -> Bool {
self.run(Command::setnx(key, value))
}
///|
pub async fn Transaction::setex(
self : Transaction,
key : String,
seconds : Int,
value : String,
) -> Unit {
self.run(Command::setex(key, seconds, value))
}
///|
pub async fn Transaction::psetex(
self : Transaction,
key : String,
milliseconds : Int,
value : String,
) -> Unit {
self.run(Command::psetex(key, milliseconds, value))
}
///|
pub async fn Transaction::mget(
self : Transaction,
keys : ArrayView[String],
) -> Array[String?] {
self.run(Command::mget(keys))
}
///|
pub async fn Transaction::mset(
self : Transaction,
items : ArrayView[(String, String)],
) -> Unit {
self.run(Command::mset(items))
}
///|
pub async fn Transaction::msetnx(
self : Transaction,
items : ArrayView[(String, String)],
) -> Bool {
self.run(Command::msetnx(items))
}
///|
pub async fn Transaction::incr(self : Transaction, key : String) -> Int {
self.run(Command::incr(key))
}
///|
pub async fn Transaction::incrby(
self : Transaction,
key : String,
increment : Int,
) -> Int {
self.run(Command::incrby(key, increment))
}
///|
pub async fn Transaction::incrbyfloat(
self : Transaction,
key : String,
increment : Double,
) -> Double {
self.run(Command::incrbyfloat(key, increment))
}
///|
pub async fn Transaction::decr(self : Transaction, key : String) -> Int {
self.run(Command::decr(key))
}
///|
pub async fn Transaction::decrby(
self : Transaction,
key : String,
decrement : Int,
) -> Int {
self.run(Command::decrby(key, decrement))
}
///|
pub async fn Transaction::append(
self : Transaction,
key : String,
value : String,
) -> Int {
self.run(Command::append(key, value))
}
///|
pub async fn Transaction::strlen(self : Transaction, key : String) -> Int {
self.run(Command::strlen(key))
}
///|
pub async fn Transaction::getrange(
self : Transaction,
key : String,
start : Int,
end : Int,
) -> String {
self.run(Command::getrange(key, start, end))
}
///|
pub async fn Transaction::setrange(
self : Transaction,
key : String,
offset : Int,
value : String,
) -> Int {
self.run(Command::setrange(key, offset, value))
}
///|
pub async fn Transaction::del(
self : Transaction,
keys : ArrayView[String],
) -> Int {
self.run(Command::del(keys))
}
///|
pub async fn Transaction::exists(
self : Transaction,
keys : ArrayView[String],
) -> Int {
self.run(Command::exists(keys))
}
///|
pub async fn Transaction::expire(
self : Transaction,
key : String,
seconds : Int,
) -> Bool {
self.run(Command::expire(key, seconds))
}
///|
pub async fn Transaction::pexpire(
self : Transaction,
key : String,
milliseconds : Int,
) -> Bool {
self.run(Command::pexpire(key, milliseconds))
}
///|
pub async fn Transaction::ttl(self : Transaction, key : String) -> Int {
self.run(Command::ttl(key))
}
///|
pub async fn Transaction::pttl(self : Transaction, key : String) -> Int {
self.run(Command::pttl(key))
}
///|
pub async fn Transaction::persist(self : Transaction, key : String) -> Bool {
self.run(Command::persist(key))
}
///|
pub async fn Transaction::rename(
self : Transaction,
key : String,
new_key : String,
) -> Unit {
self.run(Command::rename(key, new_key))
}
///|
pub async fn Transaction::renamenx(
self : Transaction,
key : String,
new_key : String,
) -> Bool {
self.run(Command::renamenx(key, new_key))
}
///|
pub async fn Transaction::type_(self : Transaction, key : String) -> String {
self.run(Command::type_(key))
}
///|
pub async fn Transaction::keys(
self : Transaction,
pattern : String,
) -> Array[String] {
self.run(Command::keys(pattern))
}
///|
pub async fn Transaction::randomkey(self : Transaction) -> String? {
self.run(Command::randomkey())
}
///|
pub async fn Transaction::ping(self : Transaction, message? : String) -> String {
self.run(Command::ping(message?))
}
///|
pub async fn Transaction::echo(self : Transaction, message : String) -> String {
self.run(Command::echo(message))
}
///|
pub async fn Transaction::scan(
self : Transaction,
cursor : String,
pattern? : String,
count? : Int,
) -> (String, Array[String]) {
self.run(Command::scan(cursor, pattern?, count?))
}
///|
pub async fn Transaction::hget(
self : Transaction,
key : String,
field : String,
) -> String? {
self.run(Command::hget(key, field))
}
///|
pub async fn Transaction::hset(
self : Transaction,
key : String,
field : String,
value : String,
) -> Int {
self.run(Command::hset(key, field, value))
}
///|
pub async fn Transaction::hmset(
self : Transaction,
key : String,
fields : ArrayView[(String, String)],
) -> Unit {
self.run(Command::hmset(key, fields))
}
///|
pub async fn Transaction::hdel(
self : Transaction,
key : String,
fields : ArrayView[String],
) -> Int {
self.run(Command::hdel(key, fields))
}
///|
pub async fn Transaction::hexists(
self : Transaction,
key : String,
field : String,
) -> Bool {
self.run(Command::hexists(key, field))
}
///|
pub async fn Transaction::hgetall(
self : Transaction,
key : String,
) -> Map[String, String] {
self.run(Command::hgetall(key))
}
///|
pub async fn Transaction::hkeys(
self : Transaction,
key : String,
) -> Array[String] {
self.run(Command::hkeys(key))
}
///|
pub async fn Transaction::hvals(
self : Transaction,
key : String,
) -> Array[String] {
self.run(Command::hvals(key))
}
///|
pub async fn Transaction::hlen(self : Transaction, key : String) -> Int {
self.run(Command::hlen(key))
}
///|
pub async fn Transaction::hmget(
self : Transaction,
key : String,
fields : ArrayView[String],
) -> Array[String?] {
self.run(Command::hmget(key, fields))
}
///|
pub async fn Transaction::hincrby(
self : Transaction,
key : String,
field : String,
increment : Int,
) -> Int {
self.run(Command::hincrby(key, field, increment))
}
///|
pub async fn Transaction::hincrbyfloat(
self : Transaction,
key : String,
field : String,
increment : Double,
) -> Double {
self.run(Command::hincrbyfloat(key, field, increment))
}
///|
pub async fn Transaction::hstrlen(
self : Transaction,
key : String,
field : String,
) -> Int {
self.run(Command::hstrlen(key, field))
}
///|
pub async fn Transaction::hscan(
self : Transaction,
key : String,
cursor : String,
pattern? : String,
count? : Int,
) -> (String, Map[String, String]) {
self.run(Command::hscan(key, cursor, pattern?, count?))
}
///|
pub async fn Transaction::lpush(
self : Transaction,
key : String,
values : ArrayView[String],
) -> Int {
self.run(Command::lpush(key, values))
}
///|
pub async fn Transaction::rpush(
self : Transaction,
key : String,
values : ArrayView[String],
) -> Int {
self.run(Command::rpush(key, values))
}
///|
pub async fn Transaction::lpop(self : Transaction, key : String) -> String? {
self.run(Command::lpop(key))
}
///|
pub async fn Transaction::rpop(self : Transaction, key : String) -> String? {
self.run(Command::rpop(key))
}
///|
pub async fn Transaction::lpop_count(
self : Transaction,
key : String,
count : Int,
) -> Array[String] {
self.run(Command::lpop_count(key, count))
}
///|
pub async fn Transaction::rpop_count(
self : Transaction,
key : String,
count : Int,
) -> Array[String] {
self.run(Command::rpop_count(key, count))
}
///|
pub async fn Transaction::llen(self : Transaction, key : String) -> Int {
self.run(Command::llen(key))
}
///|
pub async fn Transaction::lrange(
self : Transaction,
key : String,
start : Int,
stop : Int,
) -> Array[String] {
self.run(Command::lrange(key, start, stop))
}
///|
pub async fn Transaction::lindex(
self : Transaction,
key : String,
index : Int,
) -> String? {
self.run(Command::lindex(key, index))
}
///|
pub async fn Transaction::lset(
self : Transaction,
key : String,
index : Int,
value : String,
) -> Unit {
self.run(Command::lset(key, index, value))
}
///|
pub async fn Transaction::ltrim(
self : Transaction,
key : String,
start : Int,
stop : Int,
) -> Unit {
self.run(Command::ltrim(key, start, stop))
}
///|
pub async fn Transaction::lrem(
self : Transaction,
key : String,
count : Int,
value : String,
) -> Int {
self.run(Command::lrem(key, count, value))
}
///|
pub async fn Transaction::linsert_before(
self : Transaction,
key : String,
pivot : String,
value : String,
) -> Int {
self.run(Command::linsert_before(key, pivot, value))
}
///|
pub async fn Transaction::linsert_after(
self : Transaction,
key : String,
pivot : String,
value : String,
) -> Int {
self.run(Command::linsert_after(key, pivot, value))
}
///|
pub async fn Transaction::sadd(
self : Transaction,
key : String,
members : ArrayView[String],
) -> Int {
self.run(Command::sadd(key, members))
}
///|
pub async fn Transaction::srem(
self : Transaction,
key : String,
members : ArrayView[String],
) -> Int {
self.run(Command::srem(key, members))
}
///|
pub async fn Transaction::smembers(
self : Transaction,
key : String,
) -> @hashset.HashSet[String] {
self.run(Command::smembers(key))
}
///|
pub async fn Transaction::sismember(
self : Transaction,
key : String,
value : String,
) -> Bool {
self.run(Command::sismember(key, value))
}
///|
pub async fn Transaction::smismember(
self : Transaction,
key : String,
members : ArrayView[String],
) -> Array[Bool] {
self.run(Command::smismember(key, members))
}
///|
pub async fn Transaction::scard(self : Transaction, key : String) -> Int {
self.run(Command::scard(key))
}
///|
pub async fn Transaction::spop(self : Transaction, key : String) -> String? {
self.run(Command::spop(key))
}
///|
pub async fn Transaction::spop_count(
self : Transaction,
key : String,
count : Int,
) -> @hashset.HashSet[String] {
self.run(Command::spop_count(key, count))
}
///|
pub async fn Transaction::srandmember(
self : Transaction,
key : String,
) -> String? {
self.run(Command::srandmember(key))
}
///|
pub async fn Transaction::srandmember_count(
self : Transaction,
key : String,
count : Int,
) -> Array[String] {
self.run(Command::srandmember_count(key, count))
}
///|
pub async fn Transaction::smove(
self : Transaction,
source : String,
destination : String,
value : String,
) -> Bool {
self.run(Command::smove(source, destination, value))
}
///|
pub async fn Transaction::sdiff(
self : Transaction,
keys : ArrayView[String],
) -> @hashset.HashSet[String] {
self.run(Command::sdiff(keys))
}
///|
pub async fn Transaction::sinter(
self : Transaction,
keys : ArrayView[String],
) -> @hashset.HashSet[String] {
self.run(Command::sinter(keys))
}
///|
pub async fn Transaction::sunion(
self : Transaction,
keys : ArrayView[String],
) -> @hashset.HashSet[String] {
self.run(Command::sunion(keys))
}
///|
pub async fn Transaction::sdiffstore(
self : Transaction,
destination : String,
keys : ArrayView[String],
) -> Int {
self.run(Command::sdiffstore(destination, keys))
}
///|
pub async fn Transaction::sinterstore(
self : Transaction,
destination : String,
keys : ArrayView[String],
) -> Int {
self.run(Command::sinterstore(destination, keys))
}
///|
pub async fn Transaction::sunionstore(
self : Transaction,
destination : String,
keys : ArrayView[String],
) -> Int {
self.run(Command::sunionstore(destination, keys))
}
///|
pub async fn Transaction::sscan(
self : Transaction,
key : String,
cursor : String,
pattern? : String,
count? : Int,
) -> (String, @hashset.HashSet[String]) {
self.run(Command::sscan(key, cursor, pattern?, count?))
}
///|
pub async fn Transaction::zadd(
self : Transaction,
key : String,
items : ArrayView[(String, Double)],
) -> Int {
self.run(Command::zadd(key, items))
}
///|
pub async fn Transaction::zrem(
self : Transaction,
key : String,
members : ArrayView[String],
) -> Int {
self.run(Command::zrem(key, members))
}
///|
pub async fn Transaction::zscore(
self : Transaction,
key : String,
value : String,
) -> Double? {
self.run(Command::zscore(key, value))
}
///|
pub async fn Transaction::zcard(self : Transaction, key : String) -> Int {
self.run(Command::zcard(key))
}
///|
pub async fn Transaction::zcount(
self : Transaction,
key : String,
min : Double,
max : Double,
) -> Int {
self.run(Command::zcount(key, min, max))
}
///|
pub async fn Transaction::zrank(
self : Transaction,
key : String,
value : String,
) -> Int? {
self.run(Command::zrank(key, value))
}
///|
pub async fn Transaction::zrevrank(
self : Transaction,
key : String,
value : String,
) -> Int? {
self.run(Command::zrevrank(key, value))
}
///|
pub async fn Transaction::zrange(
self : Transaction,
key : String,
start : Int,
stop : Int,
) -> Array[String] {
self.run(Command::zrange(key, start, stop))
}
///|
pub async fn Transaction::zrange_with_scores(
self : Transaction,
key : String,
start : Int,
stop : Int,
) -> Array[(String, Double)] {
self.run(Command::zrange_with_scores(key, start, stop))
}
///|
pub async fn Transaction::zrevrange(
self : Transaction,
key : String,
start : Int,
stop : Int,
) -> Array[String] {
self.run(Command::zrevrange(key, start, stop))
}
///|
pub async fn Transaction::zrevrange_with_scores(
self : Transaction,
key : String,
start : Int,
stop : Int,
) -> Array[(String, Double)] {
self.run(Command::zrevrange_with_scores(key, start, stop))
}
///|
pub async fn Transaction::zrangebyscore(
self : Transaction,
key : String,
min : Double,
max : Double,
offset? : Int,
count? : Int,
) -> Array[String] {
self.run(Command::zrangebyscore(key, min, max, offset?, count?))
}
///|
pub async fn Transaction::zrangebyscore_with_scores(
self : Transaction,
key : String,
min : Double,
max : Double,
offset? : Int,
count? : Int,
) -> Array[(String, Double)] {
self.run(Command::zrangebyscore_with_scores(key, min, max, offset?, count?))
}
///|
pub async fn Transaction::zincrby(
self : Transaction,
key : String,
increment : Double,
value : String,
) -> Double {
self.run(Command::zincrby(key, increment, value))
}
///|
pub async fn Transaction::zremrangebyrank(
self : Transaction,
key : String,
start : Int,
stop : Int,
) -> Int {
self.run(Command::zremrangebyrank(key, start, stop))
}
///|
pub async fn Transaction::zremrangebyscore(
self : Transaction,
key : String,
min : Double,
max : Double,
) -> Int {
self.run(Command::zremrangebyscore(key, min, max))
}
///|
pub async fn Transaction::zpopmin(
self : Transaction,
key : String,
count? : Int,
) -> Array[(String, Double)] {
self.run(Command::zpopmin(key, count?))
}
///|
pub async fn Transaction::zpopmax(
self : Transaction,
key : String,
count? : Int,
) -> Array[(String, Double)] {
self.run(Command::zpopmax(key, count?))
}
///|
pub async fn Transaction::zscan(
self : Transaction,
key : String,
cursor : String,
pattern? : String,
count? : Int,
) -> (String, Array[(String, Double)]) {
self.run(Command::zscan(key, cursor, pattern?, count?))
}
///|
/// Executes commands added to `multi` with Redis `MULTI`/`EXEC`.
///
/// Returns `None` when watched keys changed before `EXEC`. Returns
/// `Some(result)` on success; read individual command results with
/// `MultiResult::get`.
pub async fn Transaction::exec(
self : Transaction,
multi : Multi,
) -> MultiResult? {
self.ensure_open()
self.lock.acquire()
defer self.lock.release()
self.ensure_open()
self.done = true
// The transaction session owns a short-lived Redis connection. WATCH state is
// intentionally not unwatched here: EXEC clears it, and closing the temporary
// connection clears any remaining WATCH state. If transaction connections are
// pooled later, the pool must reset connections with UNWATCH/DISCARD before
// reuse.
execute_direct_command(self.w, self.r, self.version, [b"MULTI"]).expect_ok()
for cmd in multi.commands {
guard execute_direct_command(self.w, self.r, self.version, cmd).as_string() ==
"QUEUED" else {
raise UnexpectedResponse("expected QUEUED")
}
}
let result = execute_direct_command(self.w, self.r, self.version, [b"EXEC"])
if result.is_null() {
None
} else {
let values = result.as_array(value => value)
Some({ values: ReadOnlyArray::from_array(values) })
}
}