webmcp
annotate libraries/mondelefant/mondelefant.lua @ 1:985024b16520
Version 1.0.1
New feature: JSON requests
Changes in ui.paginate: Current page setting is directly fetched from CGI params, instead of view params
Changed behavior of load methods of atom library to accept nil as input
Bugfixes in mondelefant_atom_connector timestamp(tz) loaders
Added global constant _WEBMCP_VERSION containing a version string
New feature: JSON requests
Changes in ui.paginate: Current page setting is directly fetched from CGI params, instead of view params
Changed behavior of load methods of atom library to accept nil as input
Bugfixes in mondelefant_atom_connector timestamp(tz) loaders
Added global constant _WEBMCP_VERSION containing a version string
author | jbe |
---|---|
date | Tue Nov 17 12:00:00 2009 +0100 (2009-11-17) |
parents | 9fdfb27f8e67 |
children | 5e32ef998acf |
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 |