Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit f887ecf

Browse files
committed
Add TemplateRedisFilters
1 parent d5a5cbd commit f887ecf

File tree

1 file changed

+166
-0
lines changed

1 file changed

+166
-0
lines changed
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
using System;
2+
using System.Linq;
3+
using System.Collections.Generic;
4+
using ServiceStack.Configuration;
5+
using ServiceStack.Templates;
6+
7+
namespace ServiceStack.Redis
8+
{
9+
public class RedisSearchCursorResult
10+
{
11+
public int Cursor { get; set; }
12+
public List<RedisSearchResult> Results { get; set; }
13+
}
14+
15+
public class RedisSearchResult
16+
{
17+
public string Id { get; set; }
18+
public string Type { get; set; }
19+
public long Ttl { get; set; }
20+
public long Size { get; set; }
21+
}
22+
23+
public class TemplateRedisFilters : TemplateFilter
24+
{
25+
public IRedisClientsManager RedisManager { get; set; }
26+
public IAppSettings AppSettings { get; set; }
27+
T exec<T>(Func<IRedisClient, T> fn)
28+
{
29+
using (var db = RedisManager.GetClient())
30+
{
31+
return fn(db);
32+
}
33+
}
34+
35+
static Dictionary<string, int> cmdArgCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase) {
36+
{ "SET", 3 }
37+
};
38+
39+
List<string> parseCommandString(string cmd)
40+
{
41+
var args = new List<string>();
42+
var lastPos = 0;
43+
for (var i = 0; i < cmd.Length; i++)
44+
{
45+
var c = cmd[i];
46+
if (c == '{' || c == '[')
47+
{
48+
break; //stop splitting args if value is complex type
49+
}
50+
if (c == ' ')
51+
{
52+
var arg = cmd.Substring(lastPos, i - lastPos);
53+
args.Add(arg);
54+
lastPos = i + 1;
55+
56+
//if we've reached the command args count, capture the rest of the body as the last arg
57+
if (cmdArgCounts.TryGetValue(args[0], out int argCount) && args.Count == argCount - 1)
58+
break;
59+
}
60+
}
61+
args.Add(cmd.Substring(lastPos));
62+
return args;
63+
}
64+
65+
object toObject(RedisText r)
66+
{
67+
if (r == null)
68+
return null;
69+
70+
if (r.Children != null && r.Children.Count > 0)
71+
{
72+
var to = new List<object>();
73+
for (var i = 0; i < r.Children.Count; i++)
74+
{
75+
var child = r.Children[i];
76+
var value = child.Text ?? toObject(child);
77+
to.Add(value);
78+
}
79+
return to;
80+
}
81+
return r.Text;
82+
}
83+
84+
public object redisCall(string cmd)
85+
{
86+
if (string.IsNullOrEmpty(cmd))
87+
return null;
88+
89+
var args = parseCommandString(cmd);
90+
var objParams = args.Select(x => (object)x).ToArray();
91+
var redisText = exec(r => r.Custom(objParams));
92+
var result = toObject(redisText);
93+
return result;
94+
}
95+
96+
public List<RedisSearchResult> redisSearchKeys(TemplateScopeContext scope, string query) => redisSearchKeys(scope, query, null);
97+
public List<RedisSearchResult> redisSearchKeys(TemplateScopeContext scope, string query, object options)
98+
{
99+
var json = redisSearchKeysAsJson(scope, query, options);
100+
const string noResult = "{\"cursor\":0,\"results\":{}}";
101+
if (json == noResult)
102+
return new List<RedisSearchResult>();
103+
104+
var searchResults = json.FromJson<RedisSearchCursorResult>();
105+
return searchResults.Results;
106+
}
107+
108+
public string redisSearchKeysAsJson(TemplateScopeContext scope, string query, object options)
109+
{
110+
if (string.IsNullOrEmpty(query))
111+
return null;
112+
113+
var args = scope.AssertOptions(nameof(redisSearchKeys), options);
114+
var limit = args.TryGetValue("limit", out object value)
115+
? value.ConvertTo<int>()
116+
: scope.GetValue("redis.search.limit") ?? 100;
117+
118+
const string LuaScript = @"
119+
local limit = tonumber(ARGV[2])
120+
local pattern = ARGV[1]
121+
local cursor = tonumber(ARGV[3])
122+
local len = 0
123+
local keys = {}
124+
repeat
125+
local r = redis.call('scan', cursor, 'MATCH', pattern, 'COUNT', limit)
126+
cursor = tonumber(r[1])
127+
for k,v in ipairs(r[2]) do
128+
table.insert(keys, v)
129+
len = len + 1
130+
if len == limit then break end
131+
end
132+
until cursor == 0 or len == limit
133+
local cursorAttrs = {['cursor'] = cursor, ['results'] = {}}
134+
if len == 0 then
135+
return cjson.encode(cursorAttrs)
136+
end
137+
138+
local keyAttrs = {}
139+
for i,key in ipairs(keys) do
140+
local type = redis.call('type', key)['ok']
141+
local pttl = redis.call('pttl', key)
142+
local size = 0
143+
if type == 'string' then
144+
size = redis.call('strlen', key)
145+
elseif type == 'list' then
146+
size = redis.call('llen', key)
147+
elseif type == 'set' then
148+
size = redis.call('scard', key)
149+
elseif type == 'zset' then
150+
size = redis.call('zcard', key)
151+
elseif type == 'hash' then
152+
size = redis.call('hlen', key)
153+
end
154+
local attrs = {['id'] = key, ['type'] = type, ['ttl'] = pttl, ['size'] = size, ['foo'] = 'bar'}
155+
table.insert(keyAttrs, attrs)
156+
end
157+
cursorAttrs['results'] = keyAttrs
158+
return cjson.encode(cursorAttrs)";
159+
160+
var json = exec(r => r.ExecCachedLua(LuaScript, sha1 =>
161+
r.ExecLuaShaAsString(sha1, query, limit.ToString(), "0")));
162+
163+
return json;
164+
}
165+
}
166+
}

0 commit comments

Comments
 (0)