webmcp

annotate libraries/mondelefant/mondelefant.lua @ 10:e017c47d43b5

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

Impressum / About Us