Scripting Extension of Kyoto Tycoon.

This module is imported by the server program (ktserver) of Kyoto Tycoon. The server must be built enabling the scripting extension by Lua.

The start-up command has the option "-scr" to specify a script file defining functions in Lua.

$ ktserver -scr myscript.lua

You can define your own functions in the global name space so that clients can call them by specifying the name of each function. The function name must be composed of ASCII alphabets and numbers because of security reasons. Each function receives two tables as arguments. The first table contains the input data specified by clients. The second table is to contain the output data to send to clients.

In the scripting environment, the global variable "__kyototycoon__" is defined by default. It contains objects to access server resources. The most important one is "__kyototycoon__.db", which is the primary database managed by the server. "__kyototycoon__.dbs" is an array of databases managed by the server. The return value of each function must be "__kyototycoon__.RVSUCCESS" for success or a related code for error.

Because the server uses respective instances of the Lua processor for each native thread, you cannot use global variables to share data among sessions. Use a cache database managed by the server for that purpose.

The following is an example code.

kt = __kyototycoon__
db = kt.db

-- log the start-up message
if kt.thid == 0 then
   kt.log("system", "the Lua script has been loaded")
end

-- echo back the input data as the output data
function echo(inmap, outmap)
   for key, value in pairs(inmap) do
      outmap[key] = value
   end
   return kt.RVSUCCESS
end

-- report the internal state of the server
function report(inmap, outmap)
   outmap["__kyototycoon__.VERSION"] = kt.VERSION
   outmap["__kyototycoon__.thid"] = kt.thid
   outmap["__kyototycoon__.db"] = tostring(kt.db)
   for i = 1, #kt.dbs do
      local key = "__kyototycoon__.dbs[" .. i .. "]"
      outmap[key] = tostring(kt.dbs[i])
   end
   local names = ""
   for name, value in pairs(kt.dbs) do
      if #names > 0 then names = names .. "," end
      names = names .. name
   end
   outmap["names"] = names
   return kt.RVSUCCESS
end

-- log a message
function log(inmap, outmap)
   local kind = inmap.kind
   local message = inmap.message
   if not message then
      return kt.RVEINVALID
   end
   if not kind then
      kind = "info"
   end
   kt.log(kind, message)
   return kt.RVSUCCESS
end

-- store a record
function set(inmap, outmap)
   local key = inmap.key
   local value = inmap.value
   if not key or not value then
      return kt.RVEINVALID
   end
   local xt = inmap.xt
   if not db:set(key, value, xt) then
      return kt.RVEINTERNAL
   end
   return kt.RVSUCCESS
end

-- remove a record
function remove(inmap, outmap)
   local key = inmap.key
   if not key then
      return kt.RVEINVALID
   end
   if not db:remove(key) then
      local err = db:error()
      if err:code() == kt.Error.NOREC then
         return kt.RVELOGIC
      end
      return kt.RVEINTERNAL
   end
   return kt.RVSUCCESS
end

-- increment the numeric string value
function increment(inmap, outmap)
   local key = inmap.key
   local num = inmap.num
   if not key or not num then
      return kt.RVEINVALID
   end
   local function visit(rkey, rvalue, rxt)
      rvalue = tonumber(rvalue)
      if not rvalue then rvalue = 0 end
      num = rvalue + num
      return num
   end
   if not db:accept(key, visit) then
      return kt.REINTERNAL
   end
   outmap.num = num
   return kt.RVSUCCESS
end

-- retrieve the value of a record
function get(inmap, outmap)
   local key = inmap.key
   if not key then
      return kt.RVEINVALID
   end
   local value, xt = db:get(key)
   if value then
      outmap.value = value
      outmap.xt = xt
   else
      local err = db:error()
      if err:code() == kt.Error.NOREC then
         return kt.RVELOGIC
      end
      return kt.RVEINTERNAL
   end
   return kt.RVSUCCESS
end

-- store records at once
function setbulk(inmap, outmap)
   local num = db:set_bulk(inmap)
   if num < 0 then
      return kt.RVEINTERNAL
   end
   outmap["num"] = num
   return kt.RVSUCCESS
