Skip to content

Instantly share code, notes, and snippets.

@defp
Last active May 24, 2017 04:19
Show Gist options
  • Save defp/4d07a3f59fd12d057933205b72b3a3f7 to your computer and use it in GitHub Desktop.
Save defp/4d07a3f59fd12d057933205b72b3a3f7 to your computer and use it in GitHub Desktop.
local tconcat = table.concat
local tinsert = table.insert
local tunpack = table.unpack
local INCR = "INCR"
local EXISTS = "EXISTS"
local DEL = "DEL"
local HGETALL = "HGETALL"
local HMSET = "HMSET"
local HDEL = "HDEL"
local function autoid(db, kind, scope)
return db:call(INCR, kind .. "_ids:" .. (scope or "_primary"))
end
local function exists(db, kind, id)
local key = tconcat({ kind, id }, ":")
return db:call(EXISTS, key)
end
local function delete(db, kind, id)
local key = tconcat({ kind, id }, ":")
return db:call(DEL, key)
end
local function find(db, kind, id)
local data = {}
local key = tconcat({ kind, id }, ":")
local fields = db:call(HGETALL, key)
if fields then
for i = 1, #fields, 2 do
data[fields[i]] = fields[i + 1]
end
end
return data
end
local function set(db, kind, id, fields)
local key = tconcat({ kind, id }, ":")
local args = { HMSET, key }
local empty = true
for k, v in pairs(fields) do
empty = false
tinsert(args, k)
tinsert(args, v)
end
if not empty then
db:call(tunpack(args))
end
return id
end
local function unset(db, kind, id, field, ...)
local key = tconcat({ kind, id }, ":")
local args = { HDEL, key, field, ... }
return db:call(tunpack(args))
end
return {
autoid = autoid,
exists = exists,
delete = delete,
find = find,
set = set,
unset = unset
}
local inspect = require "inspect"
local util = require "skynetx.util"
local MessagePack = require "MessagePack"
local tinsert = table.insert
local tunpack = table.unpack
local sgmatch = string.gmatch
local tconcat = table.concat
local sformat = string.format
local mtointeger = math.tointeger
local Model = {}
local DEFAULT_FIELD = { type = require("skynetx.model.types.tstring") }
local function nested_set(map, field, keylist, value)
for i = 1, #keylist - 1 do
local key = tostring(keylist[i])
if not map[key] then
map[key] = {}
end
map = map[key]
end
local key = keylist[#keylist]
if field.keytype then
key = field.keytype.decode(key)
end
map[key] = value
end
local function apply_default(obj)
for k, spec in pairs(obj.model.fields) do
if spec.default ~= nil and obj.data[k] == nil then
if type(spec.default) == "function" then
obj.data[k] = spec.default(obj)
elseif type(spec.default) == "table" then
obj.data[k] = util.clone_data(spec.default)
else
obj.data[k] = spec.default
end
end
end
end
function Model:get(key)
local map = self.data
if type(key) ~= "table" then
return map[key]
else
for i = 1, #key - 1 do
map = map[tostring(key[i])]
if not map then
return nil
end
end
return map[key[#key]]
end
end
function Model:set(key, value)
local keylist = key
local plain_key
if type(key) ~= "table" then
plain_key = key
keylist = { key }
else
plain_key= tconcat(keylist, "/")
end
local field = self.model.fields[keylist[1]] or DEFAULT_FIELD
local changed = self.changes[plain_key]
if changed then
changed[3] = value
else
changed = { field, self:get(keylist), value , keylist}
end
nested_set(self.data, field, keylist, changed[3])
self.changes[plain_key] = changed
end
function Model:incr(key, diff)
local amount = self:get(key) or 0
self:set(key, amount + (diff or 1))
return self
end
function Model:set_from_db(key, value)
local field_name = key[1]
local field = self.model.fields[field_name] or DEFAULT_FIELD
local decoded = field.type.decode(value)
if type(key) == "table" then
nested_set(self.data, field, key, decoded)
else
self.data[key] = decoded
end
end
function Model:save(db)
local unset_fields = {}
local set_fields = {}
if not self.id then
self.id = self.model.autoid(db)
end
if not next(self.changes) then
return self.id
end
for key, info in pairs(self.changes) do
if info[3] ~= nil then
set_fields[key] = info[1].type.encode(info[3])
else
tinsert(unset_fields, key)
end
end
local should_unset = #unset_fields > 0
local should_set = not not next(set_fields)
if self.data.v and (should_unset or should_set) then
should_set = true
local v = self.data.v + 1
self.data.v = v
set_fields.v = v
end
if should_unset then
self.dao.unset(db, self.model.name, self.id, tunpack(unset_fields))
end
if should_set then
self.dao.set(db, self.model.name, self.id, set_fields)
end
self.changes = {}
return self.id
end
function Model:delete(db)
if self.id then
self.model.delete(db, self.id)
end
end
function Model:__tostring()
return sformat("%s#%s%s", self.model.name, self.id, inspect(self.data, { newline = "", indent = "" }))
end
local function new(model)
assert(model.name, "requires model name")
local dao_lib = model.dao or "skynetx.dao.dao_redis"
local dao = require(dao_lib)
model.dao = dao
local meta = model.meta or {}
model.meta = meta
if not meta.__index then
for k, v in pairs(Model) do
if not meta[k] then
meta[k] = v
end
end
meta.__index = meta
end
function model.autoid(db, scope)
return dao.autoid(db, model.name, scope)
end
function model.new(data)
local obj = setmetatable({ model = model, dao = dao, data = data or {}, changes = {} }, meta)
apply_default(obj)
return obj
end
function model.exists(db, id)
return dao.exists(db, model.name, id)
end
function model.delete(db, id)
return dao.delete(db, model.name, id)
end
function model.find(db, id)
local fields = dao.find(db, model.name, id)
if fields and next(fields) then
local obj = model.new()
obj.id = mtointeger(id)
local unset_fields = {}
for k, v in pairs(fields) do
local keylist = {}
for component in sgmatch(k, "[^/]+") do
tinsert(keylist, component)
end
if obj.model.fields[keylist[1]] then
obj:set_from_db(keylist, v)
else
tinsert(unset_fields, k)
end
end
if #unset_fields > 0 then
obj.dao.unset(db, obj.model.name, obj.id, tunpack(unset_fields))
end
apply_default(obj)
return obj
else
error(sformat("%s#%s not found", model.name, id))
end
end
function model.encode(obj)
return MessagePack.pack(obj.data)
end
function model.decode(str)
local data = MessagePack.unpack(str)
return model.new(data)
end
return model
end
return {
new = new
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment