webmcp

annotate libraries/mondelefant/mondelefant.lua @ 0:9fdfb27f8e67

Version 1.0.0
author jbe/bsw
date Sun Oct 25 12:00:00 2009 +0100 (2009-10-25)
parents
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

Impressum / About Us