end

-- remove records at once
function removebulk(inmap, outmap)
   local keys = {}
   for key, value in pairs(inmap) do
      table.insert(keys, key)
   end
   local num = db:remove_bulk(keys)
   if num < 0 then
      return kt.RVEINTERNAL
   end
   outmap["num"] = num
   return kt.RVSUCCESS
end

-- retrieve records at once
function getbulk(inmap, outmap)
   local keys = {}
   for key, value in pairs(inmap) do
      table.insert(keys, key)
   end
   local res = db:get_bulk(keys)
   if not res then
      return kt.RVEINTERNAL
   end
   for key, value in pairs(res) do
      outmap[key] = value
   end
   return kt.RVSUCCESS
end

-- move the value of a record to another
function move(inmap, outmap)
   local srckey = inmap.src
   local destkey = inmap.dest
   if not srckey or not destkey then
      return kt.RVEINVALID
   end
   local keys = { srckey, destkey }
   local first = true
   local srcval = nil
   local srcxt = nil
   local function visit(key, value, xt)
      if first then
         srcval = value
         srcxt = xt
         first = false
         return kt.Visitor.REMOVE
      end
      if srcval then
         return srcval, srcxt
      end
      return kt.Visitor.NOP
   end
   if not db:accept_bulk(keys, visit) then
      return kt.REINTERNAL
   end
   if not srcval then
      return kt.RVELOGIC
   end
   return kt.RVSUCCESS
end

-- list all records
function list(inmap, outmap)
   local cur = db:cursor()
   cur:jump()
   while true do
      local key, value, xt = cur:get(true)
      if not key then break end
      outmap[key] = value
   end
   return kt.RVSUCCESS
end

-- upcate all characters in the value of a record
function upcase(inmap, outmap)
   local key = inmap.key
   if not key then
      return kt.RVEINVALID
   end
   local function visit(key, value, xt)
      if not value then
         return kt.Visitor.NOP
      end
      return string.upper(value)
   end
   if not db:accept(key, visit) then
      return kt.RVEINTERNAL
   end
   return kt.RVSUCCESS
end

-- prolong the expiration time of a record
function survive(inmap, outmap)
   local key = inmap.key
   if not key then
      return kt.RVEINVALID
   end
   local function visit(key, value, xt)
      if not value then
         return kt.Visitor.NOP
      end
      outmap.old_xt = xt
      if xt > kt.time() + 3600 then
         return kt.Visitor.NOP
      end
      return value, 3600
   end
   if not db:accept(key, visit) then
      return kt.RVEINTERNAL
   end
   return kt.RVSUCCESS
end

-- count words with the MapReduce framework
function countwords(inmap, outmap)
   local function map(key, value, emit)
      local values = kt.split(value, " ")
      for i = 1, #values do
         local word = kt.regex(values[i], "[ .?!:;]", "")
         word = string.lower(word)
         if #word > 0 then
            if not emit(word, "") then
               return false
            end
         end
      end
      return true
   end
   local function reduce(key, iter)
      local count = 0
      while true do
         local value = iter()
         if not value then
            break
         end
         count = count + 1
      end
      outmap[key] = count
      return true
   end
   if not db:mapreduce(map, reduce, nil, kt.DB.XNOLOCK) then
      return kt.RVEINTERNAL
   end
   return kt.RVSUCCESS
end

To call the above functions, execute the following commands.

$ ktremotemgr script -arg key1 value1 -arg key2 value2 echo
$ ktremotemgr script report
$ ktremotemgr script -arg kind info -arg message hello log
$ ktremotemgr script -arg key one -arg value first set
$ ktremotemgr script -arg key two -arg value second set
$ ktremotemgr script -arg key three -arg value third set
$ ktremotemgr script -arg key three remove
$ ktremotemgr script -arg key two get
$ ktremotemgr script list

Modules

kyototycoon The scripting extension of Kyoto Tycoon.
Kyoto Tycoon Manual