Last active
May 24, 2017 04:19
-
-
Save defp/4d07a3f59fd12d057933205b72b3a3f7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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