webmcp
annotate libraries/mondelefant/mondelefant.lua @ 4:5e32ef998acf
Version 1.0.4
ui.link{...} with POST target can now be parameterized with BOTH content and text to allow HTML content for JavaScript browsers and a text-only version for accessiblity
Changes related to database selectors:
- Support for row-based locking
- New method :count(), caching and returning the number of rows, which WOULD have been returned by :exec()
- Bugfix: WHERE and HAVING expressions are now enclosed in parenthesis to avoid problems with operator precedence
ui.script{...} now supports external .js files
Changes in langtool.lua to cope with escaped new-line chars (\n)
ui.link{...} with POST target can now be parameterized with BOTH content and text to allow HTML content for JavaScript browsers and a text-only version for accessiblity
Changes related to database selectors:
- Support for row-based locking
- New method :count(), caching and returning the number of rows, which WOULD have been returned by :exec()
- Bugfix: WHERE and HAVING expressions are now enclosed in parenthesis to avoid problems with operator precedence
ui.script{...} now supports external .js files
Changes in langtool.lua to cope with escaped new-line chars (\n)
| author | jbe/bsw | 
|---|---|
| date | Fri Dec 25 12:00:00 2009 +0100 (2009-12-25) | 
| parents | 9fdfb27f8e67 | 
| children | 5cba83b3f411 | 
| 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@0 | 873 selector:add_where{'"$" IN ($)', {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 |