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