1use sha1::{Digest, Sha1};
2use std::sync::LazyLock;
3
4pub(crate) struct Script {
5 pub src: &'static str,
6 pub hash: String,
7}
8
9impl Script {
10 pub fn new(src: &'static str) -> Self {
11 let mut hasher = Sha1::new();
12
13 hasher.update(src.as_bytes());
14
15 let result = hasher.finalize();
16 Self {
17 src,
18 hash: format!("{:x}", result),
19 }
20 }
21}
22
23pub(crate) static DELETE_SCRIPT: LazyLock<Script> = LazyLock::new(|| {
24 Script::new(
25 r#"
26redis.call('HSET', KEYS[1], 'lockUntil', 0)
27redis.call('HDEL', KEYS[1], 'lockOwner')
28redis.call('EXPIRE', KEYS[1], ARGV[1])"#,
29 )
30});
31
32pub(crate) static GET_SCRIPT: LazyLock<Script> = LazyLock::new(|| {
33 Script::new(
34 r#"
35local v = redis.call('HGET', KEYS[1], 'value')
36local lu = redis.call('HGET', KEYS[1], 'lockUntil')
37if lu ~= false and tonumber(lu) < tonumber(ARGV[1]) or lu == false and v == false then
38 redis.call('HSET', KEYS[1], 'lockUntil', ARGV[2])
39 redis.call('HSET', KEYS[1], 'lockOwner', ARGV[3])
40 return { v, 'LOCKED' }
41end
42return {v, lu}"#,
43 )
44});
45
46pub(crate) static SET_SCRIPT: LazyLock<Script> = LazyLock::new(|| {
47 Script::new(
48 r#"
49local o = redis.call('HGET', KEYS[1], 'lockOwner')
50if o ~= ARGV[2] then
51 return
52end
53redis.call('HSET', KEYS[1], 'value', ARGV[1])
54redis.call('HDEL', KEYS[1], 'lockUntil')
55redis.call('HDEL', KEYS[1], 'lockOwner')
56redis.call('EXPIRE', KEYS[1], ARGV[3])"#,
57 )
58});
59
60pub(crate) static UNLOCK_SCRIPT: LazyLock<Script> = LazyLock::new(|| {
61 Script::new(
62 r#"
63local lo = redis.call('HGET', KEYS[1], 'lockOwner')
64if lo == ARGV[1] then
65 redis.call('HSET', KEYS[1], 'lockUntil', 0)
66 redis.call('HDEL', KEYS[1], 'lockOwner')
67 redis.call('EXPIRE', KEYS[1], ARGV[2])
68end"#,
69 )
70});
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn test_script_new() {
78 let script = Script::new("return 1");
79 assert_eq!(script.hash, "e0e1f9fabfc9d4800c877a703b823ac0578ff8db");
80 assert_eq!(script.src, "return 1");
81 }
82}