Dynamic number of parameters in redis.call / lua - lua

i want to search by multiple MATCH from lua script, but the number of 'MATCH' depends on the script needs - i need to search for keys that match all words (in this case "aa", "bb") in any order and the number of words may be more (or less)
If its fixed its working like:
local result = redis.call("SCAN", 0, "MATCH", "*aa*", "MATCH", "*bb*")
how i can make it dynamic, where i can add as many MATCH as i need?
something like:
local match={}
for i=1, #ARGV do
table.insert(match, "MATCH")
table.insert(match, "*"..ARGV[i].."*")
end
local result = redis.call("SCAN", 0, match)
...i have tried to put that to string came up with error:
local match="SCAN 0 MATCH *aa* MATCH *bb*"
local result = redis.call(match)
Unknown Redis command called from Lua script

match in ("SCAN", 0, match) is being passed as a table, you need to unpack this.
Something similar on these lines
local match={}
match[1] = "SCAN"
match[2] = 0
for i=2, #ARGV do
match[#match+1] = "MATCH"
match[#match+1] = "*"..ARGV[i].."*"
end
redis.call(unpack(match))

Related

How can I get the number of times an enttry in a table was listed

I need to find a way to see how many times an entry is listed in a table.
I have tried looking at other code for help, and looking at examples online none of them help
local pattern = "(.+)%s?-%s?(.+)"
local table = {"Cald_fan:1", "SomePerson:2", "Cald_fan:3","anotherPerson:4"}
for i,v in pairs(table) do
local UserId, t = string.match(v, pattern)
for i,v in next,UserId do
--I have tried something like this
end
end
it is suppose to say Cald_fan was listed 2 times
Something like this should work:
local pattern = "(.+)%s*:%s*(%d+)"
local tbl = {"Cald_fan:1", "SomePerson:2", "Cald_fan:3","anotherPerson:4"}
local counts = {}
for i,v in pairs(tbl) do
local UserId, t = string.match(v, pattern)
counts[UserId] = 1 + (counts[UserId] or 0)
end
print(counts['Cald_fan']) -- 2
I renamed table to tbl (as using table variable makes the table.* functions not available) and fix the pattern (you had unescaped '-' in it, while your strings had ':').
If the format of your table entries is consistent, you can simply split the strings apart and use the components as keys in a map of counters.
It looks like your table entries are formatted as "[player_name]:[index]", but it doesn't look like you care about the index. But, if the ":" will be in every table entry, you can write a pretty reliable search pattern. You could try something like this :
-- use a list of entries with the format <player_name>:<some_number>
local entries = {"Cald_fan:1", "SomePerson:2", "Cald_fan:3","anotherPerson:4"}
local foundPlayerCount = {}
-- iterate over the list of entries
for i,v in ipairs(entries) do
-- parse out the player name and a number using the pattern :
-- (.+) = capture any number of characters
-- : = match the colon character
-- (%d+)= capture any number of numbers
local playerName, playerIndex = string.match(v, '(.+):(%d+)')
-- use the playerName as a key to count how many times it appears
if not foundPlayerCount[playerName] then
foundPlayerCount[playerName] = 0
end
foundPlayerCount[playerName] = foundPlayerCount[playerName] + 1
end
-- print out all the players
for playerName, timesAppeared in pairs(foundPlayerCount) do
print(string.format("%s was listed %d times", playerName, timesAppeared))
end
If you need to do pattern matching in the future, I highly recommend this article on lua string patterns : http://lua-users.org/wiki/PatternsTutorial
Hope this helps!

wrk executing Lua script

My question is that when I run
wrk -d10s -t20 -c20 -s /mnt/c/xxxx/post.lua http://localhost:xxxx/post
the Lua script that is only executed once? It will only put one item into the database at the URL.
-- example HTTP POST script which demonstrates setting the
-- HTTP method, body, and adding a header
math.randomseed(os.time())
number = math.random()
wrk.method = "POST"
wrk.headers["Content-Type"] = "application/json"
wrk.body = '{"name": "' .. tostring(number) .. '", "title":"test","enabled":true,"defaultValue":false}'
Is there a way to make it create the 'number' variable dynamically and keep adding new items into the database until the 'wrk' command has finished its test? Or that it will keep executing the script for the duration of the test creating and inserting new 'number' variables into 'wrk.body' ?
Apologies I have literally only being looking at Lua for a few hours.
Thanks
When you do
number = math.random
you're not setting number to a random number, you're setting it equal to the function math.random. To set the variable to the value returned by the function, that line should read
number = math.random()
You may also need to set a random seed (with the math.randomseed() function and your choice of an appropriately variable argument - system time is common) to avoid math.random() giving the same result each time the script is run. This should be done before the first call to math.random.
As the script is short, system time probably isn't a good choice of seed here (the script runs far quicker than the value from os.time() changes, so running it several times immediately after one another gives the same results each time). Reading a few bytes from /dev/urandom should give better results.
You could also just use /dev/urandom to generate a number directly, rather than feeding it to math.random as a seed. Like in the code below, as taken from this answer. This isn't a secure random number generator, but for your purposes it would be fine.
urand = assert (io.open ('/dev/urandom', 'rb'))
rand = assert (io.open ('/dev/random', 'rb'))
function RNG (b, m, r)
b = b or 4
m = m or 256
r = r or urand
local n, s = 0, r:read (b)
for i = 1, s:len () do
n = m * n + s:byte (i)
end
return n
end

how to specify number of iterations in a lua pattern match attempt?

I have the following lua code: (based on another post here on stackoverflow)
local chunks = {ip:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")}
if (#chunks == 4) then
for _,v in pairs(chunks) do
if (tonumber(v) < 0 or tonumber(v) > 255) then
return false
end
end
return true
else
return false
end
Trouble with this logic for validating IPv4 Addresses is that when I test addresses like "1.2.3.4.5", the variable "chunks" still evaluates to 4.
How can I change this pattern so that it passes ONLY when there are exactly four octets?
Thanks.
You can use the anchor patterns ^ and $ which mean "match at beginning of string" and "match at end of string" respectively at the beginning/end of your pattern to require the match capture the entire string:
local chunks = {ip:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")}

Multiple-get in one go - Redis

How can we use lua scripting to implement multi-get.
Let's say if I've set name_last to Beckham and name_first to David. What should the lua script be in order to get both name_last and name_first in one go?
I can implement something like:
eval "return redis.call('get',KEYS[1])" 1 foo
to get the value of single key. Just wondering on how to enhance that scripting part to get values related to all keys (or multiple keys) by just making one call to redis server.
First, you want to send the fields you want to return to EVAL (the 0 indicates that there are no KEYS so these arguments will be accessible from ARGV):
eval "..." 0 name_last name_first
Second, you can query the values for the individual fields using MGET:
local values = redis.call('MGET', unpack(ARGV))
Third, you can maps the values back to the field names (the index of each value corresponds to the same field):
local results = {}
for i, key in ipairs(ARGV) do
results[key] = values[i]
end
return results
The command you'll end up executing would be:
eval "local values = redis.call('MGET', unpack(ARGV)); local results = {}; for i, key in ipairs(ARGV) do results[key] = values[i] end; return results" 0 name_last name_first
Do a loop over the KEYS table and for each store its GET response in a take that you return. Untested code:
local t = {}
for _, k in pairs(KEYS) do
t[#t+1] = redis.call('GET', k)
end
return t
P.S. You can also use MGET instead btw :)

Use string.gsub to replace strings, but only whole words

I have a search replace script which works to replace strings. It already has options to do case insensitive searches and "escaped" matches (eg allows searching for % ( etc in the search.
How ever I have now been asked to match whole words only, I have tried adding %s to each end, but that does not match words at the end of a string and I can't then work out how to trap for the white-space items found to leave them intact during the replace.
Do I need to redo the script using string.find and add logic for the word checking or this possible with patterns.
The two functions I use for case insensitive and escaped items are as follows both return the pattern to search for.
-- Build Pattern from String for case insensitive search
function nocase (s)
s = string.gsub(s, "%a", function (c)
return string.format("[%s%s]", string.lower(c),
string.upper(c))
end)
return s
end
function strPlainText(strText)
-- Prefix every non-alphanumeric character (%W) with a % escape character, where %% is the % escape, and %1 is original character
return strText:gsub("(%W)","%%%1")
end
I have a way of doing what I want now, but it's inelegant. Is there a better way?
local strToString = ''
local strSearchFor = strSearchi
local strReplaceWith = strReplace
bSkip = false
if fhGetDataClass(ptr) == 'longtext' then
strBoxType = 'm'
end
if pWhole == 1 then
strSearchFor = '(%s+)('..strSearchi..')(%s+)'
strReplaceWith = '%1'..strReplace..'%3'
end
local strToString = string.gsub(strFromString,strSearchFor,strReplaceWith)
if pWhole == 1 then
-- Special Case search for last word and first word
local strSearchFor3 = '(%s+)('..strSearchi..')$'
local strReplaceWith3 = '%1'..strReplace
strToString = string.gsub(strToString,strSearchFor3,strReplaceWith3)
local strSearchFor3 = '^('..strSearchi..')(%s+)'
local strReplaceWith3 = strReplace..'%2'
strToString = string.gsub(strToString,strSearchFor3,strReplaceWith3)
end
have a way of doing what I want now, but it's inelegant. Is there a better way?
There is an undocumented feature of Lua's pattern matching library called the Frontier Pattern, which will let you write something like this:
function replacetext(source, find, replace, wholeword)
if wholeword then
find = '%f[%a]'..find..'%f[%A]'
end
return (source:gsub(find,replace))
end
local source = 'test testing this test of testicular footest testimation test'
local find = 'test'
local replace = 'XXX'
print(replacetext(source, find, replace, false)) --> XXX XXXing this XXX of XXXicular fooXXX XXXimation XXX
print(replacetext(source, find, replace, true )) --> XXX testing this XXX of testicular footest testimation XXX
do you mean if you pass nocase() foo, you want [fooFOO] instead of [fF][oO][oO]? if so, you could try this?
function nocase (s)
s = string.gsub(s, "(%a+)", function (c)
return string.format("[%s%s]", string.lower(c),
string.upper(c))
end)
return s
end
and if you want an easy way to split a sentence into words, you can use this:
function split(strText)
local words = {}
string.gsub(strText, "(%a+)", function(w)
table.insert(words, w)
end)
return words
end
once you've gotten the words split, it's pretty easy to iterate over the words in the table and do a full comparison against each word.

Resources