webmcp
diff 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 |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/libraries/mondelefant/mondelefant.lua Sun Oct 25 12:00:00 2009 +0100 1.3 @@ -0,0 +1,845 @@ 1.4 +#!/usr/bin/env lua 1.5 + 1.6 + 1.7 +--------------------------- 1.8 +-- module initialization -- 1.9 +--------------------------- 1.10 + 1.11 +local _G = _G 1.12 +local _VERSION = _VERSION 1.13 +local assert = assert 1.14 +local collectgarbage = collectgarbage 1.15 +local dofile = dofile 1.16 +local error = error 1.17 +local getfenv = getfenv 1.18 +local getmetatable = getmetatable 1.19 +local ipairs = ipairs 1.20 +local load = load 1.21 +local loadfile = loadfile 1.22 +local loadstring = loadstring 1.23 +local next = next 1.24 +local pairs = pairs 1.25 +local pcall = pcall 1.26 +local print = print 1.27 +local rawequal = rawequal 1.28 +local rawget = rawget 1.29 +local rawset = rawset 1.30 +local select = select 1.31 +local setfenv = setfenv 1.32 +local setmetatable = setmetatable 1.33 +local tonumber = tonumber 1.34 +local tostring = tostring 1.35 +local type = type 1.36 +local unpack = unpack 1.37 +local xpcall = xpcall 1.38 + 1.39 +local coroutine = coroutine 1.40 +local io = io 1.41 +local math = math 1.42 +local os = os 1.43 +local string = string 1.44 +local table = table 1.45 + 1.46 +local add = table.insert 1.47 + 1.48 +_G[...] = require("mondelefant_native") 1.49 +module(...) 1.50 + 1.51 + 1.52 + 1.53 +--------------- 1.54 +-- selectors -- 1.55 +--------------- 1.56 + 1.57 +selector_metatable = {} 1.58 +selector_prototype = {} 1.59 +selector_metatable.__index = selector_prototype 1.60 + 1.61 +local function init_selector(self, db_conn) 1.62 + self._db_conn = db_conn 1.63 + self._mode = "list" 1.64 + self._fields = { sep = ", " } 1.65 + self._distinct = false 1.66 + self._distinct_on = {sep = ", ", expression} 1.67 + self._from = { sep = " " } 1.68 + self._where = { sep = " AND " } 1.69 + self._group_by = { sep = ", " } 1.70 + self._having = { sep = " AND " } 1.71 + self._combine = { sep = " " } 1.72 + self._order_by = { sep = ", " } 1.73 + self._limit = nil 1.74 + self._offset = nil 1.75 + --[[ 1.76 + self._lock = nil 1.77 + self._lock_tables = { sep = ", " } 1.78 + --]] 1.79 + self._class = nil 1.80 + self._attach = nil 1.81 + return self 1.82 +end 1.83 + 1.84 +function connection_prototype:new_selector() 1.85 + return init_selector(setmetatable({}, selector_metatable), self) 1.86 +end 1.87 + 1.88 +function selector_prototype:get_db_conn() 1.89 + return self._db_conn 1.90 +end 1.91 + 1.92 +-- TODO: selector clone? 1.93 + 1.94 +function selector_prototype:single_object_mode() 1.95 + self._mode = "object" 1.96 + return self 1.97 +end 1.98 + 1.99 +function selector_prototype:optional_object_mode() 1.100 + self._mode = "opt_object" 1.101 + return self 1.102 +end 1.103 + 1.104 +function selector_prototype:empty_list_mode() 1.105 + self._mode = "empty_list" 1.106 + return self 1.107 +end 1.108 + 1.109 +function selector_prototype:add_distinct_on(expression) 1.110 + if self._distinct then 1.111 + error("Can not combine DISTINCT with DISTINCT ON.") 1.112 + end 1.113 + add(self._distinct_on, expression) 1.114 + return self 1.115 +end 1.116 + 1.117 +function selector_prototype:set_distinct() 1.118 + if #self._distinct_on > 0 then 1.119 + error("Can not combine DISTINCT with DISTINCT ON.") 1.120 + end 1.121 + self._distinct = true 1.122 + return self 1.123 +end 1.124 + 1.125 +function selector_prototype:add_from(expression, alias, condition) 1.126 + local first = (#self._from == 0) 1.127 + if not first then 1.128 + if condition then 1.129 + add(self._from, "INNER JOIN") 1.130 + else 1.131 + add(self._from, "CROSS JOIN") 1.132 + end 1.133 + end 1.134 + if getmetatable(expression) == selector_metatable then 1.135 + if alias then 1.136 + add(self._from, {'($) AS "$"', {expression}, {alias}}) 1.137 + else 1.138 + add(self._from, {'($) AS "subquery"', {expression}}) 1.139 + end 1.140 + else 1.141 + if alias then 1.142 + add(self._from, {'$ AS "$"', {expression}, {alias}}) 1.143 + else 1.144 + add(self._from, expression) 1.145 + end 1.146 + end 1.147 + if condition then 1.148 + if first then 1.149 + self:condition(condition) 1.150 + else 1.151 + add(self._from, "ON") 1.152 + add(self._from, condition) 1.153 + end 1.154 + end 1.155 + return self 1.156 +end 1.157 + 1.158 +function selector_prototype:add_where(expression) 1.159 + add(self._where, expression) 1.160 + return self 1.161 +end 1.162 + 1.163 +function selector_prototype:add_group_by(expression) 1.164 + add(self._group_by, expression) 1.165 + return self 1.166 +end 1.167 + 1.168 +function selector_prototype:add_having(expression) 1.169 + add(self._having, expression) 1.170 + return self 1.171 +end 1.172 + 1.173 +function selector_prototype:add_combine(expression) 1.174 + add(self._combine, expression) 1.175 + return self 1.176 +end 1.177 + 1.178 +function selector_prototype:add_order_by(expression) 1.179 + add(self._order_by, expression) 1.180 + return self 1.181 +end 1.182 + 1.183 +function selector_prototype:limit(count) 1.184 + if type(count) ~= "number" or count % 1 ~= 0 then 1.185 + error("LIMIT must be an integer.") 1.186 + end 1.187 + self._limit = count 1.188 + return self 1.189 +end 1.190 + 1.191 +function selector_prototype:offset(count) 1.192 + if type(count) ~= "number" or count % 1 ~= 0 then 1.193 + error("OFFSET must be an integer.") 1.194 + end 1.195 + self._offset = count 1.196 + return self 1.197 +end 1.198 + 1.199 +function selector_prototype:reset_fields() 1.200 + for idx in ipairs(self._fields) do 1.201 + self._fields[idx] = nil 1.202 + end 1.203 + return self 1.204 +end 1.205 + 1.206 +function selector_prototype:add_field(expression, alias, options) 1.207 + if alias then 1.208 + add(self._fields, {'$ AS "$"', {expression}, {alias}}) 1.209 + else 1.210 + add(self._fields, expression) 1.211 + end 1.212 + if options then 1.213 + for i, option in ipairs(options) do 1.214 + if option == "distinct" then 1.215 + if alias then 1.216 + self:add_distinct_on('"' .. alias .. '"') 1.217 + else 1.218 + self:add_distinct_on(expression) 1.219 + end 1.220 + elseif option == "grouped" then 1.221 + if alias then 1.222 + self:add_group_by('"' .. alias .. '"') 1.223 + else 1.224 + self:add_group_by(expression) 1.225 + end 1.226 + else 1.227 + error("Unknown option '" .. option .. "' to add_field method.") 1.228 + end 1.229 + end 1.230 + end 1.231 + return self 1.232 +end 1.233 + 1.234 +function selector_prototype:join(...) -- NOTE: alias for add_from 1.235 + return self:add_from(...) 1.236 +end 1.237 + 1.238 +function selector_prototype:from(expression, alias, condition) 1.239 + if #self._from > 0 then 1.240 + error("From-clause already existing (hint: try join).") 1.241 + end 1.242 + return self:join(expression, alias, condition) 1.243 +end 1.244 + 1.245 +function selector_prototype:left_join(expression, alias, condition) 1.246 + local first = (#self._from == 0) 1.247 + if not first then 1.248 + add(self._from, "LEFT OUTER JOIN") 1.249 + end 1.250 + if alias then 1.251 + add(self._from, {'$ AS "$"', {expression}, {alias}}) 1.252 + else 1.253 + add(self._from, expression) 1.254 + end 1.255 + if condition then 1.256 + if first then 1.257 + self:condition(condition) 1.258 + else 1.259 + add(self._from, "ON") 1.260 + add(self._from, condition) 1.261 + end 1.262 + end 1.263 + return self 1.264 +end 1.265 + 1.266 +function selector_prototype:union(expression) 1.267 + self:add_combine{"UNION $", {expression}} 1.268 + return self 1.269 +end 1.270 + 1.271 +function selector_prototype:union_all(expression) 1.272 + self:add_combine{"UNION ALL $", {expression}} 1.273 + return self 1.274 +end 1.275 + 1.276 +function selector_prototype:intersect(expression) 1.277 + self:add_combine{"INTERSECT $", {expression}} 1.278 + return self 1.279 +end 1.280 + 1.281 +function selector_prototype:intersect_all(expression) 1.282 + self:add_combine{"INTERSECT ALL $", {expression}} 1.283 + return self 1.284 +end 1.285 + 1.286 +function selector_prototype:except(expression) 1.287 + self:add_combine{"EXCEPT $", {expression}} 1.288 + return self 1.289 +end 1.290 + 1.291 +function selector_prototype:except_all(expression) 1.292 + self:add_combine{"EXCEPT ALL $", {expression}} 1.293 + return self 1.294 +end 1.295 + 1.296 +function selector_prototype:set_class(class) 1.297 + self._class = class 1.298 + return self 1.299 +end 1.300 + 1.301 +function selector_prototype:attach(mode, data2, field1, field2, ref1, ref2) 1.302 + self._attach = { 1.303 + mode = mode, 1.304 + data2 = data2, 1.305 + field1 = field1, 1.306 + field2 = field2, 1.307 + ref1 = ref1, 1.308 + ref2 = ref2 1.309 + } 1.310 + return self 1.311 +end 1.312 + 1.313 +-- TODO: many-to-many relations 1.314 + 1.315 +function selector_metatable:__tostring() 1.316 + local parts = {sep = " "} 1.317 + add(parts, "SELECT") 1.318 + if self._distinct then 1.319 + add(parts, "DISTINCT") 1.320 + elseif #self._distinct_on > 0 then 1.321 + add(parts, {"DISTINCT ON ($)", self._distinct_on}) 1.322 + end 1.323 + add(parts, {"$", self._fields}) 1.324 + if #self._from > 0 then 1.325 + add(parts, {"FROM $", self._from}) 1.326 + end 1.327 + if #self._mode == "empty_list" then 1.328 + add(parts, "WHERE FALSE") 1.329 + elseif #self._where > 0 then 1.330 + add(parts, {"WHERE $", self._where}) 1.331 + end 1.332 + if #self._group_by > 0 then 1.333 + add(parts, {"GROUP BY $", self._group_by}) 1.334 + end 1.335 + if #self._having > 0 then 1.336 + add(parts, {"HAVING $", self._having}) 1.337 + end 1.338 + for i, v in ipairs(self._combine) do 1.339 + add(parts, v) 1.340 + end 1.341 + if #self._order_by > 0 then 1.342 + add(parts, {"ORDER BY $", self._order_by}) 1.343 + end 1.344 + if self._mode == "empty_list" then 1.345 + add(parts, "LIMIT 0") 1.346 + elseif self._mode ~= "list" then 1.347 + add(parts, "LIMIT 1") 1.348 + elseif self._limit then 1.349 + add(parts, "LIMIT " .. self._limit) 1.350 + end 1.351 + if self._offset then 1.352 + add(parts, "OFFSET " .. self._offset) 1.353 + end 1.354 + return self._db_conn:assemble_command{"$", parts} 1.355 +end 1.356 + 1.357 +function selector_prototype:try_exec() 1.358 + if self._mode == "empty_list" then 1.359 + if self._class then 1.360 + return nil, self._class:create_list() 1.361 + else 1.362 + return nil, self._db_conn:create_list() 1.363 + end 1.364 + end 1.365 + local db_error, db_result = self._db_conn:try_query(self, self._mode) 1.366 + if db_error then 1.367 + return db_error 1.368 + elseif db_result then 1.369 + if self._class then set_class(db_result, self._class) end 1.370 + if self._attach then 1.371 + attach( 1.372 + self._attach.mode, 1.373 + db_result, 1.374 + self._attach.data2, 1.375 + self._attach.field1, 1.376 + self._attach.field2, 1.377 + self._attach.ref1, 1.378 + self._attach.ref2 1.379 + ) 1.380 + end 1.381 + return nil, db_result 1.382 + else 1.383 + return nil 1.384 + end 1.385 +end 1.386 + 1.387 +function selector_prototype:exec() 1.388 + local db_error, result = self:try_exec() 1.389 + if db_error then 1.390 + db_error:escalate() 1.391 + else 1.392 + return result 1.393 + end 1.394 +end 1.395 + 1.396 + 1.397 + 1.398 +----------------- 1.399 +-- attachments -- 1.400 +----------------- 1.401 + 1.402 +local function attach_key(row, fields) 1.403 + local t = type(fields) 1.404 + if t == "string" then 1.405 + return tostring(row[fields]) 1.406 + elseif t == "table" then 1.407 + local r = {} 1.408 + for idx, field in ipairs(fields) do 1.409 + r[idx] = string.format("%q", row[field]) 1.410 + end 1.411 + return table.concat(r) 1.412 + else 1.413 + error("Field information for 'mondelefant.attach' is neither a string nor a table.") 1.414 + end 1.415 +end 1.416 + 1.417 +function attach(mode, data1, data2, key1, key2, ref1, ref2) 1.418 + local many1, many2 1.419 + if mode == "11" then 1.420 + many1 = false 1.421 + many2 = false 1.422 + elseif mode == "1m" then 1.423 + many1 = false 1.424 + many2 = true 1.425 + elseif mode == "m1" then 1.426 + many1 = true 1.427 + many2 = false 1.428 + elseif mode == "mm" then 1.429 + many1 = true 1.430 + many2 = true 1.431 + else 1.432 + error("Unknown mode specified for 'mondelefant.attach'.") 1.433 + end 1.434 + local list1, list2 1.435 + if data1._type == "object" then 1.436 + list1 = { data1 } 1.437 + elseif data1._type == "list" then 1.438 + list1 = data1 1.439 + else 1.440 + error("First result data given to 'mondelefant.attach' is invalid.") 1.441 + end 1.442 + if data2._type == "object" then 1.443 + list2 = { data2 } 1.444 + elseif data2._type == "list" then 1.445 + list2 = data2 1.446 + else 1.447 + error("Second result data given to 'mondelefant.attach' is invalid.") 1.448 + end 1.449 + local hash1 = {} 1.450 + local hash2 = {} 1.451 + if ref2 then 1.452 + for i, row in ipairs(list1) do 1.453 + local key = attach_key(row, key1) 1.454 + local list = hash1[key] 1.455 + if not list then list = {}; hash1[key] = list end 1.456 + list[#list + 1] = row 1.457 + end 1.458 + end 1.459 + if ref1 then 1.460 + for i, row in ipairs(list2) do 1.461 + local key = attach_key(row, key2) 1.462 + local list = hash2[key] 1.463 + if not list then list = {}; hash2[key] = list end 1.464 + list[#list + 1] = row 1.465 + end 1.466 + for i, row in ipairs(list1) do 1.467 + local key = attach_key(row, key1) 1.468 + local matching_rows = hash2[key] 1.469 + if many2 then 1.470 + local list = data2._connection:create_list(matching_rows) 1.471 + list._class = data2._class 1.472 + row._ref[ref1] = list 1.473 + elseif matching_rows and #matching_rows == 1 then 1.474 + row._ref[ref1] = matching_rows[1] 1.475 + else 1.476 + row._ref[ref1] = false 1.477 + end 1.478 + end 1.479 + end 1.480 + if ref2 then 1.481 + for i, row in ipairs(list2) do 1.482 + local key = attach_key(row, key2) 1.483 + local matching_rows = hash1[key] 1.484 + if many1 then 1.485 + local list = data1._connection:create_list(matching_rows) 1.486 + list._class = data1._class 1.487 + row._ref[ref2] = list 1.488 + elseif matching_rows and #matching_rows == 1 then 1.489 + row._ref[ref2] = matching_rows[1] 1.490 + else 1.491 + row._ref[ref2] = false 1.492 + end 1.493 + end 1.494 + end 1.495 +end 1.496 + 1.497 + 1.498 + 1.499 +------------------ 1.500 +-- model system -- 1.501 +------------------ 1.502 + 1.503 +class_prototype.primary_key = "id" 1.504 + 1.505 +function class_prototype:get_db_conn() 1.506 + error( 1.507 + "Method mondelefant class(_prototype):get_db_conn() " .. 1.508 + "has to be implemented." 1.509 + ) 1.510 +end 1.511 + 1.512 +function class_prototype:get_qualified_table() 1.513 + if not self.table then error "Table unknown." end 1.514 + if self.schema then 1.515 + return '"' .. self.schema .. '"."' .. self.table .. '"' 1.516 + else 1.517 + return '"' .. self.table .. '"' 1.518 + end 1.519 +end 1.520 + 1.521 +function class_prototype:get_qualified_table_literal() 1.522 + if not self.table then error "Table unknown." end 1.523 + if self.schema then 1.524 + return self.schema .. '.' .. self.table 1.525 + else 1.526 + return self.table 1.527 + end 1.528 +end 1.529 + 1.530 +function class_prototype:get_primary_key_list() 1.531 + local primary_key = self.primary_key 1.532 + if type(primary_key) == "string" then 1.533 + return {primary_key} 1.534 + else 1.535 + return primary_key 1.536 + end 1.537 +end 1.538 + 1.539 +function class_prototype:get_columns() 1.540 + if self._columns then 1.541 + return self._columns 1.542 + end 1.543 + local selector = self:get_db_conn():new_selector() 1.544 + selector:set_class(self) 1.545 + selector:from(self:get_qualified_table()) 1.546 + selector:add_field("*") 1.547 + selector:add_where("FALSE") 1.548 + local db_result = selector:exec() 1.549 + local connection = db_result._connection 1.550 + local columns = {} 1.551 + for idx, info in ipairs(db_result._column_info) do 1.552 + local key = info.field_name 1.553 + local value = { 1.554 + name = key, 1.555 + type = connection.type_mappings[info.type] 1.556 + } 1.557 + columns[key] = value 1.558 + table.insert(columns, value) 1.559 + end 1.560 + self._columns = columns 1.561 + return columns 1.562 +end 1.563 + 1.564 +function class_prototype:new_selector(db_conn) 1.565 + local selector = (db_conn or self:get_db_conn()):new_selector() 1.566 + selector:set_class(self) 1.567 + selector:from(self:get_qualified_table()) 1.568 + selector:add_field(self:get_qualified_table() .. ".*") 1.569 + return selector 1.570 +end 1.571 + 1.572 +function class_prototype:create_list() 1.573 + local list = self:get_db_conn():create_list() 1.574 + list._class = self 1.575 + return list 1.576 +end 1.577 + 1.578 +function class_prototype:new() 1.579 + local object = self:get_db_conn():create_object() 1.580 + object._class = self 1.581 + object._new = true 1.582 + return object 1.583 +end 1.584 + 1.585 +function class_prototype.object:try_save() 1.586 + if not self._class then 1.587 + error("Cannot save object: No class information available.") 1.588 + end 1.589 + local primary_key = self._class:get_primary_key_list() 1.590 + local primary_key_sql = { sep = ", " } 1.591 + for idx, value in ipairs(primary_key) do 1.592 + primary_key_sql[idx] = '"' .. value .. '"' 1.593 + end 1.594 + if self._new then 1.595 + local fields = {sep = ", "} 1.596 + local values = {sep = ", "} 1.597 + for key, dummy in pairs(self._dirty or {}) do 1.598 + add(fields, {'"$"', {key}}) 1.599 + add(values, {'?', self[key]}) 1.600 + end 1.601 + if compat_returning then -- compatibility for PostgreSQL 8.1 1.602 + local db_error, db_result1, db_result2 = self._connection:try_query( 1.603 + { 1.604 + 'INSERT INTO $ ($) VALUES ($)', 1.605 + {self._class:get_qualified_table()}, 1.606 + fields, 1.607 + values, 1.608 + primary_key_sql 1.609 + }, 1.610 + "list", 1.611 + { 1.612 + 'SELECT currval(?)', 1.613 + self._class.table .. '_id_seq' 1.614 + }, 1.615 + "object" 1.616 + ) 1.617 + if db_error then 1.618 + return db_error 1.619 + end 1.620 + self.id = db_result2.id 1.621 + else 1.622 + local db_error, db_result = self._connection:try_query( 1.623 + { 1.624 + 'INSERT INTO $ ($) VALUES ($) RETURNING ($)', 1.625 + {self._class:get_qualified_table()}, 1.626 + fields, 1.627 + values, 1.628 + primary_key_sql 1.629 + }, 1.630 + "object" 1.631 + ) 1.632 + if db_error then 1.633 + return db_error 1.634 + end 1.635 + for idx, value in ipairs(primary_key) do 1.636 + self[value] = db_result[value] 1.637 + end 1.638 + end 1.639 + self._new = false 1.640 + else 1.641 + local command_sets = {sep = ", "} 1.642 + for key, dummy in pairs(self._dirty or {}) do 1.643 + add(command_sets, {'"$" = ?', {key}, self[key]}) 1.644 + end 1.645 + if #command_sets >= 1 then 1.646 + local primary_key_compare = {sep = " AND "} 1.647 + for idx, value in ipairs(primary_key) do 1.648 + primary_key_compare[idx] = { 1.649 + "$ = ?", 1.650 + {'"' .. value .. '"'}, 1.651 + self[value] 1.652 + } 1.653 + end 1.654 + local db_error = self._connection:try_query{ 1.655 + 'UPDATE $ SET $ WHERE $', 1.656 + {self._class:get_qualified_table()}, 1.657 + command_sets, 1.658 + primary_key_compare 1.659 + } 1.660 + if db_error then 1.661 + return db_error 1.662 + end 1.663 + end 1.664 + end 1.665 + return nil 1.666 +end 1.667 + 1.668 +function class_prototype.object:save() 1.669 + local db_error = self:try_save() 1.670 + if db_error then 1.671 + db_error:escalate() 1.672 + end 1.673 + return self 1.674 +end 1.675 + 1.676 +function class_prototype.object:try_destroy() 1.677 + if not self._class then 1.678 + error("Cannot destroy object: No class information available.") 1.679 + end 1.680 + local primary_key = self._class:get_primary_key_list() 1.681 + local primary_key_compare = {sep = " AND "} 1.682 + for idx, value in ipairs(primary_key) do 1.683 + primary_key_compare[idx] = { 1.684 + "$ = ?", 1.685 + {'"' .. value .. '"'}, 1.686 + self[value] 1.687 + } 1.688 + end 1.689 + return self._connection:try_query{ 1.690 + 'DELETE FROM $ WHERE $', 1.691 + {self._class:get_qualified_table()}, 1.692 + primary_key_compare 1.693 + } 1.694 +end 1.695 + 1.696 +function class_prototype.object:destroy() 1.697 + local db_error = self:try_destroy() 1.698 + if db_error then 1.699 + db_error:escalate() 1.700 + end 1.701 + return self 1.702 +end 1.703 + 1.704 +function class_prototype.list:get_reference_selector( 1.705 + ref_name, options, ref_alias, back_ref_alias 1.706 +) 1.707 + local ref_info = self._class.references[ref_name] 1.708 + if not ref_info then 1.709 + error('Reference with name "' .. ref_name .. '" not found.') 1.710 + end 1.711 + local selector = ref_info.selector_generator(self, options or {}) 1.712 + local mode = ref_info.mode 1.713 + if mode == "mm" or mode == "1m" then 1.714 + mode = "m1" 1.715 + elseif mode == "m1" then 1.716 + mode = "1m" 1.717 + end 1.718 + local ref_alias = ref_alias 1.719 + if ref_alias == false then 1.720 + ref_alias = nil 1.721 + elseif ref_alias == nil then 1.722 + ref_alias = ref_name 1.723 + end 1.724 + local back_ref_alias 1.725 + if back_ref_alias == false then 1.726 + back_ref_alias = nil 1.727 + elseif back_ref_alias == nil then 1.728 + back_ref_alias = ref_info.back_ref 1.729 + end 1.730 + selector:attach( 1.731 + mode, 1.732 + self, 1.733 + ref_info.that_key, ref_info.this_key, 1.734 + back_ref_alias or ref_info.back_ref, ref_alias or ref_name 1.735 + ) 1.736 + return selector 1.737 +end 1.738 + 1.739 +function class_prototype.list.load(...) 1.740 + return class_prototype.list.get_reference_selector(...):exec() 1.741 +end 1.742 + 1.743 +function class_prototype.object:get_reference_selector(...) 1.744 + local list = self._class:create_list() 1.745 + list[1] = self 1.746 + return list:get_reference_selector(...) 1.747 +end 1.748 + 1.749 +function class_prototype.object.load(...) 1.750 + return class_prototype.object.get_reference_selector(...):exec() 1.751 +end 1.752 + 1.753 + 1.754 +function class_prototype:add_reference(args) 1.755 + local selector_generator = args.selector_generator 1.756 + local mode = args.mode 1.757 + local to = args.to 1.758 + local this_key = args.this_key 1.759 + local that_key = args.that_key 1.760 + local connected_by_table = args.connected_by_table -- TODO: split to table and schema 1.761 + local connected_by_this_key = args.connected_by_this_key 1.762 + local connected_by_that_key = args.connected_by_that_key 1.763 + local ref = args.ref 1.764 + local back_ref = args.back_ref 1.765 + local default_order = args.default_order 1.766 + local model 1.767 + local function get_model() 1.768 + if not model then 1.769 + if type(to) == "string" then 1.770 + model = _G 1.771 + for path_element in string.gmatch(to, "[^.]+") do 1.772 + model = model[path_element] 1.773 + end 1.774 + elseif type(to) == "function" then 1.775 + model = to() 1.776 + else 1.777 + model = to 1.778 + end 1.779 + end 1.780 + if not model or model == _G then 1.781 + error("Could not get model for reference.") 1.782 + end 1.783 + return model 1.784 + end 1.785 + self.references[ref] = { 1.786 + mode = mode, 1.787 + this_key = this_key, 1.788 + that_key = connected_by_table and "mm_ref_" or that_key, 1.789 + ref = ref, 1.790 + back_ref = back_ref, 1.791 + selector_generator = selector_generator or function(list, options) 1.792 + -- TODO: support tuple keys 1.793 + local options = options or {} 1.794 + local model = get_model() 1.795 + -- TODO: too many records cause PostgreSQL command stack overflow 1.796 + local ids = { sep = ", " } 1.797 + for i, object in ipairs(list) do 1.798 + local id = object[this_key] 1.799 + if id ~= nil then 1.800 + ids[#ids+1] = {"?", id} 1.801 + end 1.802 + end 1.803 + if #ids == 0 then 1.804 + return model:new_selector():empty_list_mode() 1.805 + end 1.806 + local selector = model:new_selector() 1.807 + if connected_by_table then 1.808 + selector:join( 1.809 + connected_by_table, 1.810 + nil, 1.811 + { 1.812 + '$."$" = $."$"', 1.813 + {connected_by_table}, 1.814 + {connected_by_that_key}, 1.815 + {model:get_qualified_table()}, 1.816 + {that_key} 1.817 + } 1.818 + ) 1.819 + selector:add_field( 1.820 + { 1.821 + '$."$"', 1.822 + {connected_by_table}, 1.823 + {connected_by_this_key} 1.824 + }, 1.825 + 'mm_ref_' 1.826 + ) 1.827 + selector:add_where{ 1.828 + '$."$" IN ($)', 1.829 + {connected_by_table}, 1.830 + {connected_by_this_key}, 1.831 + ids 1.832 + } 1.833 + else 1.834 + selector:add_where{'"$" IN ($)', {that_key}, ids} 1.835 + end 1.836 + if options.order == nil and default_order then 1.837 + selector:add_order_by(default_order) 1.838 + elseif options.order then 1.839 + selector:add_order_by(options.order) 1.840 + end 1.841 + return selector 1.842 + end 1.843 + } 1.844 + if mode == "m1" or mode == "11" then 1.845 + self.foreign_keys[this_key] = ref 1.846 + end 1.847 + return self 1.848 +end