向数据库中插入记录的时候,会进行插入校验,校验过程会用到schema中定义的类型以及校验函数。这里在数据库操作封装的基础之上,分析kong/dao/dao.lua
中DAO:insert()
的实现。
2019-05-06 16:28:56:kong 1.1.x有了一个重大变换,实现了db-less模式,可以不使用数据库了,见笔记二十六:查看全部笔记。
如果是刚开始学习kong,直接从1.x开始,0.x已经不再维护,0.15是0.x的最后一个版本。
前19篇笔记是刚开始接触kong时记录的,使用的版本是0.14.1,当时对kong一知半解,笔记比较杂乱。第二十篇开始是再次折腾时的笔记,使用的版本是1.0.3,笔记相对条理一些。
从0.x到1.x需要关注的变化有:
DAO:insert()中写入之前的操作的是校验:
-- kong/dao/dao.lua
function DAO:insert(tbl, options)
options = options or {}
check_arg(tbl, 1, "table")
check_arg(options, 2, "table")
local model = self.model_mt(tbl)
local ok, err = model:validate {dao = self} -- 校验 ---
if not ok then
return ret_error(self.db.name, nil, err)
end
...
校验使用的model是用self.model_mt()
方法创建的,要去找到model_mt()的方法。
存放了大部分dao对象的DAO是用kong/dao/factory.lua
中的function _M.new(kong_config, new_db)
创建的。
-- kong/dao/factory.lua
function _M.new(kong_config, new_db)
...
load_daos(self, schemas, constraints)
...
在load_daos()
中创建了一个个具体的dao:
-- kong/dao/factory.lua
...
local ModelFactory = require "kong.dao.model_factory"
...
local function load_daos(self, schemas, constraints)
...
for m_name, schema in pairs(schemas) do
self.daos[m_name] = DAO(self.db, ModelFactory(schema), schema,
constraints[m_name])
end
...
需要注意第二个参数ModelFactory(schema)
,它就是要找的model_mt():
-- kong/dao/dao.lua
-- @param model_mt The related model metatable. Such metatables contain, among other things, validation methods.
function DAO:new(db, model_mt, schema, constraints)
self.db = db
self.model_mt = model_mt
self.schema = schema
self.table = schema.table
self.constraints = constraints
end
ModelFactory()
使用传入的schema构建了一个table,并为这个table的设置了包含validate(opts)
方法的元表:
-- kong/dao/model_factory.lua
local validate = schemas_validation.validate_entity
return setmetatable({}, {
__call = function(_, schema)
local Model_mt = {}
Model_mt.__meta = {
__schema = schema,
__name = schema.name,
__table = schema.table
}
...
function Model_mt:validate(opts)
local ok, errors, self_check_err = validate(self, self.__schema, opts)
if errors ~= nil then
return nil, Errors.schema(errors)
elseif self_check_err ~= nil then
-- TODO: merge errors and self_check_err now that our errors handle this
return nil, Errors.schema(self_check_err)
end
return ok
end
...
return setmetatable({}, {
__call = function(_, tbl)
local m = {}
for k,v in pairs(tbl) do
m[k] = v
end
return setmetatable(m, Model_mt)
end
})
end
})
Model_mt:validate()
使用的validate()
方法在kong/dao/schemas_validatio.lua
中实现,用schema中定义的每个filed对输入的数据进行校验:
--kong/dao/schemas_validatio.lua
function _M.validate_entity(tbl, schema, options)
...
for tk, t in pairs(key_values) do
...
for column, v in pairs(schema.fields) do
if t[column] ~= nil and t[column] ~= ngx.null and v.type ~= nil then
local is_valid_type
-- ALIASES: number, timestamp, boolean and array can be passed as strings and will be converted
if type(t[column]) == "string" then
if schema.fields[column].trim_whitespace ~= false then
t[column] = utils.strip(t[column])
end
if v.type == "boolean" then
local bool = t[column]:lower()
is_valid_type = bool == "true" or bool == "false"
t[column] = bool == "true"
elseif v.type == "array" then
is_valid_type = validate_array(v, t, column)
elseif v.type == "number" or v.type == "timestamp" then
t[column] = tonumber(t[column])
is_valid_type = validate_type(v.type, t[column])
else -- if string
is_valid_type = validate_type(v.type, t[column])
end
else
is_valid_type = validate_type(v.type, t[column])
end
if not is_valid_type and POSSIBLE_TYPES[v.type] then
errors = utils.add_error(errors, error_prefix .. column,
string.format("%s is not %s %s", column, v.type == "array" and "an" or "a", v.type))
goto continue
end
end
...
可以看到支持的类型有string
、 boolean
、array
、number
、timestamp
。
field的成员都有:
v.type
v.default
v.immutable
v.enum
v.regex
v.schema
v.required
v.dao_insert_value
v.func
Model_mt:validate()
还会调用schema的self_check()
进行检查:
--kong/dao/schemas_validatio.lua
function _M.validate_entity(tbl, schema, options)
...
for tk, t in pairs(key_values) do
...
if errors == nil and type(schema.self_check) == "function" then
local nil_c = {}
for column in pairs(schema.fields) do
if t[column] == ngx.null then
t[column] = nil
table.insert(nil_c, column)
end
end
local ok, err = schema.self_check(schema, t, options.dao, options.update)
if ok == false then
return false, nil, err
end
...
end
kong/dao/schemas中有的schema实现了self_check()
,例如kong/dao/schemas/plugins.lua
:
--kong/dao/schemas/plugins.lua
return {
table = "plugins",
primary_key = {"id", "name"},
cache_key = { "name", "route_id", "service_id", "consumer_id", "api_id" },
fields = {
...
config = {
type = "table",
schema = load_config_schema,
default = {}
},
...
},
self_check = function(self, plugin_t, dao, is_update)
if plugin_t.api_id and (plugin_t.route_id or plugin_t.service_id) then
return false, Errors.schema("cannot configure plugin with api_id " ..
"and one of route_id or service_id")
end
-- Load the config schema
local config_schema, err = self.fields.config.schema(plugin_t)
...
if config_schema.self_check and type(config_schema.self_check) == "function" then
local ok, err = config_schema.self_check(config_schema, plugin_t.config and plugin_t.config or {}, dao, is_update)
...
end
}
需要注意的是用self.fields.confg.schema()
创建的config_schema
也可能实现了self_check
。self.fields.confg.schema是函数load_config_schema()
,它加载了插件目录中的schema。
-- kong/dao/schemas/plugins.lua
local function load_config_schema(plugin_t)
...
local loaded, plugin_schema = utils.load_module_if_exists("kong.plugins."
.. plugin_name .. ".schema")
if loaded then
return plugin_schema
...
校验通过之后,使用db的insert方法插入数据:
-- kong/dao/dao.lua
function DAO:insert(tbl, options)
...
local res, err = self.db:insert(self.table, self.schema, model, self.constraints, options)
if not err and not options.quiet then
if self.events then
local _, err = self.events.post_local("dao:crud", "create", {
schema = self.schema,
operation = "create",
entity = res,
})
if err then
ngx.log(ngx.ERR, "could not propagate CRUD operation: ", err)
end
end
end
return ret_error(self.db.name, res, err)
需要注意的是,如果dao对象中有events
,插入之后要向dao:crud
频道中发出一个create
事件。