| rev | 
   line source | 
| 
jbe/bsw@0
 | 
     1 #!/usr/bin/env lua
 | 
| 
jbe/bsw@0
 | 
     2 
 | 
| 
jbe/bsw@0
 | 
     3 
 | 
| 
jbe/bsw@0
 | 
     4 ---------------------------
 | 
| 
jbe/bsw@0
 | 
     5 -- module initialization --
 | 
| 
jbe/bsw@0
 | 
     6 ---------------------------
 | 
| 
jbe/bsw@0
 | 
     7 
 | 
| 
jbe/bsw@0
 | 
     8 local _G              = _G
 | 
| 
jbe/bsw@0
 | 
     9 local _VERSION        = _VERSION
 | 
| 
jbe/bsw@0
 | 
    10 local assert          = assert
 | 
| 
jbe/bsw@0
 | 
    11 local collectgarbage  = collectgarbage
 | 
| 
jbe/bsw@0
 | 
    12 local dofile          = dofile
 | 
| 
jbe/bsw@0
 | 
    13 local error           = error
 | 
| 
jbe/bsw@0
 | 
    14 local getfenv         = getfenv
 | 
| 
jbe/bsw@0
 | 
    15 local getmetatable    = getmetatable
 | 
| 
jbe/bsw@0
 | 
    16 local ipairs          = ipairs
 | 
| 
jbe/bsw@0
 | 
    17 local load            = load
 | 
| 
jbe/bsw@0
 | 
    18 local loadfile        = loadfile
 | 
| 
jbe/bsw@0
 | 
    19 local loadstring      = loadstring
 | 
| 
jbe/bsw@0
 | 
    20 local next            = next
 | 
| 
jbe/bsw@0
 | 
    21 local pairs           = pairs
 | 
| 
jbe/bsw@0
 | 
    22 local pcall           = pcall
 | 
| 
jbe/bsw@0
 | 
    23 local print           = print
 | 
| 
jbe/bsw@0
 | 
    24 local rawequal        = rawequal
 | 
| 
jbe/bsw@0
 | 
    25 local rawget          = rawget
 | 
| 
jbe/bsw@0
 | 
    26 local rawset          = rawset
 | 
| 
jbe/bsw@0
 | 
    27 local select          = select
 | 
| 
jbe/bsw@0
 | 
    28 local setfenv         = setfenv
 | 
| 
jbe/bsw@0
 | 
    29 local setmetatable    = setmetatable
 | 
| 
jbe/bsw@0
 | 
    30 local tonumber        = tonumber
 | 
| 
jbe/bsw@0
 | 
    31 local tostring        = tostring
 | 
| 
jbe/bsw@0
 | 
    32 local type            = type
 | 
| 
jbe/bsw@0
 | 
    33 local unpack          = unpack
 | 
| 
jbe/bsw@0
 | 
    34 local xpcall          = xpcall
 | 
| 
jbe/bsw@0
 | 
    35 
 | 
| 
jbe/bsw@0
 | 
    36 local coroutine       = coroutine
 | 
| 
jbe/bsw@0
 | 
    37 local io              = io
 | 
| 
jbe/bsw@0
 | 
    38 local math            = math
 | 
| 
jbe/bsw@0
 | 
    39 local os              = os
 | 
| 
jbe/bsw@0
 | 
    40 local string          = string
 | 
| 
jbe/bsw@0
 | 
    41 local table           = table
 | 
| 
jbe/bsw@0
 | 
    42 
 | 
| 
jbe/bsw@0
 | 
    43 local add             = table.insert
 | 
| 
jbe/bsw@0
 | 
    44 
 | 
| 
jbe/bsw@0
 | 
    45 _G[...] = require("mondelefant_native")
 | 
| 
jbe/bsw@0
 | 
    46 module(...)
 | 
| 
jbe/bsw@0
 | 
    47 
 | 
| 
jbe/bsw@0
 | 
    48 
 | 
| 
jbe/bsw@0
 | 
    49 
 | 
| 
jbe/bsw@0
 | 
    50 ---------------
 | 
| 
jbe/bsw@0
 | 
    51 -- selectors --
 | 
| 
jbe/bsw@0
 | 
    52 ---------------
 | 
| 
jbe/bsw@0
 | 
    53 
 | 
| 
jbe/bsw@0
 | 
    54 selector_metatable = {}
 | 
| 
jbe/bsw@0
 | 
    55 selector_prototype = {}
 | 
| 
jbe/bsw@0
 | 
    56 selector_metatable.__index = selector_prototype
 | 
| 
jbe/bsw@0
 | 
    57 
 | 
| 
jbe/bsw@0
 | 
    58 local function init_selector(self, db_conn)
 | 
| 
jbe/bsw@0
 | 
    59   self._db_conn = db_conn
 | 
| 
jbe/bsw@0
 | 
    60   self._mode = "list"
 | 
| 
jbe/bsw@0
 | 
    61   self._fields = { sep = ", " }
 | 
| 
jbe/bsw@0
 | 
    62   self._distinct = false
 | 
| 
jbe/bsw@0
 | 
    63   self._distinct_on = {sep = ", ", expression}
 | 
| 
jbe/bsw@0
 | 
    64   self._from = { sep = " " }
 | 
| 
jbe/bsw@4
 | 
    65   self._where = { sep = ") AND (" }
 | 
| 
jbe/bsw@0
 | 
    66   self._group_by = { sep = ", " }
 | 
| 
jbe/bsw@4
 | 
    67   self._having = { sep = ") AND (" }
 | 
| 
jbe/bsw@0
 | 
    68   self._combine = { sep = " " }
 | 
| 
jbe/bsw@0
 | 
    69   self._order_by = { sep = ", " }
 | 
| 
jbe/bsw@0
 | 
    70   self._limit = nil
 | 
| 
jbe/bsw@0
 | 
    71   self._offset = nil
 | 
| 
jbe/bsw@4
 | 
    72   self._read_lock = { sep = ", " }
 | 
| 
jbe/bsw@4
 | 
    73   self._write_lock = { sep = ", " }
 | 
| 
jbe/bsw@0
 | 
    74   self._class = nil
 | 
| 
jbe/bsw@0
 | 
    75   self._attach = nil
 | 
| 
jbe/bsw@0
 | 
    76   return self
 | 
| 
jbe/bsw@0
 | 
    77 end
 | 
| 
jbe/bsw@0
 | 
    78 
 | 
| 
jbe/bsw@0
 | 
    79 function connection_prototype:new_selector()
 | 
| 
jbe/bsw@0
 | 
    80   return init_selector(setmetatable({}, selector_metatable), self)
 | 
| 
jbe/bsw@0
 | 
    81 end
 | 
| 
jbe/bsw@0
 | 
    82 
 | 
| 
jbe/bsw@0
 | 
    83 function selector_prototype:get_db_conn()
 | 
| 
jbe/bsw@0
 | 
    84   return self._db_conn
 | 
| 
jbe/bsw@0
 | 
    85 end
 | 
| 
jbe/bsw@0
 | 
    86 
 | 
| 
jbe/bsw@0
 | 
    87 -- TODO: selector clone?
 | 
| 
jbe/bsw@0
 | 
    88 
 | 
| 
jbe/bsw@0
 | 
    89 function selector_prototype:single_object_mode()
 | 
| 
jbe/bsw@0
 | 
    90   self._mode = "object"
 | 
| 
jbe/bsw@0
 | 
    91   return self
 | 
| 
jbe/bsw@0
 | 
    92 end
 | 
| 
jbe/bsw@0
 | 
    93 
 | 
| 
jbe/bsw@0
 | 
    94 function selector_prototype:optional_object_mode()
 | 
| 
jbe/bsw@0
 | 
    95   self._mode = "opt_object"
 | 
| 
jbe/bsw@0
 | 
    96   return self
 | 
| 
jbe/bsw@0
 | 
    97 end
 | 
| 
jbe/bsw@0
 | 
    98 
 | 
| 
jbe/bsw@0
 | 
    99 function selector_prototype:empty_list_mode()
 | 
| 
jbe/bsw@0
 | 
   100   self._mode = "empty_list"
 | 
| 
jbe/bsw@0
 | 
   101   return self
 | 
| 
jbe/bsw@0
 | 
   102 end
 | 
| 
jbe/bsw@0
 | 
   103 
 | 
| 
jbe/bsw@0
 | 
   104 function selector_prototype:add_distinct_on(expression)
 | 
| 
jbe/bsw@0
 | 
   105   if self._distinct then
 | 
| 
jbe/bsw@0
 | 
   106     error("Can not combine DISTINCT with DISTINCT ON.")
 | 
| 
jbe/bsw@0
 | 
   107   end
 | 
| 
jbe/bsw@0
 | 
   108   add(self._distinct_on, expression)
 | 
| 
jbe/bsw@0
 | 
   109   return self
 | 
| 
jbe/bsw@0
 | 
   110 end
 | 
| 
jbe/bsw@0
 | 
   111 
 | 
| 
jbe/bsw@0
 | 
   112 function selector_prototype:set_distinct()
 | 
