添加orm进行数据库简化操作
This commit is contained in:
parent
9da130ca55
commit
025c47b58d
141
src/share/orm/class/fields.lua
Normal file
141
src/share/orm/class/fields.lua
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Class --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local Field = {
|
||||||
|
-- Table column type
|
||||||
|
__type__ = "varchar",
|
||||||
|
|
||||||
|
-- Validator handler
|
||||||
|
validator = function (self, value)
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Default parser
|
||||||
|
as = function (value)
|
||||||
|
return value
|
||||||
|
end,
|
||||||
|
|
||||||
|
to_type = Type.to.str,
|
||||||
|
|
||||||
|
-- Call when create new field in some table
|
||||||
|
register = function (self, args)
|
||||||
|
if not args then
|
||||||
|
args = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- New field type
|
||||||
|
-------------------------------------------
|
||||||
|
-- @args {table}
|
||||||
|
-- Table can have parametrs:
|
||||||
|
-- @args.__type__ {string} some sql valid type
|
||||||
|
-- @args.validator {function} type validator
|
||||||
|
-- @args.as {function} return parse value
|
||||||
|
-------------------------------------------
|
||||||
|
new_field_type = {
|
||||||
|
-- some sql valid type
|
||||||
|
__type__ = args.__type__ or self.__type__,
|
||||||
|
|
||||||
|
-- Validator handler
|
||||||
|
validator = args.validator or self.validator,
|
||||||
|
|
||||||
|
-- Parse variable for equation
|
||||||
|
as = args.as or self.as,
|
||||||
|
|
||||||
|
-- Cast values to correct type
|
||||||
|
to_type = args.to_type or self.to_type,
|
||||||
|
|
||||||
|
-- Default settings for type
|
||||||
|
settings = args.settings or {},
|
||||||
|
|
||||||
|
-- Get new table column instance
|
||||||
|
new = function (this, args)
|
||||||
|
if not args then
|
||||||
|
args = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local new_self = {
|
||||||
|
-- link to field instance
|
||||||
|
field = this,
|
||||||
|
|
||||||
|
-- Column name
|
||||||
|
name = nil,
|
||||||
|
|
||||||
|
-- Parent table
|
||||||
|
__table__ = nil,
|
||||||
|
|
||||||
|
-- table column settings
|
||||||
|
settings = {
|
||||||
|
default = nil,
|
||||||
|
null = false,
|
||||||
|
unique = false,
|
||||||
|
max_length = nil,
|
||||||
|
primary_key = false,
|
||||||
|
escape_value = false
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Return string for column type create
|
||||||
|
_create_type = function (this)
|
||||||
|
local _type = this.field.__type__
|
||||||
|
|
||||||
|
if this.settings.max_length and this.settings.max_length > 0 then
|
||||||
|
_type = _type .. "(" .. this.settings.max_length .. ")"
|
||||||
|
end
|
||||||
|
|
||||||
|
if this.settings.primary_key then
|
||||||
|
_type = _type .. " PRIMARY KEY"
|
||||||
|
end
|
||||||
|
|
||||||
|
if this.settings.auto_increment and DB.type ~= SQLITE then
|
||||||
|
_type = _type .. " AUTO_INCREMENT"
|
||||||
|
end
|
||||||
|
|
||||||
|
if this.settings.unique then
|
||||||
|
_type = _type .. " UNIQUE"
|
||||||
|
end
|
||||||
|
|
||||||
|
_type = _type .. (this.settings.null and " NULL"
|
||||||
|
or " NOT NULL")
|
||||||
|
return _type
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Set Default settings
|
||||||
|
|
||||||
|
--
|
||||||
|
-- The content of the settings table must be copied because trying to copy a table
|
||||||
|
-- directly will result in a reference to the original table, thus all
|
||||||
|
-- instances of the same field type would have the same settings table.
|
||||||
|
--
|
||||||
|
for index, setting in pairs(new_self.field.settings) do
|
||||||
|
new_self.settings[index] = setting
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set settings for column
|
||||||
|
if args.max_length then
|
||||||
|
new_self.settings.max_length = args.max_length
|
||||||
|
end
|
||||||
|
|
||||||
|
if args.null ~= nil then
|
||||||
|
new_self.settings.null = args.null
|
||||||
|
end
|
||||||
|
|
||||||
|
if new_self.settings.foreign_key and args.to then
|
||||||
|
new_self.settings.to = args.to
|
||||||
|
end
|
||||||
|
|
||||||
|
if args.escape_value then
|
||||||
|
new_self.settings.escape_value = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return new_self
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(new_field_type, {__call = new_field_type.new})
|
||||||
|
|
||||||
|
return new_field_type
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
return Field
|
||||||
103
src/share/orm/class/global.lua
Normal file
103
src/share/orm/class/global.lua
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Constants --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- Backtrace types
|
||||||
|
ERROR = 'e'
|
||||||
|
WARNING = 'w'
|
||||||
|
INFO = 'i'
|
||||||
|
DEBUG = 'd'
|
||||||
|
|
||||||
|
All_Tables = {}
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Helping functions --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local _pairs = pairs
|
||||||
|
|
||||||
|
function pairs(Table)
|
||||||
|
if Table.__classname__ == QUERY_LIST then
|
||||||
|
return Table()
|
||||||
|
else
|
||||||
|
return _pairs(Table)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BACKTRACE(tracetype, message)
|
||||||
|
if DB.backtrace then
|
||||||
|
if tracetype == ERROR then
|
||||||
|
print("[SQL:Error] " .. message)
|
||||||
|
os.exit()
|
||||||
|
|
||||||
|
elseif tracetype == WARNING then
|
||||||
|
print("[SQL:Warning] " .. message)
|
||||||
|
|
||||||
|
elseif tracetype == INFO then
|
||||||
|
print("[SQL:Info] " .. message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if DB.DEBUG and tracetype == DEBUG then
|
||||||
|
print("[SQL:Debug] " .. message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.endswith(String, End)
|
||||||
|
return End == '' or string.sub(String, -string.len(End)) == End
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.cutend(String, End)
|
||||||
|
return End == '' and String or string.sub(String, 0, -#End - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.divided_into(String, separator)
|
||||||
|
local separator_pos = string.find(String, separator)
|
||||||
|
return string.sub(String, 0, separator_pos - 1),
|
||||||
|
string.sub(String, separator_pos + 1, #String)
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.has_key(array, key)
|
||||||
|
if Type.is.table(key) and key.colname then
|
||||||
|
key = key.colname
|
||||||
|
end
|
||||||
|
|
||||||
|
for array_key, _ in pairs(array) do
|
||||||
|
if array_key == key then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.has_value(array, value)
|
||||||
|
if Type.is.table(value) and value.colname then
|
||||||
|
value = value.colname
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, array_value in pairs(array) do
|
||||||
|
if array_value == value then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.join(array, separator)
|
||||||
|
local result = ""
|
||||||
|
local counter = 0
|
||||||
|
|
||||||
|
if not separator then
|
||||||
|
separator = ","
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, value in pairs(array) do
|
||||||
|
if counter ~= 0 then
|
||||||
|
value = separator .. value
|
||||||
|
end
|
||||||
|
|
||||||
|
result = result .. value
|
||||||
|
counter = counter + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
25
src/share/orm/class/property.lua
Normal file
25
src/share/orm/class/property.lua
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
-- Function for create column functions
|
||||||
|
local function Property(args)
|
||||||
|
return function (colname)
|
||||||
|
local column_func = {
|
||||||
|
-- class type
|
||||||
|
__classtype__ = AGGREGATOR,
|
||||||
|
|
||||||
|
-- Asc column name
|
||||||
|
colname = colname,
|
||||||
|
|
||||||
|
-- concatenate methods
|
||||||
|
__concat = function (left_part, right_part)
|
||||||
|
return tostring(left_part) .. tostring(right_part)
|
||||||
|
end,
|
||||||
|
|
||||||
|
__tostring = args.parse or self.parse
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(column_func, {__tostring = column_func.__tostring,
|
||||||
|
__concat = column_func.__concat})
|
||||||
|
return column_func
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Property
|
||||||
231
src/share/orm/class/query.lua
Normal file
231
src/share/orm/class/query.lua
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- query.lua --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
-- Creates an instance to retrieve and manage a
|
||||||
|
-- string table with the database
|
||||||
|
---------------------------------------------------
|
||||||
|
-- @own_table {table} parent table instace
|
||||||
|
-- @data {table} data returned by the query to the database
|
||||||
|
--
|
||||||
|
-- @return {table} database query instance
|
||||||
|
---------------------------------------------------
|
||||||
|
function Query(own_table, data)
|
||||||
|
local query = {
|
||||||
|
------------------------------------------------
|
||||||
|
-- Table info varibles --
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
-- Table instance
|
||||||
|
own_table = own_table,
|
||||||
|
|
||||||
|
-- Column data
|
||||||
|
-- Structure example of one column
|
||||||
|
-- fieldname = {
|
||||||
|
-- old = nil,
|
||||||
|
-- new = nil
|
||||||
|
-- }
|
||||||
|
_data = {},
|
||||||
|
|
||||||
|
-- Data only for read mode
|
||||||
|
_readonly = {},
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
-- Metamethods --
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
-- Get column value
|
||||||
|
-----------------------------------------
|
||||||
|
-- @colname {string} column name in table
|
||||||
|
--
|
||||||
|
-- @return {string|boolean|number|nil} column value
|
||||||
|
-----------------------------------------
|
||||||
|
_get_col = function (self, colname)
|
||||||
|
if self._data[colname] and self._data[colname].new then
|
||||||
|
return self._data[colname].new
|
||||||
|
|
||||||
|
elseif self._readonly[colname] then
|
||||||
|
return self._readonly[colname]
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Set column new value
|
||||||
|
-----------------------------------------
|
||||||
|
-- @colname {string} column name in table
|
||||||
|
-- @colvalue {string|number|boolean} new column value
|
||||||
|
-----------------------------------------
|
||||||
|
_set_col = function (self, colname, colvalue)
|
||||||
|
local coltype
|
||||||
|
|
||||||
|
if self._data[colname] and self._data[colname].new and colname ~= ID then
|
||||||
|
coltype = self.own_table:get_column(colname)
|
||||||
|
|
||||||
|
if coltype and coltype.field.validator(colvalue) then
|
||||||
|
self._data[colname].old = self._data[colname].new
|
||||||
|
self._data[colname].new = colvalue
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "Not valid column value for update")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
-- Private methods --
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
-- Add new row to table
|
||||||
|
_add = function (self)
|
||||||
|
local insert = "INSERT INTO `" .. self.own_table.__tablename__ .. "` ("
|
||||||
|
local counter = 0
|
||||||
|
local values = ""
|
||||||
|
local _connect
|
||||||
|
local value
|
||||||
|
local colname
|
||||||
|
|
||||||
|
for _, table_column in pairs(self.own_table.__colnames) do
|
||||||
|
colname = table_column.name
|
||||||
|
|
||||||
|
if colname ~= ID then
|
||||||
|
|
||||||
|
-- If value exist correct value
|
||||||
|
if self[colname] ~= nil then
|
||||||
|
value = self[colname]
|
||||||
|
|
||||||
|
if table_column.field.validator(value) then
|
||||||
|
value = _G.escapeValue(self.own_table, colname, value)
|
||||||
|
value = table_column.field.as(value)
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "Wrong type for table '" ..
|
||||||
|
self.own_table.__tablename__ ..
|
||||||
|
"' in column '" .. tostring(colname) .. "'")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set default value
|
||||||
|
elseif table_column.settings.default then
|
||||||
|
value = table_column.field.as(table_column.settings.default)
|
||||||
|
|
||||||
|
else
|
||||||
|
value = "NULL"
|
||||||
|
end
|
||||||
|
|
||||||
|
colname = "`" .. colname .. "`"
|
||||||
|
|
||||||
|
-- TODO: save in correct type
|
||||||
|
if counter ~= 0 then
|
||||||
|
colname = ", " .. colname
|
||||||
|
value = ", " .. value
|
||||||
|
end
|
||||||
|
|
||||||
|
values = values .. value
|
||||||
|
insert = insert .. colname
|
||||||
|
|
||||||
|
counter = counter + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
insert = insert .. ") \n\t VALUES (" .. values .. ")"
|
||||||
|
|
||||||
|
-- TODO: return valid ID
|
||||||
|
_connect = db:insert(insert)
|
||||||
|
|
||||||
|
self._data.id = {new = _connect}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Update data in database
|
||||||
|
_update = function (self)
|
||||||
|
local update = "UPDATE `" .. self.own_table.__tablename__ .. "` "
|
||||||
|
local equation_for_set = {}
|
||||||
|
local set, coltype
|
||||||
|
|
||||||
|
for colname, colinfo in pairs(self._data) do
|
||||||
|
if colinfo.old ~= colinfo.new and colname ~= ID then
|
||||||
|
coltype = self.own_table:get_column(colname)
|
||||||
|
|
||||||
|
if coltype and coltype.field.validator(colinfo.new) then
|
||||||
|
|
||||||
|
local colvalue = _G.escapeValue(self.own_table, colname, colinfo.new)
|
||||||
|
set = " `" .. colname .. "` = " .. coltype.field.as(colvalue)
|
||||||
|
|
||||||
|
table.insert(equation_for_set, set)
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "Can't update value for column `" ..
|
||||||
|
Type.to.str(colname) .. "`")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
set = table.join(equation_for_set, ",")
|
||||||
|
|
||||||
|
if set ~= "" then
|
||||||
|
update = update .. " SET " .. set .. "\n\t WHERE `" .. ID .. "` = " .. self.id
|
||||||
|
db:execute(update)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
-- User methods --
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
-- save row
|
||||||
|
save = function (self)
|
||||||
|
if self.id then
|
||||||
|
self:_update()
|
||||||
|
else
|
||||||
|
self:_add()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- delete row
|
||||||
|
delete = function (self)
|
||||||
|
local delete, result
|
||||||
|
|
||||||
|
if self.id then
|
||||||
|
delete = "DELETE FROM `" .. self.own_table.__tablename__ .. "` "
|
||||||
|
delete = delete .. "WHERE `" .. ID .. "` = " .. self.id
|
||||||
|
|
||||||
|
db:execute(delete)
|
||||||
|
end
|
||||||
|
self._data = {}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
if data then
|
||||||
|
local current_table
|
||||||
|
|
||||||
|
for colname, colvalue in pairs(data) do
|
||||||
|
if query.own_table:has_column(colname) then
|
||||||
|
colvalue = query.own_table:get_column(colname)
|
||||||
|
.field.to_type(colvalue)
|
||||||
|
query._data[colname] = {
|
||||||
|
new = colvalue,
|
||||||
|
old = colvalue
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if _G.All_Tables[colname] then
|
||||||
|
current_table = _G.All_Tables[colname]
|
||||||
|
colvalue = Query(current_table, colvalue)
|
||||||
|
|
||||||
|
query._readonly[colname .. "_all"] = QueryList(current_table, {})
|
||||||
|
query._readonly[colname .. "_all"]:add(colvalue)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
query._readonly[colname] = colvalue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
BACKTRACE(INFO, "Create empty row instance for table '" ..
|
||||||
|
self.own_table.__tablename__ .. "'")
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(query, {__index = query._get_col,
|
||||||
|
__newindex = query._set_col})
|
||||||
|
|
||||||
|
return query
|
||||||
|
end
|
||||||
|
|
||||||
|
local QueryList = require('orm.class.query_list')
|
||||||
|
|
||||||
|
return Query, QueryList
|
||||||
109
src/share/orm/class/query_list.lua
Normal file
109
src/share/orm/class/query_list.lua
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- query_list.lua --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function QueryList(own_table, rows)
|
||||||
|
local current_query
|
||||||
|
local _query_list = {
|
||||||
|
------------------------------------------------
|
||||||
|
-- Table info varibles --
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
--class name
|
||||||
|
__classname__ = QUERY_LIST,
|
||||||
|
|
||||||
|
-- Own Table
|
||||||
|
own_table = own_table,
|
||||||
|
|
||||||
|
-- Stack of data rows
|
||||||
|
_stack = {},
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
-- Metamethods --
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
-- Get n-th position value from Query stack
|
||||||
|
------------------------------------------------
|
||||||
|
-- @position {integer} position element is stack
|
||||||
|
--
|
||||||
|
-- @return {Query Instance} Table row instance
|
||||||
|
-- in n-th position
|
||||||
|
------------------------------------------------
|
||||||
|
__index = function (self, position)
|
||||||
|
if Type.is.int(position) and position >= 1 then
|
||||||
|
return self._stack[position]
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
__call = function (self)
|
||||||
|
return pairs(self._stack)
|
||||||
|
end,
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
-- User methods --
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
-- Get Query instance by id
|
||||||
|
------------------------------------------------
|
||||||
|
-- @id {integer} table data row identifier
|
||||||
|
--
|
||||||
|
-- @return {table/nil} get Query instance or nil if
|
||||||
|
-- instance is not exists
|
||||||
|
------------------------------------------------
|
||||||
|
with_id = function (self, id)
|
||||||
|
if Type.is.int(id) then
|
||||||
|
for _, query in pairs(self) do
|
||||||
|
if query.id == id then
|
||||||
|
return query
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "ID `" .. id .. "` is not integer value")
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Add new Query Instance to stack
|
||||||
|
add = function (self, QueryInstance)
|
||||||
|
table.insert(self._stack, QueryInstance)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Get count of values in stack
|
||||||
|
count = function (self)
|
||||||
|
return #self._stack
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Remove from database all elements from stack
|
||||||
|
delete = function (self)
|
||||||
|
for _, query in pairs(self._stack) do
|
||||||
|
query:delete()
|
||||||
|
end
|
||||||
|
|
||||||
|
self._stack = {}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(_query_list, {__index = _query_list.__index,
|
||||||
|
__len = _query_list.__len,
|
||||||
|
__call = _query_list.__call})
|
||||||
|
|
||||||
|
for _, row in pairs(rows) do
|
||||||
|
current_query = _query_list:with_id(Type.to.number(row.id))
|
||||||
|
|
||||||
|
if current_query then
|
||||||
|
for key, value in pairs(row) do
|
||||||
|
if Type.is.table(value)
|
||||||
|
and current_query._readonly[key .. "_all"] then
|
||||||
|
current_query._readonly[key .. "_all"]:add(
|
||||||
|
Query(_G.All_Tables[key], value)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
_query_list:add(Query(own_table, row))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return _query_list
|
||||||
|
end
|
||||||
|
|
||||||
|
return QueryList
|
||||||
610
src/share/orm/class/select.lua
Normal file
610
src/share/orm/class/select.lua
Normal file
|
|
@ -0,0 +1,610 @@
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Constants --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- For WHERE equations ends
|
||||||
|
local LESS_THEN = "__lt"
|
||||||
|
local EQ_OR_LESS_THEN = "__lte"
|
||||||
|
local MORE_THEN = "__gt"
|
||||||
|
local EQ_OR_MORE_THEN = "__gte"
|
||||||
|
local IN = "__in"
|
||||||
|
local NOT_IN = "__notin"
|
||||||
|
local IS_NULL = '__null'
|
||||||
|
|
||||||
|
-- Joining types
|
||||||
|
local JOIN = {
|
||||||
|
INNER = 'i',
|
||||||
|
LEFT = 'l',
|
||||||
|
RIGHT = 'r',
|
||||||
|
FULL = 'f'
|
||||||
|
}
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Class --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local Select = function(own_table)
|
||||||
|
return {
|
||||||
|
------------------------------------------------
|
||||||
|
-- Table info varibles --
|
||||||
|
------------------------------------------------
|
||||||
|
-- Link for table instance
|
||||||
|
own_table = own_table,
|
||||||
|
|
||||||
|
-- Create select rules
|
||||||
|
_rules = {
|
||||||
|
-- Where equation rules
|
||||||
|
where = {},
|
||||||
|
-- Having equation rules
|
||||||
|
having = {},
|
||||||
|
-- limit
|
||||||
|
limit = nil,
|
||||||
|
-- offset
|
||||||
|
offset = nil,
|
||||||
|
-- order columns list
|
||||||
|
order = {},
|
||||||
|
-- group columns list
|
||||||
|
group = {},
|
||||||
|
--Columns rules
|
||||||
|
columns = {
|
||||||
|
-- Joining tables rules
|
||||||
|
join = {},
|
||||||
|
-- including columns list
|
||||||
|
include = {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
-- Private methods --
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
-- Build correctly equation for SQL searching
|
||||||
|
_build_equation = function (self, colname, value)
|
||||||
|
local result = ""
|
||||||
|
local table_column
|
||||||
|
local rule
|
||||||
|
local _in
|
||||||
|
|
||||||
|
-- Special conditions that need no value escaping
|
||||||
|
if colname:endswith(IS_NULL) then
|
||||||
|
colname = string.cutend(colname, IS_NULL)
|
||||||
|
|
||||||
|
if value then
|
||||||
|
result = " IS NULL"
|
||||||
|
else
|
||||||
|
result = " NOT NULL"
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif colname:endswith(IN) or colname:endswith(NOT_IN) then
|
||||||
|
rule = colname:endswith(IN) and IN or NOT_IN
|
||||||
|
|
||||||
|
if type(value) == "table" and #value > 0 then
|
||||||
|
colname = string.cutend(colname, rule)
|
||||||
|
table_column = self.own_table:get_column(colname)
|
||||||
|
_in = {}
|
||||||
|
|
||||||
|
for counter, val in pairs(value) do
|
||||||
|
table.insert(_in, table_column.field.as(val))
|
||||||
|
end
|
||||||
|
|
||||||
|
if rule == IN then
|
||||||
|
result = " IN (" .. table.join(_in) .. ")"
|
||||||
|
elseif rule == NOT_IN then
|
||||||
|
result = " NOT IN (" .. table.join(_in) .. ")"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
-- Conditions that need value escaping when it's enabled
|
||||||
|
local conditionPrepend = ""
|
||||||
|
|
||||||
|
if colname:endswith(LESS_THEN) and Type.is.number(value) then
|
||||||
|
colname = string.cutend(colname, LESS_THEN)
|
||||||
|
conditionPrepend = " < "
|
||||||
|
|
||||||
|
elseif colname:endswith(MORE_THEN) and Type.is.number(value) then
|
||||||
|
colname = string.cutend(colname, MORE_THEN)
|
||||||
|
conditionPrepend = " > "
|
||||||
|
|
||||||
|
elseif colname:endswith(EQ_OR_LESS_THEN) and Type.is.number(value) then
|
||||||
|
colname = string.cutend(colname, EQ_OR_LESS_THEN)
|
||||||
|
conditionPrepend = " <= "
|
||||||
|
|
||||||
|
elseif colname:endswith(EQ_OR_MORE_THEN) and Type.is.number(value) then
|
||||||
|
colname = string.cutend(colname, EQ_OR_MORE_THEN)
|
||||||
|
conditionPrepend = " >= "
|
||||||
|
|
||||||
|
else
|
||||||
|
conditionPrepend = " = "
|
||||||
|
end
|
||||||
|
|
||||||
|
value = _G.escapeValue(self.own_table, colname, value)
|
||||||
|
table_column = self.own_table:get_column(colname)
|
||||||
|
result = conditionPrepend .. table_column.field.as(value)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.own_table:has_column(colname) then
|
||||||
|
local parse_column, _ = self.own_table:column(colname)
|
||||||
|
result = parse_column .. result
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Need for ASC and DESC columns
|
||||||
|
_update_col_names = function (self, list_of_cols)
|
||||||
|
local tablename = self.own_table.__tablename__
|
||||||
|
local result = {}
|
||||||
|
local parsed_column
|
||||||
|
|
||||||
|
for _, col in pairs(list_of_cols) do
|
||||||
|
if Type.is.table(col) and col.__classtype__ == AGGREGATOR then
|
||||||
|
col.__table__ = self.own_table.__tablename__
|
||||||
|
table.insert(result, col)
|
||||||
|
|
||||||
|
else
|
||||||
|
parsed_column, _ = self.own_table:column(col)
|
||||||
|
table.insert(result, parsed_column)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Build condition for equation rules
|
||||||
|
---------------------------------------------------
|
||||||
|
-- @rules {table} list of columns
|
||||||
|
-- @start_with {string} WHERE or HAVING
|
||||||
|
--
|
||||||
|
-- @retrun {string} parsed string for select equation
|
||||||
|
---------------------------------------------------
|
||||||
|
_condition = function (self, rules, start_with)
|
||||||
|
local counter = 0
|
||||||
|
local condition = ""
|
||||||
|
local _equation
|
||||||
|
|
||||||
|
condition = condition .. start_with
|
||||||
|
|
||||||
|
-- TODO: add OR
|
||||||
|
for colname, value in pairs(rules) do
|
||||||
|
_equation = self:_build_equation(colname, value)
|
||||||
|
|
||||||
|
if counter ~= 0 then
|
||||||
|
_equation = "AND " .. _equation
|
||||||
|
end
|
||||||
|
|
||||||
|
condition = condition .. " " .. _equation
|
||||||
|
counter = counter + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return condition
|
||||||
|
end,
|
||||||
|
|
||||||
|
_has_foreign_key_table = function (self, left_table, right_table)
|
||||||
|
for _, key in pairs(left_table.__foreign_keys) do
|
||||||
|
if key.settings.to == right_table then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Build join tables rules
|
||||||
|
_build_join = function (self)
|
||||||
|
local result_join = ""
|
||||||
|
local unique_tables = {}
|
||||||
|
local left_table, right_table, mode
|
||||||
|
local join_mode, colname
|
||||||
|
local parsed_column, _
|
||||||
|
local tablename
|
||||||
|
|
||||||
|
for _, value in pairs(self._rules.columns.join) do
|
||||||
|
left_table = value[1]
|
||||||
|
right_table = value[2]
|
||||||
|
mode = value[3]
|
||||||
|
tablename = left_table.__tablename__
|
||||||
|
|
||||||
|
if mode == JOIN.INNER then
|
||||||
|
join_mode = "INNER JOIN"
|
||||||
|
|
||||||
|
elseif mode == JOIN.LEFT then
|
||||||
|
join_mode = "LEFT OUTER JOIN"
|
||||||
|
|
||||||
|
elseif mode == JOIN.RIGHT then
|
||||||
|
join_mode = "RIGHT OUTER JOIN"
|
||||||
|
|
||||||
|
elseif mode == JOIN.FULL then
|
||||||
|
join_mode = "FULL OUTER JOIN"
|
||||||
|
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "Not valid join mode " .. mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
if self:_has_foreign_key_table(right_table, left_table) then
|
||||||
|
left_table, right_table = right_table, left_table
|
||||||
|
tablename = right_table.__tablename__
|
||||||
|
|
||||||
|
elseif not self:_has_foreign_key_table(right_table, left_table) then
|
||||||
|
BACKTRACE(WARNING, "Not valid tables links")
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, key in pairs(left_table.__foreign_keys) do
|
||||||
|
if key.settings.to == right_table then
|
||||||
|
colname = key.name
|
||||||
|
|
||||||
|
result_join = result_join .. " \n" .. join_mode .. " `" ..
|
||||||
|
tablename .. "` ON "
|
||||||
|
|
||||||
|
parsed_column, _ = left_table:column(colname)
|
||||||
|
result_join = result_join .. parsed_column
|
||||||
|
|
||||||
|
parsed_column, _ = right_table:column(ID)
|
||||||
|
result_join = result_join .. " = " .. parsed_column
|
||||||
|
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result_join
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- String with including data in select
|
||||||
|
--------------------------------------------
|
||||||
|
-- @own_table {table|nil} Table instance
|
||||||
|
--
|
||||||
|
-- @return {string} comma separated fields
|
||||||
|
--------------------------------------------
|
||||||
|
_build_including = function (self, own_table)
|
||||||
|
local include = {}
|
||||||
|
local colname_as, colname
|
||||||
|
|
||||||
|
if not own_table then
|
||||||
|
own_table = self.own_table
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get current column
|
||||||
|
for _, column in pairs(own_table.__colnames) do
|
||||||
|
colname, colname_as = own_table:column(column.name)
|
||||||
|
table.insert(include, colname .. " AS " .. colname_as)
|
||||||
|
end
|
||||||
|
|
||||||
|
include = table.join(include)
|
||||||
|
|
||||||
|
return include
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Method for build select with rules
|
||||||
|
_select = function (self)
|
||||||
|
local including = self:_build_including()
|
||||||
|
local joining = ""
|
||||||
|
local _select
|
||||||
|
local tablename
|
||||||
|
local condition
|
||||||
|
local where
|
||||||
|
local rule
|
||||||
|
local join
|
||||||
|
|
||||||
|
--------------------- Include Columns To Select ------------------
|
||||||
|
_select = "SELECT " .. including
|
||||||
|
|
||||||
|
-- Add join rules
|
||||||
|
if #self._rules.columns.join > 0 then
|
||||||
|
local unique_tables = { self.own_table }
|
||||||
|
local join_tables = {}
|
||||||
|
local left_table, right_table
|
||||||
|
|
||||||
|
for _, values in pairs(self._rules.columns.join) do
|
||||||
|
left_table = values[1]
|
||||||
|
right_table = values[2]
|
||||||
|
|
||||||
|
if not table.has_value(unique_tables, left_table) then
|
||||||
|
table.insert(unique_tables, left_table)
|
||||||
|
_select = _select .. ", " .. self:_build_including(left_table)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not table.has_value(unique_tables, right_table) then
|
||||||
|
table.insert(unique_tables, right_table)
|
||||||
|
_select = _select .. ", " .. self:_build_including(right_table)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
join = self:_build_join()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check aggregators in select
|
||||||
|
if #self._rules.columns.include > 0 then
|
||||||
|
local aggregators = {}
|
||||||
|
local aggregator, as
|
||||||
|
|
||||||
|
for _, value in pairs(self._rules.columns.include) do
|
||||||
|
_, as = own_table:column(value.as)
|
||||||
|
table.insert(aggregators, value[1] .. " AS " .. as)
|
||||||
|
end
|
||||||
|
|
||||||
|
_select = _select .. ", " .. table.join(aggregators)
|
||||||
|
end
|
||||||
|
------------------- End Include Columns To Select ----------------
|
||||||
|
|
||||||
|
_select = _select .. " FROM `" .. self.own_table.__tablename__ .. "`"
|
||||||
|
|
||||||
|
if join then
|
||||||
|
_select = _select .. " " .. join
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build WHERE
|
||||||
|
if next(self._rules.where) then
|
||||||
|
condition = self:_condition(self._rules.where, "\nWHERE")
|
||||||
|
_select = _select .. " " .. condition
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build GROUP BY
|
||||||
|
if #self._rules.group > 0 then
|
||||||
|
rule = self:_update_col_names(self._rules.group)
|
||||||
|
rule = table.join(rule)
|
||||||
|
_select = _select .. " \nGROUP BY " .. rule
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build HAVING
|
||||||
|
if next(self._rules.having) and self._rules.group then
|
||||||
|
condition = self:_condition(self._rules.having, "\nHAVING")
|
||||||
|
_select = _select .. " " .. condition
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build ORDER BY
|
||||||
|
if #self._rules.order > 0 then
|
||||||
|
rule = self:_update_col_names(self._rules.order)
|
||||||
|
rule = table.join(rule)
|
||||||
|
_select = _select .. " \nORDER BY " .. rule
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build LIMIT
|
||||||
|
if self._rules.limit then
|
||||||
|
_select = _select .. " \nLIMIT " .. self._rules.limit
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build OFFSET
|
||||||
|
if self._rules.offset then
|
||||||
|
_select = _select .. " \nOFFSET " .. self._rules.offset
|
||||||
|
end
|
||||||
|
|
||||||
|
return db:rows(_select, self.own_table)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Add column to table
|
||||||
|
-------------------------------------------------
|
||||||
|
-- @col_table {table} table with column names
|
||||||
|
-- @colname {string/table} column name or list of column names
|
||||||
|
-------------------------------------------------
|
||||||
|
_add_col_to_table = function (self, col_table, colname)
|
||||||
|
if Type.is.str(colname) and self.own_table:has_column(colname) then
|
||||||
|
table.insert(col_table, colname)
|
||||||
|
|
||||||
|
elseif Type.is.table(colname) then
|
||||||
|
for _, column in pairs(colname) do
|
||||||
|
if (Type.is.table(column) and column.__classtype__ == AGGREGATOR
|
||||||
|
and self.own_table:has_column(column.colname))
|
||||||
|
or self.own_table:has_column(column) then
|
||||||
|
table.insert(col_table, column)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "Not a string and not a table (" ..
|
||||||
|
tostring(colname) .. ")")
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
--------------------------------------------------------
|
||||||
|
-- Column filters --
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
-- Including columns to select query
|
||||||
|
include = function (self, column_list)
|
||||||
|
if Type.is.table(column_list) then
|
||||||
|
for _, value in pairs(column_list) do
|
||||||
|
if Type.is.table(value) and value.as and value[1]
|
||||||
|
and value[1].__classtype__ == AGGREGATOR then
|
||||||
|
table.insert(self._rules.columns.include, value)
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "Not valid aggregator syntax")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "You can include only table type data")
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
--------------------------------------------------------
|
||||||
|
-- Joining tables methods --
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
-- By default, join is INNER JOIN command
|
||||||
|
_join = function (self, left_table, MODE, right_table)
|
||||||
|
if not right_table then
|
||||||
|
right_table = self.own_table
|
||||||
|
end
|
||||||
|
|
||||||
|
if left_table.__tablename__ then
|
||||||
|
table.insert(self._rules.columns.join,
|
||||||
|
{left_table, right_table, MODE})
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "Not table in join")
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
join = function (self, left_table, right_table)
|
||||||
|
self:_join(left_table, JOIN.INNER, right_table)
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- left outer joining command
|
||||||
|
left_join = function (self, left_table, right_table)
|
||||||
|
self:_join(left_table, JOIN.LEFT, right_table)
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- right outer joining command
|
||||||
|
right_join = function (self, left_table, right_table)
|
||||||
|
self:_join(left_table, JOIN.RIGHT, right_table)
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- full outer joining command
|
||||||
|
full_join = function (self, left_table, right_table)
|
||||||
|
self:_join(left_table, JOIN.FULL, right_table)
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
--------------------------------------------------------
|
||||||
|
-- Select building methods --
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
-- SQL Where query rules
|
||||||
|
where = function (self, args)
|
||||||
|
for col, value in pairs(args) do
|
||||||
|
self._rules.where[col] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Set returned data limit
|
||||||
|
limit = function (self, count)
|
||||||
|
if Type.is.int(count) then
|
||||||
|
self._rules.limit = count
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "You try set limit to not integer value")
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- From which position start get data
|
||||||
|
offset = function (self, count)
|
||||||
|
if Type.is.int(count) then
|
||||||
|
self._rules.offset = count
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "You try set offset to not integer value")
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Order table
|
||||||
|
order_by = function (self, colname)
|
||||||
|
self:_add_col_to_table(self._rules.order, colname)
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Group table
|
||||||
|
group_by = function (self, colname)
|
||||||
|
self:_add_col_to_table(self._rules.group, colname)
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Having
|
||||||
|
having = function (self, args)
|
||||||
|
for col, value in pairs(args) do
|
||||||
|
self._rules.having[col] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end,
|
||||||
|
|
||||||
|
--------------------------------------------------------
|
||||||
|
-- Update data methods --
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
update = function (self, data)
|
||||||
|
if Type.is.table(data) then
|
||||||
|
local _update = "UPDATE `" .. self.own_table.__tablename__ .. "`"
|
||||||
|
local _set = ""
|
||||||
|
local coltype
|
||||||
|
local _set_tbl = {}
|
||||||
|
local i=1
|
||||||
|
|
||||||
|
for colname, new_value in pairs(data) do
|
||||||
|
coltype = self.own_table:get_column(colname)
|
||||||
|
|
||||||
|
if coltype and coltype.field.validator(new_value) then
|
||||||
|
_set = _set .. " `" .. colname .. "` = " ..
|
||||||
|
coltype.field.as(new_value)
|
||||||
|
_set_tbl[i] = " `" .. colname .. "` = " ..
|
||||||
|
coltype.field.as(new_value)
|
||||||
|
i=i+1
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "Can't update value for column `" ..
|
||||||
|
Type.to.str(colname) .. "`")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build WHERE
|
||||||
|
if next(self._rules.where) then
|
||||||
|
_where = self:_condition(self._rules.where, "\nWHERE")
|
||||||
|
else
|
||||||
|
BACKTRACE(INFO, "No 'where' statement. All data update!")
|
||||||
|
end
|
||||||
|
|
||||||
|
if _set ~= "" then
|
||||||
|
if #_set_tbl<2 then
|
||||||
|
_update = _update .. " SET " .. _set .. " " .. _where
|
||||||
|
else
|
||||||
|
_update = _update .. " SET " .. table.concat(_set_tbl,",") .. " " .. _where
|
||||||
|
end
|
||||||
|
|
||||||
|
db:execute(_update)
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "No table columns for update")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "No data for global update")
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
--------------------------------------------------------
|
||||||
|
-- Delete data methods --
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
delete = function (self)
|
||||||
|
local _delete = "DELETE FROM `" .. self.own_table.__tablename__ .. "` "
|
||||||
|
|
||||||
|
-- Build WHERE
|
||||||
|
if next(self._rules.where) then
|
||||||
|
_delete = _delete .. self:_condition(self._rules.where, "\nWHERE")
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "Try delete all values")
|
||||||
|
end
|
||||||
|
|
||||||
|
db:execute(_delete)
|
||||||
|
end,
|
||||||
|
|
||||||
|
--------------------------------------------------------
|
||||||
|
-- Get select data methods --
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
-- Return one value
|
||||||
|
first = function (self)
|
||||||
|
self._rules.limit = 1
|
||||||
|
local data = self:all()
|
||||||
|
|
||||||
|
if data:count() == 1 then
|
||||||
|
return data[1]
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Return list of values
|
||||||
|
all = function (self)
|
||||||
|
local data = self:_select()
|
||||||
|
return QueryList(self.own_table, data)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return Select
|
||||||
236
src/share/orm/class/table.lua
Normal file
236
src/share/orm/class/table.lua
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Required classes --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local Select = require('orm.class.select')
|
||||||
|
local Query, QueryList = require('orm.class.query')
|
||||||
|
local fields = require('orm.tools.fields')
|
||||||
|
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Table --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Table = {
|
||||||
|
-- database table name
|
||||||
|
__tablename__ = nil,
|
||||||
|
|
||||||
|
-- Foreign Keys list
|
||||||
|
foreign_keys = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
-- This method create new table
|
||||||
|
-------------------------------------------
|
||||||
|
-- @table_instance {table} class Table instance
|
||||||
|
--
|
||||||
|
-- @table_instance.__tablename__ {string} table name
|
||||||
|
-- @table_instance.__colnames {table} list of column instances
|
||||||
|
-- @table_instance.__foreign_keys {table} list of foreign key
|
||||||
|
-- column instances
|
||||||
|
-------------------------------------------
|
||||||
|
function Table:create_table(table_instance)
|
||||||
|
-- table information
|
||||||
|
local tablename = table_instance.__tablename__
|
||||||
|
local columns = table_instance.__colnames
|
||||||
|
local foreign_keys = table_instance.__foreign_keys
|
||||||
|
|
||||||
|
BACKTRACE(INFO, "Start create table: " .. tablename)
|
||||||
|
|
||||||
|
-- other variables
|
||||||
|
local create_query = "CREATE TABLE IF NOT EXISTS `" .. tablename .. "` \n("
|
||||||
|
local counter = 0
|
||||||
|
local column_query
|
||||||
|
local result
|
||||||
|
|
||||||
|
for _, coltype in pairs(columns) do
|
||||||
|
column_query = "\n `" .. coltype.name .. "` " .. coltype:_create_type()
|
||||||
|
|
||||||
|
if counter ~= 0 then
|
||||||
|
column_query = "," .. column_query
|
||||||
|
end
|
||||||
|
|
||||||
|
create_query = create_query .. column_query
|
||||||
|
counter = counter + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, coltype in pairs(foreign_keys) do
|
||||||
|
create_query = create_query .. ",\n FOREIGN KEY(`" ..
|
||||||
|
coltype.name .. "`)" .. " REFERENCES `" ..
|
||||||
|
coltype.settings.to.__tablename__ ..
|
||||||
|
"`(`id`)"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_query = create_query .. "\n)"
|
||||||
|
|
||||||
|
db:execute(create_query)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create new table instance
|
||||||
|
--------------------------------------
|
||||||
|
-- @args {table} must have __tablename__ key
|
||||||
|
-- and other must be a column names
|
||||||
|
--------------------------------------
|
||||||
|
function Table.new(self, args)
|
||||||
|
local colnames = {}
|
||||||
|
local create_query
|
||||||
|
|
||||||
|
self.__tablename__ = args.__tablename__
|
||||||
|
args.__tablename__ = nil
|
||||||
|
|
||||||
|
-- Determine the column creation order
|
||||||
|
-- This is necessary because tables in lua have no order
|
||||||
|
self.__columnCreateOrder__ = { "id" };
|
||||||
|
|
||||||
|
local customColumnCreateOrder = args.__columnCreateOrder__;
|
||||||
|
args.__columnCreateOrder__ = nil;
|
||||||
|
|
||||||
|
if (customColumnCreateOrder) then
|
||||||
|
for _, colname in ipairs(customColumnCreateOrder) do
|
||||||
|
|
||||||
|
-- Add only existing columns to the column create order
|
||||||
|
if (args[colname]) then
|
||||||
|
table.insert(self.__columnCreateOrder__, colname);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for colname, coltype in pairs(args) do
|
||||||
|
|
||||||
|
-- Add the columns that are defined but missing from the column create order
|
||||||
|
if (not table.has_value(self.__columnCreateOrder__, colname)) then
|
||||||
|
table.insert(self.__columnCreateOrder__, colname);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local Table_instance = {
|
||||||
|
------------------------------------------------
|
||||||
|
-- Table info variables --
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
-- SQL table name
|
||||||
|
__tablename__ = self.__tablename__,
|
||||||
|
|
||||||
|
-- list of column names
|
||||||
|
__colnames = {},
|
||||||
|
|
||||||
|
-- Foreign keys list
|
||||||
|
__foreign_keys = {},
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
-- Metamethods --
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
-- If try get value by name "get" it return Select class instance
|
||||||
|
__index = function (self, key)
|
||||||
|
if key == "get" then
|
||||||
|
return Select(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
local old_index = self.__index
|
||||||
|
setmetatable(self, {__index = nil})
|
||||||
|
|
||||||
|
key = self[key]
|
||||||
|
|
||||||
|
setmetatable(self, {__index = old_index, __call = self.create})
|
||||||
|
|
||||||
|
return key
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Create new row instance
|
||||||
|
-----------------------------------------
|
||||||
|
-- @data {table} parsed query answer data
|
||||||
|
--
|
||||||
|
-- @retrun {table} Query instance
|
||||||
|
-----------------------------------------
|
||||||
|
create = function (self, data)
|
||||||
|
return Query(self, data)
|
||||||
|
end,
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
-- Methods which using --
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
-- parse column in correct types
|
||||||
|
column = function (self, column)
|
||||||
|
local tablename = self.__tablename__
|
||||||
|
|
||||||
|
if Type.is.table(column) and column.__classtype__ == AGGREGATOR then
|
||||||
|
column.colname = tablename .. column.colname
|
||||||
|
column = column .. ""
|
||||||
|
end
|
||||||
|
|
||||||
|
return "`" .. tablename .. "`.`" .. column .. "`",
|
||||||
|
tablename .. "_" .. column
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Check column in table
|
||||||
|
-----------------------------------------
|
||||||
|
-- @colname {string} column name
|
||||||
|
--
|
||||||
|
-- @return {boolean} get true if column exist
|
||||||
|
-----------------------------------------
|
||||||
|
has_column = function (self, colname)
|
||||||
|
for _, table_column in pairs(self.__colnames) do
|
||||||
|
if table_column.name == colname then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
BACKTRACE(WARNING, "Can't find column '" .. tostring(colname) ..
|
||||||
|
"' in table '" .. self.__tablename__ .. "'")
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- get column instance by name
|
||||||
|
-----------------------------------------
|
||||||
|
-- @colname {string} column name
|
||||||
|
--
|
||||||
|
-- @return {table} get column instance if column exist
|
||||||
|
-----------------------------------------
|
||||||
|
get_column = function (self, colname)
|
||||||
|
for _, table_column in pairs(self.__colnames) do
|
||||||
|
if table_column.name == colname then
|
||||||
|
return table_column
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
BACKTRACE(WARNING, "Can't find column '" .. tostring(column) ..
|
||||||
|
"' in table '" .. self.__tablename__ .. "'")
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Add default column 'id'
|
||||||
|
args.id = fields.PrimaryField({auto_increment = true})
|
||||||
|
|
||||||
|
-- copy column arguments to new table instance
|
||||||
|
for _, colname in ipairs(self.__columnCreateOrder__) do
|
||||||
|
|
||||||
|
local coltype = args[colname];
|
||||||
|
coltype.name = colname
|
||||||
|
coltype.__table__ = Table_instance
|
||||||
|
|
||||||
|
table.insert(Table_instance.__colnames, coltype)
|
||||||
|
|
||||||
|
if coltype.settings.foreign_key then
|
||||||
|
table.insert(Table_instance.__foreign_keys, coltype)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(Table_instance, {
|
||||||
|
__call = Table_instance.create,
|
||||||
|
__index = Table_instance.__index
|
||||||
|
})
|
||||||
|
|
||||||
|
_G.All_Tables[self.__tablename__] = Table_instance
|
||||||
|
|
||||||
|
-- Create new table if needed
|
||||||
|
if DB.new then
|
||||||
|
self:create_table(Table_instance)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Table_instance
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(Table, {__call = Table.new})
|
||||||
|
|
||||||
|
return Table
|
||||||
44
src/share/orm/class/type.lua
Normal file
44
src/share/orm/class/type.lua
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- type.lua --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local Type = {
|
||||||
|
-- Check value for correct type
|
||||||
|
----------------------------------
|
||||||
|
-- @value {any type} checked value
|
||||||
|
--
|
||||||
|
-- @return {boolean} get true if type is correct
|
||||||
|
----------------------------------
|
||||||
|
is = {
|
||||||
|
int = function (value)
|
||||||
|
if type(value) == "number" then
|
||||||
|
integer, fractional = math.modf(value)
|
||||||
|
return fractional == 0
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
number = function (value)
|
||||||
|
return type(value) == "number"
|
||||||
|
end,
|
||||||
|
|
||||||
|
str = function (value)
|
||||||
|
return type(value) == "string"
|
||||||
|
end,
|
||||||
|
|
||||||
|
table = function (value)
|
||||||
|
return type(value) == "table"
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
to = {
|
||||||
|
number = function (value)
|
||||||
|
return tonumber(value)
|
||||||
|
end,
|
||||||
|
|
||||||
|
str = function (value)
|
||||||
|
return tostring(value)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Type
|
||||||
154
src/share/orm/model.lua
Normal file
154
src/share/orm/model.lua
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Require --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
require('orm.class.global')
|
||||||
|
require("orm.tools.func")
|
||||||
|
|
||||||
|
local Table = require('orm.class.table')
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Constants --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Global
|
||||||
|
ID = "id"
|
||||||
|
AGGREGATOR = "aggregator"
|
||||||
|
QUERY_LIST = "query_list"
|
||||||
|
|
||||||
|
-- databases types
|
||||||
|
SQLITE = "sqlite3"
|
||||||
|
ORACLE = "oracle"
|
||||||
|
MYSQL = "mysql"
|
||||||
|
POSTGRESQL = "postgresql"
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Model Settings --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if not DB then
|
||||||
|
print("[SQL:Startup] Can't find global database settings variable 'DB'. Creating empty one.")
|
||||||
|
DB = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
DB = {
|
||||||
|
-- ORM settings
|
||||||
|
new = (DB.new == true),
|
||||||
|
DEBUG = (DB.DEBUG == true),
|
||||||
|
backtrace = (DB.backtrace == true),
|
||||||
|
-- database settings
|
||||||
|
type = DB.type or "sqlite3",
|
||||||
|
-- if you use sqlite set database path value
|
||||||
|
-- if not set a database name
|
||||||
|
name = DB.name or "database.db",
|
||||||
|
-- not sqlite db settings
|
||||||
|
host = DB.host or nil,
|
||||||
|
port = DB.port or nil,
|
||||||
|
username = DB.username or nil,
|
||||||
|
password = DB.password or nil
|
||||||
|
}
|
||||||
|
|
||||||
|
local sql, _connect
|
||||||
|
|
||||||
|
-- Get database by settings
|
||||||
|
if DB.type == SQLITE then
|
||||||
|
local luasql = require("luasql.sqlite3")
|
||||||
|
sql = luasql.sqlite3()
|
||||||
|
_connect = sql:connect(DB.name)
|
||||||
|
|
||||||
|
elseif DB.type == MYSQL then
|
||||||
|
local luasql = require("luasql.mysql")
|
||||||
|
sql = luasql.mysql()
|
||||||
|
print(DB.name, DB.username, DB.password, DB.host, DB.port)
|
||||||
|
_connect = sql:connect(DB.name, DB.username, DB.password, DB.host, DB.port)
|
||||||
|
|
||||||
|
elseif DB.type == POSTGRESQL then
|
||||||
|
local luasql = require("luasql.postgres")
|
||||||
|
sql = luasql.postgres()
|
||||||
|
print(DB.name, DB.username, DB.password, DB.host, DB.port)
|
||||||
|
_connect = sql:connect(DB.name, DB.username, DB.password, DB.host, DB.port)
|
||||||
|
|
||||||
|
else
|
||||||
|
BACKTRACE(ERROR, "Database type not suported '" .. tostring(DB.type) .. "'")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not _connect then
|
||||||
|
BACKTRACE(ERROR, "Connect problem!")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if DB.new then
|
||||||
|
-- BACKTRACE(INFO, "Remove old database")
|
||||||
|
|
||||||
|
-- if DB.type == SQLITE then
|
||||||
|
-- os.remove(DB.name)
|
||||||
|
-- else
|
||||||
|
-- _connect:execute('DROP DATABASE `' .. DB.name .. '`')
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Database --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- Database settings
|
||||||
|
db = {
|
||||||
|
-- Database connect instance
|
||||||
|
connect = _connect,
|
||||||
|
|
||||||
|
-- Execute SQL query
|
||||||
|
execute = function (self, query)
|
||||||
|
BACKTRACE(DEBUG, query)
|
||||||
|
|
||||||
|
local result = self.connect:execute(query)
|
||||||
|
|
||||||
|
if result then
|
||||||
|
return result
|
||||||
|
else
|
||||||
|
BACKTRACE(WARNING, "Wrong SQL query")
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Return insert query id
|
||||||
|
insert = function (self, query)
|
||||||
|
local _cursor = self:execute(query)
|
||||||
|
return 1
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- get parced data
|
||||||
|
rows = function (self, query, own_table)
|
||||||
|
local _cursor = self:execute(query)
|
||||||
|
local data = {}
|
||||||
|
local current_row = {}
|
||||||
|
local current_table
|
||||||
|
local row
|
||||||
|
|
||||||
|
if _cursor then
|
||||||
|
row = _cursor:fetch({}, "a")
|
||||||
|
|
||||||
|
while row do
|
||||||
|
for colname, value in pairs(row) do
|
||||||
|
current_table, colname = string.divided_into(colname, "_")
|
||||||
|
|
||||||
|
if current_table == own_table.__tablename__ then
|
||||||
|
current_row[colname] = value
|
||||||
|
else
|
||||||
|
if not current_row[current_table] then
|
||||||
|
current_row[current_table] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
current_row[current_table][colname] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(data, current_row)
|
||||||
|
|
||||||
|
current_row = {}
|
||||||
|
row = _cursor:fetch({}, "a")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
return Table
|
||||||
83
src/share/orm/tools/fields.lua
Normal file
83
src/share/orm/tools/fields.lua
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Libs --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Type = require('orm.class.type')
|
||||||
|
Field = require('orm.class.fields')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- Field Types --
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
local function save_as_str(str)
|
||||||
|
return "'" .. str .. "'"
|
||||||
|
end
|
||||||
|
|
||||||
|
local field = {}
|
||||||
|
|
||||||
|
-- The "Field" class will be used to search a table index that the "field" class doesn't have.
|
||||||
|
-- This way field:register() will call the same function like Field:register() and the register
|
||||||
|
-- function has access to the default values for the field configuration.
|
||||||
|
setmetatable(field, {__index = Field});
|
||||||
|
|
||||||
|
|
||||||
|
field.PrimaryField = Field:register({
|
||||||
|
__type__ = "integer",
|
||||||
|
validator = Type.is.int,
|
||||||
|
settings = {
|
||||||
|
null = true,
|
||||||
|
primary_key = true,
|
||||||
|
auto_increment = true
|
||||||
|
},
|
||||||
|
to_type = Type.to.number
|
||||||
|
})
|
||||||
|
|
||||||
|
field.IntegerField = Field:register({
|
||||||
|
__type__ = "integer",
|
||||||
|
validator = Type.is.int,
|
||||||
|
to_type = Type.to.number
|
||||||
|
})
|
||||||
|
|
||||||
|
field.CharField = Field:register({
|
||||||
|
__type__ = "varchar",
|
||||||
|
validator = Type.is.str,
|
||||||
|
as = save_as_str
|
||||||
|
})
|
||||||
|
|
||||||
|
field.TextField = Field:register({
|
||||||
|
__type__ = "text",
|
||||||
|
validator = Type.is.str,
|
||||||
|
as = save_as_str
|
||||||
|
})
|
||||||
|
|
||||||
|
field.BooleandField = Field:register({
|
||||||
|
__type__ = "bool"
|
||||||
|
})
|
||||||
|
|
||||||
|
field.DateTimeField = Field:register({
|
||||||
|
__type__ = "integer",
|
||||||
|
validator = function (value)
|
||||||
|
if (Type.is.table(value) and value.isdst ~= nil)
|
||||||
|
or Type.is.int(value) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
as = function (value)
|
||||||
|
return Type.is.int(value) and value or os.time(value)
|
||||||
|
end,
|
||||||
|
to_type = function (value)
|
||||||
|
return os.date("*t", Type.to.number(value))
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
field.ForeignKey = Field:register({
|
||||||
|
__type__ = "integer",
|
||||||
|
settings = {
|
||||||
|
null = true,
|
||||||
|
foreign_key = true
|
||||||
|
},
|
||||||
|
to_type = Type.to.number
|
||||||
|
})
|
||||||
|
|
||||||
|
return field
|
||||||
64
src/share/orm/tools/func.lua
Normal file
64
src/share/orm/tools/func.lua
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
|
||||||
|
local Property = require('orm.class.property')
|
||||||
|
|
||||||
|
_G.asc = Property({
|
||||||
|
parse = function (self)
|
||||||
|
return "`" .. self.__table__ .. "`.`" .. self.colname .. "` ASC"
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
_G.desc = Property({
|
||||||
|
parse = function (self)
|
||||||
|
return "`" .. self.__table__ .. "`.`" .. self.colname .. "` DESC"
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
_G.MAX = Property({
|
||||||
|
parse = function (self)
|
||||||
|
return "MAX(`" .. self.__table__ .. "`.`" .. self.colname .. "`)"
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
_G.MIN = Property({
|
||||||
|
parse = function (self)
|
||||||
|
return "MIN(`" .. self.__table__ .. "`.`" .. self.colname .. "`)"
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
_G.COUNT = Property({
|
||||||
|
parse = function (self)
|
||||||
|
return "COUNT(`" .. self.__table__ .. "`.`" .. self.colname .. "`)"
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
_G.SUM = Property({
|
||||||
|
parse = function (self)
|
||||||
|
return "SUM(" .. self.colname .. ")"
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Escape text values to prevent sql injection
|
||||||
|
function _G.escapeValue(own_table, colname, colvalue)
|
||||||
|
|
||||||
|
local coltype = own_table:get_column(colname)
|
||||||
|
if coltype and coltype.settings.escape_value then
|
||||||
|
|
||||||
|
local fieldtype = coltype.field.__type__
|
||||||
|
if fieldtype:find("text") or fieldtype:find("char") then
|
||||||
|
|
||||||
|
if (DB.type == "sqlite3" or DB.type == "mysql" or DB.type == "postgresql") then
|
||||||
|
|
||||||
|
-- See https://keplerproject.github.io/luasql/manual.html for a list of
|
||||||
|
-- database drivers that support this method
|
||||||
|
colvalue = db.connect:escape(colvalue)
|
||||||
|
elseif (DB.type == "oracle") then
|
||||||
|
BACKTRACE(WARNING, "Can't autoescape values for oracle databases (Tried to escape field `" .. colname .. "`)");
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
return colvalue;
|
||||||
|
|
||||||
|
end
|
||||||
Loading…
Reference in New Issue
Block a user