| 
jbe/bsw@0
 | 
   113   if #self._distinct_on > 0 then
 | 
| 
jbe/bsw@0
 | 
   114     error("Can not combine DISTINCT with DISTINCT ON.")
 | 
| 
jbe/bsw@0
 | 
   115   end
 | 
| 
jbe/bsw@0
 | 
   116   self._distinct = true
 | 
| 
jbe/bsw@0
 | 
   117   return self
 | 
| 
jbe/bsw@0
 | 
   118 end
 | 
| 
jbe/bsw@0
 | 
   119 
 | 
| 
jbe/bsw@0
 | 
   120 function selector_prototype:add_from(expression, alias, condition)
 | 
| 
jbe/bsw@0
 | 
   121   local first = (#self._from == 0)
 | 
| 
jbe/bsw@0
 | 
   122   if not first then
 | 
| 
jbe/bsw@0
 | 
   123     if condition then
 | 
| 
jbe/bsw@0
 | 
   124       add(self._from, "INNER JOIN")
 | 
| 
jbe/bsw@0
 | 
   125     else
 | 
| 
jbe/bsw@0
 | 
   126       add(self._from, "CROSS JOIN")
 | 
| 
jbe/bsw@0
 | 
   127     end
 | 
| 
jbe/bsw@0
 | 
   128   end
 | 
| 
jbe/bsw@0
 | 
   129   if getmetatable(expression) == selector_metatable then
 | 
| 
jbe/bsw@0
 | 
   130     if alias then
 | 
| 
jbe/bsw@0
 | 
   131       add(self._from, {'($) AS "$"', {expression}, {alias}})
 | 
| 
jbe/bsw@0
 | 
   132     else
 | 
| 
jbe/bsw@0
 | 
   133       add(self._from, {'($) AS "subquery"', {expression}})
 | 
| 
jbe/bsw@0
 | 
   134     end
 | 
| 
jbe/bsw@0
 | 
   135   else
 | 
| 
jbe/bsw@0
 | 
   136     if alias then
 | 
| 
jbe/bsw@0
 | 
   137       add(self._from, {'$ AS "$"', {expression}, {alias}})
 | 
| 
jbe/bsw@0
 | 
   138     else
 | 
| 
jbe/bsw@0
 | 
   139       add(self._from, expression)
 | 
| 
jbe/bsw@0
 | 
   140     end
 | 
| 
jbe/bsw@0
 | 
   141   end
 | 
| 
jbe/bsw@0
 | 
   142   if condition then
 | 
| 
jbe/bsw@0
 | 
   143     if first then
 | 
| 
jbe/bsw@0
 | 
   144       self:condition(condition)
 | 
| 
jbe/bsw@0
 | 
   145     else
 | 
| 
jbe/bsw@0
 | 
   146       add(self._from, "ON")
 | 
| 
jbe/bsw@0
 | 
   147       add(self._from, condition)
 | 
| 
jbe/bsw@0
 | 
   148     end
 | 
| 
jbe/bsw@0
 | 
   149   end
 | 
| 
jbe/bsw@0
 | 
   150   return self
 | 
| 
jbe/bsw@0
 | 
   151 end
 | 
| 
jbe/bsw@0
 | 
   152 
 | 
| 
jbe/bsw@0
 | 
   153 function selector_prototype:add_where(expression)
 | 
| 
jbe/bsw@0
 | 
   154   add(self._where, expression)
 | 
| 
jbe/bsw@0
 | 
   155   return self
 | 
| 
jbe/bsw@0
 | 
   156 end
 | 
| 
jbe/bsw@0
 | 
   157 
 | 
| 
jbe/bsw@0
 | 
   158 function selector_prototype:add_group_by(expression)
 | 
| 
jbe/bsw@0
 | 
   159   add(self._group_by, expression)
 | 
| 
jbe/bsw@0
 | 
   160   return self
 | 
| 
jbe/bsw@0
 | 
   161 end
 | 
| 
jbe/bsw@0
 | 
   162 
 | 
| 
jbe/bsw@0
 | 
   163 function selector_prototype:add_having(expression)
 | 
| 
jbe/bsw@0
 | 
   164   add(self._having, expression)
 | 
| 
jbe/bsw@0
 | 
   165   return self
 | 
| 
jbe/bsw@0
 | 
   166 end
 | 
| 
jbe/bsw@0
 | 
   167 
 | 
| 
jbe/bsw@0
 | 
   168 function selector_prototype:add_combine(expression)
 | 
| 
jbe/bsw@0
 | 
   169   add(self._combine, expression)
 | 
| 
jbe/bsw@0
 | 
   170   return self
 | 
| 
jbe/bsw@0
 | 
   171 end
 | 
| 
jbe/bsw@0
 | 
   172 
 | 
| 
jbe/bsw@0
 | 
   173 function selector_prototype:add_order_by(expression)
 | 
| 
jbe/bsw@0
 | 
   174   add(self._order_by, expression)
 | 
| 
jbe/bsw@0
 | 
   175   return self
 | 
| 
jbe/bsw@0
 | 
   176 end
 | 
| 
jbe/bsw@0
 | 
   177 
 | 
| 
jbe/bsw@0
 | 
   178 function selector_prototype:limit(count)
 | 
| 
jbe/bsw@0
 | 
   179   if type(count) ~= "number" or count % 1 ~= 0 then
 | 
| 
jbe/bsw@0
 | 
   180     error("LIMIT must be an integer.")
 | 
| 
jbe/bsw@0
 | 
   181   end
 | 
| 
jbe/bsw@0
 | 
   182   self._limit = count
 | 
| 
jbe/bsw@0
 | 
   183   return self
 | 
| 
jbe/bsw@0
 | 
   184 end
 | 
| 
jbe/bsw@0
 | 
   185 
 | 
| 
jbe/bsw@0
 | 
   186 function selector_prototype:offset(count)
 | 
| 
jbe/bsw@0
 | 
   187   if type(count) ~= "number" or count % 1 ~= 0 then
 | 
| 
jbe/bsw@0
 | 
   188     error("OFFSET must be an integer.")
 | 
| 
jbe/bsw@0
 | 
   189   end
 | 
| 
jbe/bsw@0
 | 
   190   self._offset = count
 | 
| 
jbe/bsw@0
 | 
   191   return self
 | 
| 
jbe/bsw@0
 | 
   192 end
 | 
| 
jbe/bsw@0
 | 
   193 
 | 
| 
jbe/bsw@4
 | 
   194 function selector_prototype:for_share()
 | 
| 
jbe/bsw@4
 | 
   195   self._read_lock.all = true
 | 
| 
jbe/bsw@4
 | 
   196   return self
 | 
| 
jbe/bsw@4
 | 
   197 end
 | 
| 
jbe/bsw@4
 | 
   198 
 | 
| 
jbe/bsw@4
 | 
   199 function selector_prototype:for_share_of(expression)
 | 
| 
jbe/bsw@4
 | 
   200   add(self._read_lock, expression)
 | 
| 
jbe/bsw@4
 | 
   201   return self
 | 
| 
jbe/bsw@4
 | 
   202 end
 | 
| 
jbe/bsw@4
 | 
   203 
 | 
| 
jbe/bsw@4
 | 
   204 function selector_prototype:for_update()
 | 
| 
jbe/bsw@4
 | 
   205   self._write_lock.all = true
 | 
| 
jbe/bsw@4
 | 
   206   return self
 | 
| 
jbe/bsw@4
 | 
   207 end
 | 
| 
jbe/bsw@4
 | 
   208 
 | 
| 
jbe/bsw@4
 | 
   209 function selector_prototype:for_update_of(expression)
 | 
| 
jbe/bsw@4
 | 
   210   add(self._write_lock, expression)
 | 
| 
jbe/bsw@4
 | 
   211   return self
 | 
| 
jbe/bsw@4
 | 
   212 end
 | 
| 
jbe/bsw@4
 | 
   213 
 | 
| 
jbe/bsw@0
 | 
   214 function selector_prototype:reset_fields()
 | 
| 
jbe/bsw@0
 | 
   215   for idx in ipairs(self._fields) do
 | 
| 
jbe/bsw@0
 | 
   216     self._fields[idx] = nil
 | 
| 
jbe/bsw@0
 | 
   217   end
 | 
| 
jbe/bsw@0
 | 
   218   return self
 | 
| 
jbe/bsw@0
 | 
   219 end
 | 
| 
jbe/bsw@0
 | 
   220 
 | 
| 
jbe/bsw@0
 | 
   221 function selector_prototype:add_field(expression, alias, options)
 | 
| 
jbe/bsw@0
 | 
   222   if alias then
 | 
| 
jbe/bsw@0
 | 
   223     add(self._fields, {'$ AS "$"', {expression}, {alias}})
 | 
| 
jbe/bsw@0
 | 
   224   else
 | 
| 
jbe/bsw@0
 | 
   225     add(self._fields, expression)
 | 
| 
jbe/bsw@0
 | 
   226   end
 | 
| 
jbe/bsw@0
 | 
   227   if options then
 | 
| 
jbe/bsw@0
 | 
   228     for i, option in ipairs(options) do
 | 
| 
jbe/bsw@0
 | 
   229       if option == "distinct" then
 | 
| 
jbe/bsw@0
 | 
   230         if alias then
 | 
| 
jbe/bsw@0
 | 
   231           self:add_distinct_on('"' .. alias .. '"')
 | 
| 
jbe/bsw@0
 | 
   232         else
 | 
| 
jbe/bsw@0
 | 
   233           self:add_distinct_on(expression)
 | 
| 
jbe/bsw@0
 | 
   234         end
 | 
| 
jbe/bsw@0
 | 
   235       elseif option == "grouped" then
 | 
| 
jbe/bsw@0
 | 
   236         if alias then
 | 
| 
jbe/bsw@0
 | 
   237           self:add_group_by('"' .. alias .. '"')
 | 
| 
jbe/bsw@0
 | 
   238         else
 | 
| 
jbe/bsw@0
 | 
   239           self:add_group_by(expression)
 | 
| 
jbe/bsw@0
 | 
   240         end
 | 
| 
jbe/bsw@0
 | 
   241       else
 | 
| 
jbe/bsw@0
 | 
   242         error("Unknown option '" .. option .. "' to add_field method.")
 | 
| 
jbe/bsw@0
 | 
   243       end
 | 
| 
jbe/bsw@0
 | 
   244     end
 | 
| 
jbe/bsw@0
 | 
   245   end
 | 
| 
jbe/bsw@0
 | 
   246   return self
 | 
| 
jbe/bsw@0
 | 
   247 end
 | 
| 
jbe/bsw@0
 | 
   248 
 | 
| 
jbe/bsw@0
 | 
   249 function selector_prototype:join(...)  -- NOTE: alias for add_from
 | 
| 
jbe/bsw@0
 | 
   250   return self:add_from(...)
 | 
| 
jbe/bsw@0
 | 
   251 end
 | 
| 
jbe/bsw@0
 | 
   252 
 | 
| 
jbe/bsw@0
 | 
   253 function selector_prototype:from(expression, alias, condition)
 | 
| 
jbe/bsw@0
 | 
   254   if #self._from > 0 then
 | 
| 
jbe/bsw@0
 | 
   255     error("From-clause already existing (hint: try join).")
 | 
| 
jbe/bsw@0
 | 
   256   end
 | 
| 
jbe/bsw@0
 | 
   257   return self:join(expression, alias, condition)
 | 
| 
jbe/bsw@0
 | 
   258 end
 | 
| 
jbe/bsw@0
 | 
   259 
 | 
| 
jbe/bsw@0
 | 
   260 function selector_prototype:left_join(expression, alias, condition)
 | 
| 
jbe/bsw@0
 | 
   261   local first = (#self._from == 0)
 | 
| 
jbe/bsw@0
 | 
   262   if not first then
 | 
| 
jbe/bsw@0
 | 
   263     add(self._from, "LEFT OUTER JOIN")
 | 
| 
jbe/bsw@0
 | 
   264   end
 | 
| 
jbe/bsw@0
 | 
   265   if alias then
 | 
| 
jbe/bsw@0
 | 
   266     add(self._from, {'$ AS "$"', {expression}, {alias}})
 | 
| 
jbe/bsw@0
 | 
   267   else
 | 
| 
jbe/bsw@0
 | 
   268     add(self._from, expression)
 | 
| 
jbe/bsw@0
 | 
   269   end
 | 
| 
jbe/bsw@0
 | 
   270   if condition then
 | 
| 
jbe/bsw@0
 | 
   271     if first then
 | 
| 
jbe/bsw@0
 | 
   272       self:condition(condition)
 | 
| 
jbe/bsw@0
 | 
   273     else
 | 
| 
jbe/bsw@0
 | 
   274       add(self._from, "ON")
 | 
| 
jbe/bsw@0
 | 
   275       add(self._from, condition)
 | 
| 
jbe/bsw@0
 | 
   276     end
 | 
| 
jbe/bsw@0
 | 
   277   end
 | 
| 
jbe/bsw@0
 | 
   278   return self
 | 
| 
jbe/bsw@0
 | 
   279 end
 | 
| 
jbe/bsw@0
 | 
   280 
 | 
| 
jbe/bsw@0
 | 
   281 function selector_prototype:union(expression)
 | 
| 
jbe/bsw@0
 | 
   282   self:add_combine{"UNION $", {expression}}
 | 
| 
jbe/bsw@0
 | 
   283   return self
 | 
| 
jbe/bsw@0
 | 
   284 end
 | 
| 
jbe/bsw@0
 | 
   285 
 | 
| 
jbe/bsw@0
 | 
   286 function selector_prototype:union_all(expression)
 | 
| 
jbe/bsw@0
 | 
   287   self:add_combine{"UNION ALL $", {expression}}
 | 
| 
jbe/bsw@0
 | 
   288   return self
 | 
| 
jbe/bsw@0
 | 
   289 end
 | 
| 
jbe/bsw@0
 | 
   290 
 | 
| 
jbe/bsw@0
 | 
   291 function selector_prototype:intersect(expression)
 | 
| 
jbe/bsw@0
 | 
   292   self:add_combine{"INTERSECT $", {expression}}
 | 
| 
jbe/bsw@0
 | 
   293   return self
 | 
| 
jbe/bsw@0
 | 
   294 end
 | 
| 
jbe/bsw@0
 | 
   295 
 | 
| 
jbe/bsw@0
 | 
   296 function selector_prototype:intersect_all(expression)
 | 
| 
jbe/bsw@0
 | 
   297   self:add_combine{"INTERSECT ALL $", {expression}}
 | 
| 
jbe/bsw@0
 | 
   298   return self
 | 
| 
jbe/bsw@0
 | 
   299 end
 | 
| 
jbe/bsw@0
 | 
   300 
 | 
| 
jbe/bsw@0
 | 
   301 function selector_prototype:except(expression)
 | 
| 
jbe/bsw@0
 | 
   302   self:add_combine{"EXCEPT $", {expression}}
 | 
| 
jbe/bsw@0
 | 
   303   return self
 | 
| 
jbe/bsw@0
 | 
   304 end
 | 
| 
jbe/bsw@0
 | 
   305 
 | 
| 
jbe/bsw@0
 | 
   306 function selector_prototype:except_all(expression)
 | 
| 
jbe/bsw@0
 | 
   307   self:add_combine{"EXCEPT ALL $", {expression}}
 | 
| 
jbe/bsw@0
 | 
   308   return self
 | 
| 
jbe/bsw@0
 | 
   309 end
 | 
| 
jbe/bsw@0
 | 
   310 
 | 
| 
jbe/bsw@0
 | 
   311 function selector_prototype:set_class(class)
 | 
| 
jbe/bsw@0
 | 
   312   self._class = class
 | 
| 
jbe/bsw@0
 | 
   313   return self
 | 
| 
jbe/bsw@0
 | 
   314 end
 | 
| 
jbe/bsw@0
 | 
   315 
 | 
| 
jbe/bsw@0
 | 
   316 function selector_prototype:attach(mode, data2, field1, field2, ref1, ref2)
 | 
| 
jbe/bsw@0
 | 
   317   self._attach = {
 | 
| 
jbe/bsw@0
 | 
   318     mode = mode,
 | 
| 
jbe/bsw@0
 | 
   319     data2 = data2,
 | 
| 
jbe/bsw@0
 | 
   320     field1 = field1,
 | 
| 
jbe/bsw@0
 | 
   321     field2 = field2,
 | 
| 
jbe/bsw@0
 | 
   322     ref1 = ref1,
 | 
| 
jbe/bsw@0
 | 
   323     ref2 = ref2
 | 
| 
jbe/bsw@0
 | 
   324   }
 | 
| 
jbe/bsw@0
 | 
   325   return self
 | 
| 
jbe/bsw@0
 | 
   326 end
 | 
| 
jbe/bsw@0
 | 
   327 
 | 
| 
jbe/bsw@0
 | 
   328 -- TODO: many-to-many relations
 | 
| 
jbe/bsw@0
 | 
   329 
 | 
| 
jbe/bsw@0
 | 
   330 function selector_metatable:__tostring()
 | 
| 
jbe/bsw@0
 | 
   331   local parts = {sep = " "}
 | 
| 
jbe/bsw@0
 | 
   332   add(parts, "SELECT")
 | 
| 
jbe/bsw@0
 | 
   333   if self._distinct then
 | 
| 
jbe/bsw@0
 | 
   334     add(parts, "DISTINCT")
 | 
| 
jbe/bsw@0
 | 
   335   elseif #self._distinct_on > 0 then
 | 
| 
jbe/bsw@0
 | 
   336     add(parts, {"DISTINCT ON ($)", self._distinct_on})
 | 
| 
jbe/bsw@0
 | 
   337   end
 | 
| 
jbe/bsw@0
 | 
   338   add(parts, {"$", self._fields})
 | 
| 
jbe/bsw@0
 | 
   339   if #self._from > 0 then
 | 
| 
jbe/bsw@0
 | 
   340     add(parts, {"FROM $", self._from})
 | 
| 
jbe/bsw@0
 | 
   341   end
 | 
| 
jbe/bsw@0
 | 
   342   if #self._mode == "empty_list" then
 | 
| 
jbe/bsw@0
 | 
   343     add(parts, "WHERE FALSE")
 | 
| 
jbe/bsw@0
 | 
   344   elseif #self._where > 0 then
 | 
| 
jbe/bsw@4
 | 
   345     add(parts, {"WHERE ($)", self._where})
 | 
| 
jbe/bsw@0
 | 
   346   end
 | 
| 
jbe/bsw@0
 | 
   347   if #self._group_by > 0 then
 | 
| 
jbe/bsw@0
 | 
   348     add(parts, {"GROUP BY $", self._group_by})
 | 
| 
jbe/bsw@0
 | 
   349   end
 | 
| 
jbe/bsw@0
 | 
   350   if #self._having > 0 then
 | 
| 
jbe/bsw@4
 | 
   351     add(parts, {"HAVING ($)", self._having})
 | 
| 
jbe/bsw@0
 | 
   352   end
 | 
| 
jbe/bsw@0
 | 
   353   for i, v in ipairs(self._combine) do
 | 
| 
jbe/bsw@0
 | 
   354     add(parts, v)
 | 
| 
jbe/bsw@0
 | 
   355   end
 | 
| 
jbe/bsw@0
 | 
   356   if #self._order_by > 0 then
 | 
| 
jbe/bsw@0
 | 
   357     add(parts, {"ORDER BY $", self._order_by})
 | 
| 
jbe/bsw@0
 | 
   358   end
 | 
| 
jbe/bsw@0
 | 
   359   if self._mode == "empty_list" then
 | 
| 
jbe/bsw@0
 | 
   360     add(parts, "LIMIT 0")
 | 
| 
jbe/bsw@0
 | 
   361   elseif self._mode ~= "list" then
 | 
| 
jbe/bsw@0
 | 
   362     add(parts, "LIMIT 1")
 | 
| 
jbe/bsw@0
 | 
   363   elseif self._limit then
 | 
| 
jbe/bsw@0
 | 
   364     add(parts, "LIMIT " .. self._limit)
 | 
| 
jbe/bsw@0
 | 
   365   end
 | 
| 
jbe/bsw@0
 | 
   366   if self._offset then
 | 
| 
jbe/bsw@0
 | 
   367     add(parts, "OFFSET " .. self._offset)
 | 
| 
jbe/bsw@0
 | 
   368   end
 | 
| 
jbe/bsw@4
 | 
   369   if self._write_lock.all then
 | 
| 
jbe/bsw@4
 | 
   370     add(parts, "FOR UPDATE")
 | 
| 
jbe/bsw@4
 | 
   371   else
 | 
| 
jbe/bsw@4
 | 
   372     if self._read_lock.all then
 | 
| 
jbe/bsw@4
 | 
   373       add(parts, "FOR SHARE")
 | 
| 
jbe/bsw@4
 | 
   374     elseif #self._read_lock > 0 then
 | 
| 
jbe/bsw@4
 | 
   375       add(parts, {"FOR SHARE OF $", self._read_lock})
 | 
| 
jbe/bsw@4
 | 
   376     end
 | 
| 
jbe/bsw@4
 | 
   377     if #self._write_lock > 0 then
 | 
| 
jbe/bsw@4
 | 
   378       add(parts, {"FOR UPDATE OF $", self._write_lock})
 | 
| 
jbe/bsw@4
 | 
   379     end
 | 
| 
jbe/bsw@4
 | 
   380   end
 | 
| 
jbe/bsw@0
 | 
   381   return self._db_conn:assemble_command{"$", parts}
 | 
| 
jbe/bsw@0
 | 
   382 end
 | 
| 
jbe/bsw@0
 | 
   383 
 | 
| 
jbe/bsw@0
 | 
   384 function selector_prototype:try_exec()
 | 
| 
jbe/bsw@0
 | 
   385   if self._mode == "empty_list" then
 | 
| 
jbe/bsw@0
 | 
   386     if self._class then
 | 
| 
jbe/bsw@0
 | 
   387       return nil, self._class:create_list()
 | 
| 
jbe/bsw@0
 | 
   388     else
 | 
| 
jbe/bsw@0
 | 
   389        return nil, self._db_conn:create_list()
 | 
| 
jbe/bsw@0
 | 
   390     end
 | 
| 
jbe/bsw@0
 | 
   391   end
 | 
| 
jbe/bsw@0
 | 
   392   local db_error, db_result = self._db_conn:try_query(self, self._mode)
 | 
| 
jbe/bsw@0
 | 
   393   if db_error then
 | 
| 
jbe/bsw@0
 | 
   394     return db_error
 | 
| 
jbe/bsw@0
 | 
   395   elseif db_result then
 | 
| 
jbe/bsw@0
 | 
   396     if self._class then set_class(db_result, self._class) end
 | 
| 
jbe/bsw@0
 | 
   397     if self._attach then
 | 
| 
jbe/bsw@0
 | 
   398       attach(
 | 
| 
jbe/bsw@0
 | 
   399         self._attach.mode,
 | 
| 
jbe/bsw@0
 | 
   400         db_result,
 | 
| 
jbe/bsw@0
 | 
   401         self._attach.data2,
 | 
| 
jbe/bsw@0
 | 
   402         self._attach.field1,
 | 
| 
jbe/bsw@0
 | 
   403         self._attach.field2,
 | 
| 
jbe/bsw@0
 | 
   404         self._attach.ref1,
 | 
| 
jbe/bsw@0
 | 
   405         self._attach.ref2
 | 
| 
jbe/bsw@0
 | 
   406       )
 | 
| 
jbe/bsw@0
 | 
   407     end
 | 
| 
jbe/bsw@0
 | 
   408     return nil, db_result
 | 
| 
jbe/bsw@0
 | 
   409   else
 | 
| 
jbe/bsw@0
 | 
   410     return nil
 | 
| 
jbe/bsw@0
 | 
   411   end
 | 
| 
jbe/bsw@0
 | 
   412 end
 | 
| 
jbe/bsw@0
 | 
   413 
 | 
| 
jbe/bsw@0
 | 
   414 function selector_prototype:exec()
 | 
| 
jbe/bsw@0
 | 
   415   local db_error, result = self:try_exec()
 | 
| 
jbe/bsw@0
 | 
   416   if db_error then
 | 
| 
jbe/bsw@0
 | 
   417     db_error:escalate()
 | 
| 
jbe/bsw@0
 | 
   418   else
 | 
| 
jbe/bsw@0
 | 
   419     return result
 | 
| 
jbe/bsw@0
 | 
   420   end
 | 
| 
jbe/bsw@0
 | 
   421 end
 | 
| 
jbe/bsw@0
 | 
   422 
 | 
| 
jbe/bsw@4
 | 
   423 -- NOTE: This function caches the result!
 | 
| 
jbe/bsw@4
 | 
   424 function selector_prototype:count()
 | 
| 
jbe/bsw@4
 | 
   425   if not self._count then
 | 
| 
jbe/bsw@4
 | 
   426     local count_selector = self:get_db_conn():new_selector()
 | 
| 
jbe/bsw@4
 | 
   427     count_selector:add_field('count(1)')
 | 
| 
jbe/bsw@4
 | 
   428     count_selector:add_from(self)
 | 
| 
jbe/bsw@4
 | 
   429     count_selector:single_object_mode()
 | 
| 
jbe/bsw@4
 | 
   430     self._count = count_selector:exec().count
 | 
| 
jbe/bsw@4
 | 
   431   end
 | 
| 
jbe/bsw@4
 | 
   432   return self._count
 | 
| 
jbe/bsw@4
 | 
   433 end
 | 
| 
jbe/bsw@4
 | 
   434 
 | 
| 
jbe/bsw@0
 | 
   435 
 | 
| 
jbe/bsw@0
 | 
   436 
 | 
| 
jbe/bsw@0
 | 
   437 -----------------
 | 
| 
jbe/bsw@0
 | 
   438 -- attachments --
 | 
| 
jbe/bsw@0
 | 
   439 -----------------
 | 
| 
jbe/bsw@0
 | 
   440 
 | 
| 
jbe/bsw@0
 | 
   441 local function attach_key(row, fields)
 | 
| 
jbe/bsw@0
 | 
   442   local t = type(fields)
 | 
| 
jbe/bsw@0
 | 
   443   if t == "string" then
 | 
| 
jbe/bsw@0
 | 
   444     return tostring(row[fields])
 | 
| 
jbe/bsw@0
 | 
   445   elseif t == "table" then
 | 
| 
jbe/bsw@0
 | 
   446     local r = {}
 | 
| 
jbe/bsw@0
 | 
   447     for idx, field in ipairs(fields) do
 | 
| 
jbe/bsw@0
 | 
   448       r[idx] = string.format("%q", row[field])
 | 
| 
jbe/bsw@0
 | 
   449     end
 | 
| 
jbe/bsw@0
 | 
   450     return table.concat(r)
 | 
| 
jbe/bsw@0
 | 
   451   else
 | 
| 
jbe/bsw@0
 | 
   452     error("Field information for 'mondelefant.attach' is neither a string nor a table.")
 | 
| 
jbe/bsw@0
 | 
   453   end
 | 
| 
jbe/bsw@0
 | 
   454 end
 | 
| 
jbe/bsw@0
 | 
   455 
 | 
| 
jbe/bsw@0
 | 
   456 function attach(mode, data1, data2, key1, key2, ref1, ref2)
 | 
| 
jbe/bsw@0
 | 
   457   local many1, many2
 | 
| 
jbe/bsw@0
 | 
   458   if mode == "11" then
 | 
| 
jbe/bsw@0
 | 
   459     many1 = false
 | 
| 
jbe/bsw@0
 | 
   460     many2 = false
 | 
| 
jbe/bsw@0
 | 
   461   elseif mode == "1m" then
 | 
| 
jbe/bsw@0
 | 
   462     many1 = false
 | 
| 
jbe/bsw@0
 | 
   463     many2 = true
 | 
| 
jbe/bsw@0
 | 
   464   elseif mode == "m1" then
 | 
| 
jbe/bsw@0
 | 
   465     many1 = true
 | 
| 
jbe/bsw@0
 | 
   466     many2 = false
 | 
| 
jbe/bsw@0
 | 
   467   elseif mode == "mm" then
 | 
| 
jbe/bsw@0
 | 
   468     many1 = true
 | 
| 
jbe/bsw@0
 | 
   469     many2 = true
 | 
| 
jbe/bsw@0
 | 
   470   else
 | 
| 
jbe/bsw@0
 | 
   471     error("Unknown mode specified for 'mondelefant.attach'.")
 | 
| 
jbe/bsw@0
 | 
   472   end
 | 
| 
jbe/bsw@0
 | 
   473   local list1, list2
 | 
| 
jbe/bsw@0
 | 
   474   if data1._type == "object" then
 | 
| 
jbe/bsw@0
 | 
   475     list1 = { data1 }
 | 
| 
jbe/bsw@0
 | 
   476   elseif data1._type == "list" then
 | 
| 
jbe/bsw@0
 | 
   477     list1 = data1
 | 
| 
jbe/bsw@0
 | 
   478   else
 | 
| 
jbe/bsw@0
 | 
   479     error("First result data given to 'mondelefant.attach' is invalid.")
 | 
| 
jbe/bsw@0
 | 
   480   end
 | 
| 
jbe/bsw@0
 | 
   481   if data2._type == "object" then
 | 
| 
jbe/bsw@0
 | 
   482     list2 = { data2 }
 | 
| 
jbe/bsw@0
 | 
   483   elseif data2._type == "list" then
 | 
| 
jbe/bsw@0
 | 
   484     list2 = data2
 | 
| 
jbe/bsw@0
 | 
   485   else
 | 
| 
jbe/bsw@0
 | 
   486     error("Second result data given to 'mondelefant.attach' is invalid.")
 | 
| 
jbe/bsw@0
 | 
   487   end
 | 
| 
jbe/bsw@0
 | 
   488   local hash1 = {}
 | 
| 
jbe/bsw@0
 | 
   489   local hash2 = {}
 | 
| 
jbe/bsw@0
 | 
   490   if ref2 then
 | 
| 
jbe/bsw@0
 | 
   491     for i, row in ipairs(list1) do
 | 
| 
jbe/bsw@0
 | 
   492       local key = attach_key(row, key1)
 | 
| 
jbe/bsw@0
 | 
   493       local list = hash1[key]
 | 
| 
jbe/bsw@0
 | 
   494       if not list then list = {}; hash1[key] = list end
 | 
| 
jbe/bsw@0
 | 
   495       list[#list + 1] = row
 | 
| 
jbe/bsw@0
 | 
   496     end
 | 
| 
jbe/bsw@0
 | 
   497   end
 | 
| 
jbe/bsw@0
 | 
   498   if ref1 then
 | 
| 
jbe/bsw@0
 | 
   499     for i, row in ipairs(list2) do
 | 
| 
jbe/bsw@0
 | 
   500       local key = attach_key(row, key2)
 | 
| 
jbe/bsw@0
 | 
   501       local list = hash2[key]
 | 
| 
jbe/bsw@0
 | 
   502       if not list then list = {}; hash2[key] = list end
 | 
| 
jbe/bsw@0
 | 
   503       list[#list + 1] = row
 | 
| 
jbe/bsw@0
 | 
   504     end
 | 
| 
jbe/bsw@0
 | 
   505     for i, row in ipairs(list1) do
 | 
| 
jbe/bsw@0
 | 
   506       local key = attach_key(row, key1)
 | 
| 
jbe/bsw@0
 | 
   507       local matching_rows = hash2[key]
 | 
| 
jbe/bsw@0
 | 
   508       if many2 then
 | 
| 
jbe/bsw@0
 | 
   509         local list = data2._connection:create_list(matching_rows)
 | 
| 
jbe/bsw@0
 | 
   510         list._class = data2._class
 | 
| 
jbe/bsw@0
 | 
   511         row._ref[ref1] = list
 | 
| 
jbe/bsw@0
 | 
   512       elseif matching_rows and #matching_rows == 1 then
 | 
| 
jbe/bsw@0
 | 
   513         row._ref[ref1] = matching_rows[1]
 | 
| 
jbe/bsw@0
 | 
   514       else
 | 
| 
jbe/bsw@0
 | 
   515         row._ref[ref1] = false
 | 
| 
jbe/bsw@0
 | 
   516       end
 | 
| 
jbe/bsw@0
 | 
   517     end
 | 
| 
jbe/bsw@0
 | 
   518   end
 | 
| 
jbe/bsw@0
 | 
   519   if ref2 then
 | 
| 
jbe/bsw@0
 | 
   520     for i, row in ipairs(list2) do
 | 
| 
jbe/bsw@0
 | 
   521       local key = attach_key(row, key2)
 | 
| 
jbe/bsw@0
 | 
   522       local matching_rows = hash1[key]
 | 
| 
jbe/bsw@0
 | 
   523       if many1 then
 | 
| 
jbe/bsw@0
 | 
   524         local list = data1._connection:create_list(matching_rows)
 | 
| 
jbe/bsw@0
 | 
   525         list._class = data1._class
 | 
| 
jbe/bsw@0
 | 
   526         row._ref[ref2] = list
 | 
| 
jbe/bsw@0
 | 
   527       elseif matching_rows and #matching_rows == 1 then
 | 
| 
jbe/bsw@0
 | 
   528         row._ref[ref2] = matching_rows[1]
 | 
| 
jbe/bsw@0
 | 
   529       else
 | 
| 
jbe/bsw@0
 | 
   530         row._ref[ref2] = false
 | 
| 
jbe/bsw@0
 | 
   531       end
 | 
| 
jbe/bsw@0
 | 
   532     end
 | 
| 
jbe/bsw@0
 | 
   533   end
 | 
| 
jbe/bsw@0
 | 
   534 end
 | 
| 
jbe/bsw@0
 | 
   535 
 | 
| 
jbe/bsw@0
 | 
   536 
 | 
| 
jbe/bsw@0
 | 
   537 
 | 
| 
jbe/bsw@0
 | 
   538 ------------------
 | 
| 
jbe/bsw@0
 | 
   539 -- model system --
 | 
| 
jbe/bsw@0
 | 
   540 ------------------
 | 
| 
jbe/bsw@0
 | 
   541 
 | 
| 
jbe/bsw@0
 | 
   542 class_prototype.primary_key = "id"
 | 
| 
jbe/bsw@0
 | 
   543 
 | 
| 
jbe/bsw@0
 | 
   544 function class_prototype:get_db_conn()
 | 
| 
jbe/bsw@0
 | 
   545   error(
 | 
| 
jbe/bsw@0
 | 
   546     "Method mondelefant class(_prototype):get_db_conn() " ..
 | 
| 
jbe/bsw@0
 | 
   547     "has to be implemented."
 | 
| 
jbe/bsw@0
 | 
   548   )
 | 
| 
jbe/bsw@0
 | 
   549 end
 | 
| 
jbe/bsw@0
 | 
   550 
 | 
| 
jbe/bsw@0
 | 
   551 function class_prototype:get_qualified_table()
 | 
| 
jbe/bsw@0
 | 
   552   if not self.table then error "Table unknown." end
 | 
| 
jbe/bsw@0
 | 
   553   if self.schema then
 | 
| 
jbe/bsw@0
 | 
   554     return '"' .. self.schema .. '"."' .. self.table .. '"'
 | 
| 
jbe/bsw@0
 | 
   555   else
 | 
| 
jbe/bsw@0
 | 
   556     return '"' .. self.table .. '"'
 | 
| 
jbe/bsw@0
 | 
   557   end
 | 
| 
jbe/bsw@0
 | 
   558 end
 | 
| 
jbe/bsw@0
 | 
   559 
 | 
| 
jbe/bsw@0
 | 
   560 function class_prototype:get_qualified_table_literal()
 | 
| 
jbe/bsw@0
 | 
   561   if not self.table then error "Table unknown." end
 | 
| 
jbe/bsw@0
 | 
   562   if self.schema then
 | 
| 
jbe/bsw@0
 | 
   563     return self.schema .. '.' .. self.table
 | 
| 
jbe/bsw@0
 | 
   564   else
 | 
| 
jbe/bsw@0
 | 
   565     return self.table
 | 
| 
jbe/bsw@0
 | 
   566   end
 | 
| 
jbe/bsw@0
 | 
   567 end
 | 
| 
jbe/bsw@0
 | 
   568 
 | 
| 
jbe/bsw@0
 | 
   569 function class_prototype:get_primary_key_list()
 | 
| 
jbe/bsw@0
 | 
   570   local primary_key = self.primary_key
 | 
| 
jbe/bsw@0
 | 
   571   if type(primary_key) == "string" then
 | 
| 
jbe/bsw@0
 | 
   572     return {primary_key}
 | 
| 
jbe/bsw@0
 | 
   573   else
 | 
| 
jbe/bsw@0
 | 
   574     return primary_key
 | 
| 
jbe/bsw@0
 | 
   575   end
 | 
| 
jbe/bsw@0
 | 
   576 end
 | 
| 
jbe/bsw@0
 | 
   577 
 | 
| 
jbe/bsw@0
 | 
   578 function class_prototype:get_columns()
 | 
| 
jbe/bsw@0
 | 
   579   if self._columns then
 | 
| 
jbe/bsw@0
 | 
   580     return self._columns
 | 
| 
jbe/bsw@0
 | 
   581   end
 | 
| 
jbe/bsw@0
 | 
   582   local selector = self:get_db_conn():new_selector()
 | 
| 
jbe/bsw@0
 | 
   583   selector:set_class(self)
 | 
| 
jbe/bsw@0
 | 
   584   selector:from(self:get_qualified_table())
 | 
| 
jbe/bsw@0
 | 
   585   selector:add_field("*")
 | 
| 
jbe/bsw@0
 | 
   586   selector:add_where("FALSE")
 | 
| 
jbe/bsw@0
 | 
   587   local db_result = selector:exec()
 | 
| 
jbe/bsw@0
 | 
   588   local connection = db_result._connection
 | 
| 
jbe/bsw@0
 | 
   589   local columns = {}
 | 
| 
jbe/bsw@0
 | 
   590   for idx, info in ipairs(db_result._column_info) do
 | 
| 
jbe/bsw@0
 | 
   591     local key   = info.field_name
 | 
| 
jbe/bsw@0
 | 
   592     local value = {
 | 
| 
jbe/bsw@0
 | 
   593       name = key,
 | 
| 
jbe/bsw@0
 | 
   594       type = connection.type_mappings[info.type]
 | 
| 
jbe/bsw@0
 | 
   595     }
 | 
| 
jbe/bsw@0
 | 
   596     columns[key] = value
 | 
| 
jbe/bsw@0
 | 
   597     table.insert(columns, value)
 | 
| 
jbe/bsw@0
 | 
   598   end
 | 
| 
jbe/bsw@0
 | 
   599   self._columns = columns
 | 
| 
jbe/bsw@0
 | 
   600   return columns
 | 
| 
jbe/bsw@0
 | 
   601 end
 | 
| 
jbe/bsw@0
 | 
   602 
 | 
| 
jbe/bsw@0
 | 
   603 function class_prototype:new_selector(db_conn)
 | 
| 
jbe/bsw@0
 | 
   604   local selector = (db_conn or self:get_db_conn()):new_selector()
 | 
| 
jbe/bsw@0
 | 
   605   selector:set_class(self)
 | 
| 
jbe/bsw@0
 | 
   606   selector:from(self:get_qualified_table())
 | 
| 
jbe/bsw@0
 | 
   607   selector:add_field(self:get_qualified_table() .. ".*")
 | 
| 
jbe/bsw@0
 | 
   608   return selector
 | 
| 
jbe/bsw@0
 | 
   609 end
 | 
| 
jbe/bsw@0
 | 
   610 
 | 
| 
jbe/bsw@0
 | 
   611 function class_prototype:create_list()
 | 
| 
jbe/bsw@0
 | 
   612   local list = self:get_db_conn():create_list()
 | 
| 
jbe/bsw@0
 | 
   613   list._class = self
 | 
| 
jbe/bsw@0
 | 
   614   return list
 | 
| 
jbe/bsw@0
 | 
   615 end
 | 
| 
jbe/bsw@0
 | 
   616 
 | 
| 
jbe/bsw@0
 | 
   617 function class_prototype:new()
 | 
| 
jbe/bsw@0
 | 
   618   local object = self:get_db_conn():create_object()
 | 
| 
jbe/bsw@0
 | 
   619   object._class = self
 | 
| 
jbe/bsw@0
 | 
   620   object._new = true
 | 
| 
jbe/bsw@0
 | 
   621   return object
 | 
| 
jbe/bsw@0
 | 
   622 end
 | 
| 
jbe/bsw@0
 | 
   623 
 | 
| 
jbe/bsw@0
 | 
   624 function class_prototype.object:try_save()
 | 
| 
jbe/bsw@0
 | 
   625   if not self._class then
 | 
| 
jbe/bsw@0
 | 
   626     error("Cannot save object: No class information available.")
 | 
| 
jbe/bsw@0
 | 
   627   end
 | 
| 
jbe/bsw@0
 | 
   628   local primary_key = self._class:get_primary_key_list()
 | 
| 
jbe/bsw@0
 | 
   629   local primary_key_sql = { sep = ", " }
 | 
| 
jbe/bsw@0
 | 
   630   for idx, value in ipairs(primary_key) do
 | 
| 
jbe/bsw@0
 | 
   631     primary_key_sql[idx] = '"' .. value .. '"'
 | 
| 
jbe/bsw@0
 | 
   632   end
 | 
| 
jbe/bsw@0
 | 
   633   if self._new then
 | 
| 
jbe/bsw@0
 | 
   634     local fields = {sep = ", "}
 | 
| 
jbe/bsw@0
 | 
   635     local values = {sep = ", "}
 | 
| 
jbe/bsw@0
 | 
   636     for key, dummy in pairs(self._dirty or {}) do
 | 
| 
jbe/bsw@0
 | 
   637       add(fields, {'"$"', {key}})
 | 
| 
jbe/bsw@0
 | 
   638       add(values, {'?', self[key]})
 | 
| 
jbe/bsw@0
 | 
   639     end
 | 
| 
jbe/bsw@0
 | 
   640     if compat_returning then  -- compatibility for PostgreSQL 8.1
 | 
| 
jbe/bsw@0
 | 
   641       local db_error, db_result1, db_result2 = self._connection:try_query(
 | 
| 
jbe/bsw@0
 | 
   642         {
 | 
| 
jbe/bsw@0
 | 
   643           'INSERT INTO $ ($) VALUES ($)',
 | 
| 
jbe/bsw@0
 | 
   644           {self._class:get_qualified_table()},
 | 
| 
jbe/bsw@0
 | 
   645           fields,
 | 
| 
jbe/bsw@0
 | 
   646           values,
 | 
| 
jbe/bsw@0
 | 
   647           primary_key_sql
 | 
| 
jbe/bsw@0
 | 
   648         },
 | 
| 
jbe/bsw@0
 | 
   649         "list",
 | 
| 
jbe/bsw@0
 | 
   650         {
 | 
| 
jbe/bsw@0
 | 
   651           'SELECT currval(?)',
 | 
| 
jbe/bsw@0
 | 
   652           self._class.table .. '_id_seq'
 | 
| 
jbe/bsw@0
 | 
   653         },
 | 
| 
jbe/bsw@0
 | 
   654         "object"
 | 
| 
jbe/bsw@0
 | 
   655       )
 | 
| 
jbe/bsw@0
 | 
   656       if db_error then
 | 
| 
jbe/bsw@0
 | 
   657         return db_error
 | 
| 
jbe/bsw@0
 | 
   658       end
 | 
| 
jbe/bsw@0
 | 
   659       self.id = db_result2.id
 | 
| 
jbe/bsw@0
 | 
   660     else
 | 
| 
jbe/bsw@0
 | 
   661       local db_error, db_result = self._connection:try_query(
 | 
| 
jbe/bsw@0
 | 
   662         {
 | 
| 
jbe/bsw@0
 | 
   663           'INSERT INTO $ ($) VALUES ($) RETURNING ($)',
 | 
| 
jbe/bsw@0
 | 
   664           {self._class:get_qualified_table()},
 | 
| 
jbe/bsw@0
 | 
   665           fields,
 | 
| 
jbe/bsw@0
 | 
   666           values,
 | 
| 
jbe/bsw@0
 | 
   667           primary_key_sql
 | 
| 
jbe/bsw@0
 | 
   668         },
 | 
| 
jbe/bsw@0
 | 
   669         "object"
 | 
| 
jbe/bsw@0
 | 
   670       )
 | 
| 
jbe/bsw@0
 | 
   671       if db_error then
 | 
| 
jbe/bsw@0
 | 
   672         return db_error
 | 
| 
jbe/bsw@0
 | 
   673       end
 | 
| 
jbe/bsw@0
 | 
   674       for idx, value in ipairs(primary_key) do
 | 
| 
jbe/bsw@0
 | 
   675         self[value] = db_result[value]
 | 
| 
jbe/bsw@0
 | 
   676       end
 | 
| 
jbe/bsw@0
 | 
   677     end
 | 
| 
jbe/bsw@0
 | 
   678     self._new = false
 | 
| 
jbe/bsw@0
 | 
   679   else
 | 
| 
jbe/bsw@0
 | 
   680     local command_sets = {sep = ", "}
 | 
| 
jbe/bsw@0
 | 
   681     for key, dummy in pairs(self._dirty or {}) do
 | 
| 
jbe/bsw@0
 | 
   682       add(command_sets, {'"$" = ?', {key}, self[key]})
 | 
| 
jbe/bsw@0
 | 
   683     end
 | 
| 
jbe/bsw@0
 | 
   684     if #command_sets >= 1 then
 | 
| 
jbe/bsw@0
 | 
   685       local primary_key_compare = {sep = " AND "}
 | 
| 
jbe/bsw@0
 | 
   686       for idx, value in ipairs(primary_key) do
 | 
| 
jbe/bsw@0
 | 
   687         primary_key_compare[idx] = {
 | 
| 
jbe/bsw@0
 | 
   688           "$ = ?",
 | 
| 
jbe/bsw@0
 | 
   689           {'"' .. value .. '"'},
 | 
| 
jbe/bsw@0
 | 
   690           self[value]
 | 
| 
jbe/bsw@0
 | 
   691         }
 | 
| 
jbe/bsw@0
 | 
   692       end
 | 
| 
jbe/bsw@0
 | 
   693       local db_error = self._connection:try_query{
 | 
| 
jbe/bsw@0
 | 
   694         'UPDATE $ SET $ WHERE $',
 | 
| 
jbe/bsw@0
 | 
   695         {self._class:get_qualified_table()},
 | 
| 
jbe/bsw@0
 | 
   696         command_sets,
 | 
| 
jbe/bsw@0
 | 
   697         primary_key_compare
 | 
| 
jbe/bsw@0
 | 
   698       }
 | 
| 
jbe/bsw@0
 | 
   699       if db_error then
 | 
| 
jbe/bsw@0
 | 
   700         return db_error
 | 
| 
jbe/bsw@0
 | 
   701       end
 | 
| 
jbe/bsw@0
 | 
   702     end
 | 
| 
jbe/bsw@0
 | 
   703   end
 | 
| 
jbe/bsw@0
 | 
   704   return nil
 | 
| 
jbe/bsw@0
 | 
   705 end
 | 
| 
jbe/bsw@0
 | 
   706 
 | 
| 
jbe/bsw@0
 | 
   707 function class_prototype.object:save()
 | 
| 
jbe/bsw@0
 | 
   708   local db_error = self:try_save()
 | 
| 
jbe/bsw@0
 | 
   709   if db_error then
 | 
| 
jbe/bsw@0
 | 
   710     db_error:escalate()
 | 
| 
jbe/bsw@0
 | 
   711   end
 | 
| 
jbe/bsw@0
 | 
   712   return self
 | 
| 
jbe/bsw@0
 | 
   713 end
 | 
| 
jbe/bsw@0
 | 
   714 
 | 
| 
jbe/bsw@0
 | 
   715 function class_prototype.object:try_destroy()
 | 
| 
jbe/bsw@0
 | 
   716   if not self._class then
 | 
| 
jbe/bsw@0
 | 
   717     error("Cannot destroy object: No class information available.")
 | 
| 
jbe/bsw@0
 | 
   718   end
 | 
| 
jbe/bsw@0
 | 
   719   local primary_key = self._class:get_primary_key_list()
 | 
| 
jbe/bsw@0
 | 
   720   local primary_key_compare = {sep = " AND "}
 | 
| 
jbe/bsw@0
 | 
   721   for idx, value in ipairs(primary_key) do
 | 
| 
jbe/bsw@0
 | 
   722     primary_key_compare[idx] = {
 | 
| 
jbe/bsw@0
 | 
   723       "$ = ?",
 | 
| 
jbe/bsw@0
 | 
   724       {'"' .. value .. '"'},
 | 
| 
jbe/bsw@0
 | 
   725       self[value]
 | 
| 
jbe/bsw@0
 | 
   726     }
 | 
| 
jbe/bsw@0
 | 
   727   end
 | 
| 
jbe/bsw@0
 | 
   728   return self._connection:try_query{
 | 
| 
jbe/bsw@0
 | 
   729     'DELETE FROM $ WHERE $',
 | 
| 
jbe/bsw@0
 | 
   730     {self._class:get_qualified_table()},
 | 
| 
jbe/bsw@0
 | 
   731     primary_key_compare
 | 
| 
jbe/bsw@0
 | 
   732   }
 | 
| 
jbe/bsw@0
 | 
   733 end
 | 
| 
jbe/bsw@0
 | 
   734 
 | 
| 
jbe/bsw@0
 | 
   735 function class_prototype.object:destroy()
 | 
| 
jbe/bsw@0
 | 
   736   local db_error = self:try_destroy()
 | 
| 
jbe/bsw@0
 | 
   737   if db_error then
 | 
| 
jbe/bsw@0
 | 
   738     db_error:escalate()
 | 
| 
jbe/bsw@0
 | 
   739   end
 | 
| 
jbe/bsw@0
 | 
   740   return self
 | 
| 
jbe/bsw@0
 | 
   741 end
 | 
| 
jbe/bsw@0
 | 
   742 
 | 
| 
jbe/bsw@0
 | 
   743 function class_prototype.list:get_reference_selector(
 | 
| 
jbe/bsw@0
 | 
   744   ref_name, options, ref_alias, back_ref_alias
 | 
| 
jbe/bsw@0
 | 
   745 )
 | 
| 
jbe/bsw@0
 | 
   746   local ref_info = self._class.references[ref_name]
 | 
| 
jbe/bsw@0
 | 
   747   if not ref_info then
 | 
| 
jbe/bsw@0
 | 
   748     error('Reference with name "' .. ref_name .. '" not found.')
 | 
| 
jbe/bsw@0
 | 
   749   end
 | 
| 
jbe/bsw@0
 | 
   750   local selector = ref_info.selector_generator(self, options or {})
 | 
| 
jbe/bsw@0
 | 
   751   local mode = ref_info.mode
 | 
| 
jbe/bsw@0
 | 
   752   if mode == "mm" or mode == "1m" then
 | 
| 
jbe/bsw@0
 | 
   753     mode = "m1"
 | 
| 
jbe/bsw@0
 | 
   754   elseif mode == "m1" then
 | 
| 
jbe/bsw@0
 | 
   755     mode = "1m"
 | 
| 
jbe/bsw@0
 | 
   756   end
 | 
| 
jbe/bsw@0
 | 
   757   local ref_alias = ref_alias
 | 
| 
jbe/bsw@0
 | 
   758   if ref_alias == false then
 | 
| 
jbe/bsw@0
 | 
   759     ref_alias = nil
 | 
| 
jbe/bsw@0
 | 
   760   elseif ref_alias == nil then
 | 
| 
jbe/bsw@0
 | 
   761     ref_alias = ref_name
 | 
| 
jbe/bsw@0
 | 
   762   end
 | 
| 
jbe/bsw@0
 | 
   763   local back_ref_alias
 | 
| 
jbe/bsw@0
 | 
   764   if back_ref_alias == false then
 | 
| 
jbe/bsw@0
 | 
   765     back_ref_alias = nil
 | 
| 
jbe/bsw@0
 | 
   766   elseif back_ref_alias == nil then
 | 
| 
jbe/bsw@0
 | 
   767     back_ref_alias = ref_info.back_ref
 | 
| 
jbe/bsw@0
 | 
   768   end
 | 
| 
jbe/bsw@0
 | 
   769   selector:attach(
 | 
| 
jbe/bsw@0
 | 
   770     mode,
 | 
| 
jbe/bsw@0
 | 
   771     self,
 | 
| 
jbe/bsw@0
 | 
   772     ref_info.that_key,                   ref_info.this_key,
 | 
| 
jbe/bsw@0
 | 
   773     back_ref_alias or ref_info.back_ref, ref_alias or ref_name
 | 
| 
jbe/bsw@0
 | 
   774   )
 | 
| 
jbe/bsw@0
 | 
   775   return selector
 | 
| 
jbe/bsw@0
 | 
   776 end
 | 
| 
jbe/bsw@0
 | 
   777 
 | 
| 
jbe/bsw@0
 | 
   778 function class_prototype.list.load(...)
 | 
| 
jbe/bsw@0
 | 
   779   return class_prototype.list.get_reference_selector(...):exec()
 | 
| 
jbe/bsw@0
 | 
   780 end
 | 
| 
jbe/bsw@0
 | 
   781 
 | 
| 
jbe/bsw@0
 | 
   782 function class_prototype.object:get_reference_selector(...)
 | 
| 
jbe/bsw@0
 | 
   783   local list = self._class:create_list()
 | 
| 
jbe/bsw@0
 | 
   784   list[1] = self
 | 
| 
jbe/bsw@0
 | 
   785   return list:get_reference_selector(...)
 | 
| 
jbe/bsw@0
 | 
   786 end
 | 
| 
jbe/bsw@0
 | 
   787 
 | 
| 
jbe/bsw@0
 | 
   788 function class_prototype.object.load(...)
 | 
| 
jbe/bsw@0
 | 
   789   return class_prototype.object.get_reference_selector(...):exec()
 | 
| 
jbe/bsw@0
 | 
   790 end
 | 
| 
jbe/bsw@0
 | 
   791 
 | 
| 
jbe/bsw@0
 | 
   792 
 | 
| 
jbe/bsw@0
 | 
   793 function class_prototype:add_reference(args)
 | 
| 
jbe/bsw@0
 | 
   794   local selector_generator    = args.selector_generator
 | 
| 
jbe/bsw@0
 | 
   795   local mode                  = args.mode
 | 
| 
jbe/bsw@0
 | 
   796   local to                    = args.to
 | 
| 
jbe/bsw@0
 | 
   797   local this_key              = args.this_key
 | 
| 
jbe/bsw@0
 | 
   798   local that_key              = args.that_key
 | 
| 
jbe/bsw@0
 | 
   799   local connected_by_table    = args.connected_by_table  -- TODO: split to table and schema
 | 
| 
jbe/bsw@0
 | 
   800   local connected_by_this_key = args.connected_by_this_key
 | 
| 
jbe/bsw@0
 | 
   801   local connected_by_that_key = args.connected_by_that_key
 | 
| 
jbe/bsw@0
 | 
   802   local ref                   = args.ref
 | 
| 
jbe/bsw@0
 | 
   803   local back_ref              = args.back_ref
 | 
| 
jbe/bsw@0
 | 
   804   local default_order         = args.default_order
 | 
| 
jbe/bsw@0
 | 
   805   local model
 | 
| 
jbe/bsw@0
 | 
   806   local function get_model()
 | 
| 
jbe/bsw@0
 | 
   807     if not model then
 | 
| 
jbe/bsw@0
 | 
   808       if type(to) == "string" then
 | 
| 
jbe/bsw@0
 | 
   809         model = _G
 | 
| 
jbe/bsw@0
 | 
   810         for path_element in string.gmatch(to, "[^.]+") do
 | 
| 
jbe/bsw@0
 | 
   811           model = model[path_element]
 | 
| 
jbe/bsw@0
 | 
   812         end
 | 
| 
jbe/bsw@0
 | 
   813       elseif type(to) == "function" then
 | 
| 
jbe/bsw@0
 | 
   814         model = to()
 | 
| 
jbe/bsw@0
 | 
   815       else
 | 
| 
jbe/bsw@0
 | 
   816         model = to
 | 
| 
jbe/bsw@0
 | 
   817       end
 | 
| 
jbe/bsw@0
 | 
   818     end
 | 
| 
jbe/bsw@0
 | 
   819     if not model or model == _G then
 | 
| 
jbe/bsw@0
 | 
   820       error("Could not get model for reference.")
 | 
| 
jbe/bsw@0
 | 
   821     end
 | 
| 
jbe/bsw@0
 | 
   822     return model
 | 
| 
jbe/bsw@0
 | 
   823   end
 | 
| 
jbe/bsw@0
 | 
   824   self.references[ref] = {
 | 
| 
jbe/bsw@0
 | 
   825     mode     = mode,
 | 
| 
jbe/bsw@0
 | 
   826     this_key = this_key,
 | 
| 
jbe/bsw@0
 | 
   827     that_key = connected_by_table and "mm_ref_" or that_key,
 | 
| 
jbe/bsw@0
 | 
   828     ref      = ref,
 | 
| 
jbe/bsw@0
 | 
   829     back_ref = back_ref,
 | 
| 
jbe/bsw@0
 | 
   830     selector_generator = selector_generator or function(list, options)
 | 
| 
jbe/bsw@0
 | 
   831       -- TODO: support tuple keys
 | 
| 
jbe/bsw@0
 | 
   832       local options = options or {}
 | 
| 
jbe/bsw@0
 | 
   833       local model = get_model()
 | 
| 
jbe/bsw@0
 | 
   834       -- TODO: too many records cause PostgreSQL command stack overflow
 | 
| 
jbe/bsw@0
 | 
   835       local ids = { sep = ", " }
 | 
| 
jbe/bsw@0
 | 
   836       for i, object in ipairs(list) do
 | 
| 
jbe/bsw@0
 | 
   837         local id = object[this_key]
 | 
| 
jbe/bsw@0
 | 
   838         if id ~= nil then
 | 
| 
jbe/bsw@0
 | 
   839           ids[#ids+1] = {"?", id}
 | 
| 
jbe/bsw@0
 | 
   840         end
 | 
| 
jbe/bsw@0
 | 
   841       end
 | 
| 
jbe/bsw@0
 | 
   842       if #ids == 0 then
 | 
| 
jbe/bsw@0
 | 
   843         return model:new_selector():empty_list_mode()
 | 
| 
jbe/bsw@0
 | 
   844       end
 | 
| 
jbe/bsw@0
 | 
   845       local selector = model:new_selector()
 | 
| 
jbe/bsw@0
 | 
   846       if connected_by_table then
 | 
| 
jbe/bsw@0
 | 
   847         selector:join(
 | 
| 
jbe/bsw@0
 | 
   848           connected_by_table,
 | 
| 
jbe/bsw@0
 | 
   849           nil,
 | 
| 
jbe/bsw@0
 | 
   850           {
 | 
| 
jbe/bsw@0
 | 
   851             '$."$" = $."$"',
 | 
| 
jbe/bsw@0
 | 
   852             {connected_by_table},
 | 
| 
jbe/bsw@0
 | 
   853             {connected_by_that_key},
 | 
| 
jbe/bsw@0
 | 
   854             {model:get_qualified_table()},
 | 
| 
jbe/bsw@0
 | 
   855             {that_key}
 | 
| 
jbe/bsw@0
 | 
   856           }
 | 
| 
jbe/bsw@0
 | 
   857         )
 | 
| 
jbe/bsw@0
 | 
   858         selector:add_field(
 | 
| 
jbe/bsw@0
 | 
   859           {
 | 
| 
jbe/bsw@0
 | 
   860             '$."$"',
 | 
| 
jbe/bsw@0
 | 
   861             {connected_by_table},
 | 
| 
jbe/bsw@0
 | 
   862             {connected_by_this_key}
 | 
| 
jbe/bsw@0
 | 
   863           },
 | 
| 
jbe/bsw@0
 | 
   864           'mm_ref_'
 | 
| 
jbe/bsw@0
 | 
   865         )
 | 
| 
jbe/bsw@0
 | 
   866         selector:add_where{
 | 
| 
jbe/bsw@0
 | 
   867           '$."$" IN ($)',
 | 
| 
jbe/bsw@0
 | 
   868           {connected_by_table},
 | 
| 
jbe/bsw@0
 | 
   869           {connected_by_this_key},
 | 
| 
jbe/bsw@0
 | 
   870           ids
 | 
| 
jbe/bsw@0
 | 
   871         }
 | 
| 
jbe/bsw@0
 | 
   872       else
 | 
| 
jbe/bsw@6
 | 
   873         selector:add_where{'$."$" IN ($)', {model:get_qualified_table()}, {that_key}, ids}
 | 
| 
jbe/bsw@0
 | 
   874       end
 | 
| 
jbe/bsw@0
 | 
   875       if options.order == nil and default_order then
 | 
| 
jbe/bsw@0
 | 
   876         selector:add_order_by(default_order)
 | 
| 
jbe/bsw@0
 | 
   877       elseif options.order then
 | 
| 
jbe/bsw@0
 | 
   878         selector:add_order_by(options.order)
 | 
| 
jbe/bsw@0
 | 
   879       end
 | 
| 
jbe/bsw@0
 | 
   880       return selector
 | 
| 
jbe/bsw@0
 | 
   881     end
 | 
| 
jbe/bsw@0
 | 
   882   }
 | 
| 
jbe/bsw@0
 | 
   883   if mode == "m1" or mode == "11" then
 | 
| 
jbe/bsw@0
 | 
   884     self.foreign_keys[this_key] = ref
 | 
| 
jbe/bsw@0
 | 
   885   end
 | 
| 
jbe/bsw@0
 | 
   886   return self
 | 
| 
jbe/bsw@0
 | 
   887 end
 |