# HG changeset patch # User jbe # Date 1275670834 -7200 # Node ID 3a6fe8663b26f247f003d46803ee447a3278731e # Parent c1f3eb9713a4d73e1a6d6c704a6c271c05876f65 Code cleanup and documentation added; Year in copyright notice changed to 2009-2010 Details: - Changed quoting style in auth.openid.xrds_document{...} - Fixed documentation for auth.openid.initiate{...} - Added documentation for mondelefant - Code-cleanup in mondelefant: -- removed unneccessary lines "rows = PQntuples(res); cols = PQnfields(res);" -- avoided extra copy of first argument (self) in mondelefant_conn_query -- no rawget in meta-method "__index" of database result lists and objects -- removed unreachable "return 0;" in meta-method "__newindex" of database result lists and objects - Year in copyright notice changed to 2009-2010 - Version string changed to "1.1.1" diff -r c1f3eb9713a4 -r 3a6fe8663b26 LICENSE --- a/LICENSE Thu Apr 22 20:46:29 2010 +0200 +++ b/LICENSE Fri Jun 04 19:00:34 2010 +0200 @@ -1,4 +1,4 @@ -Copyright (c) 2009 Public Software Group e. V., Berlin, Germany +Copyright (c) 2009-2010 Public Software Group e. V., Berlin, Germany Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff -r c1f3eb9713a4 -r 3a6fe8663b26 doc/autodoc-footer.htmlpart --- a/doc/autodoc-footer.htmlpart Thu Apr 22 20:46:29 2010 +0200 +++ b/doc/autodoc-footer.htmlpart Fri Jun 04 19:00:34 2010 +0200 @@ -1,7 +1,7 @@

- Copyright (c) 2009 Public Software Group e. V., Berlin + Copyright (c) 2009-2010 Public Software Group e. V., Berlin

diff -r c1f3eb9713a4 -r 3a6fe8663b26 doc/autodoc-header.htmlpart --- a/doc/autodoc-header.htmlpart Thu Apr 22 20:46:29 2010 +0200 +++ b/doc/autodoc-header.htmlpart Fri Jun 04 19:00:34 2010 +0200 @@ -55,10 +55,10 @@ color: #505050; } - WebMCP 1.1.0 Documentation + WebMCP 1.1.1 Documentation -

WebMCP 1.1.0 Documentation

+

WebMCP 1.1.1 Documentation

WebMCP is a completely new web development framework, and has not been extensively tested yet. The API might change at any time, but in future releases there will be a list of all changes, which break downward compatibility.

diff -r c1f3eb9713a4 -r 3a6fe8663b26 framework/cgi-bin/webmcp.lua --- a/framework/cgi-bin/webmcp.lua Thu Apr 22 20:46:29 2010 +0200 +++ b/framework/cgi-bin/webmcp.lua Fri Jun 04 19:00:34 2010 +0200 @@ -1,6 +1,6 @@ #!/usr/bin/env lua -_WEBMCP_VERSION = "1.1.0" +_WEBMCP_VERSION = "1.1.1" -- include "../lib/" in search path for libraries do diff -r c1f3eb9713a4 -r 3a6fe8663b26 framework/env/auth/openid/initiate.lua --- a/framework/env/auth/openid/initiate.lua Thu Apr 22 20:46:29 2010 +0200 +++ b/framework/env/auth/openid/initiate.lua Fri Jun 04 19:00:34 2010 +0200 @@ -1,6 +1,7 @@ --[[-- success, -- boolean indicating success or failure -errmsg = -- error message in case of failure (TODO: not implemented yet) +errmsg, -- error message in case of failure +errcode = -- error code in case of failure (TODO: not implemented yet) auth.openid.initiate{ user_supplied_identifier = user_supplied_identifier, -- string given by user https_as_default = https_as_default, -- default to https diff -r c1f3eb9713a4 -r 3a6fe8663b26 framework/env/auth/openid/xrds_document.lua --- a/framework/env/auth/openid/xrds_document.lua Thu Apr 22 20:46:29 2010 +0200 +++ b/framework/env/auth/openid/xrds_document.lua Fri Jun 04 19:00:34 2010 +0200 @@ -13,20 +13,20 @@ function auth.openid.xrds_document(args) slot.set_layout(nil, "application/xrds+xml") slot.put_into("data", - "\n", - "\n", - " \n", - " \n", - " http://specs.openid.net/auth/2.0/return_to\n", - " ", + '\n', + '\n', + ' \n', + ' \n', + ' http://specs.openid.net/auth/2.0/return_to\n', + ' ', encode.url{ base = request.get_absolute_baseurl(), module = args.return_to_module, view = args.return_to_view }, - "\n", - " \n", - " \n", - "\n" + '\n', + ' \n', + ' \n', + '\n' ) end diff -r c1f3eb9713a4 -r 3a6fe8663b26 libraries/mondelefant/mondelefant.lua --- a/libraries/mondelefant/mondelefant.lua Thu Apr 22 20:46:29 2010 +0200 +++ b/libraries/mondelefant/mondelefant.lua Fri Jun 04 19:00:34 2010 +0200 @@ -76,31 +76,79 @@ return self end +--[[-- +selector = -- new selector +:new_selector() + +Creates a new selector to operate on the given database handle. +--]]-- function connection_prototype:new_selector() return init_selector(setmetatable({}, selector_metatable), self) end +--//-- +--[[-- +db_handle = -- handle of database connection +:get_db_conn() + +Returns the database connection handle used by a selector. + +--]]-- function selector_prototype:get_db_conn() return self._db_conn end +--//-- -- TODO: selector clone? +--[[-- +db_selector = -- same selector returned +:single_object_mode() + +Sets selector to single object mode (mode "object" passed to "query" method of database handle). The selector is modified and returned. + +--]]-- function selector_prototype:single_object_mode() self._mode = "object" return self end +--//-- +--[[-- +db_selector = -- same selector returned +:optional_object_mode() + +Sets selector to single object mode (mode "opt_object" passed to "query" method of database handle). The selector is modified and returned. + +--]]-- function selector_prototype:optional_object_mode() self._mode = "opt_object" return self end +--//-- +--[[-- +db_selector = -- same selector returned +:empty_list_mode() + +Sets selector to empty list mode. The selector is modified and returned. When using the selector, no SQL query will be issued, but instead an empty database result list is returned. + +--]]-- function selector_prototype:empty_list_mode() self._mode = "empty_list" return self end +--//-- +--[[-- +db_selector = -- same selector returned +:add_distinct_on( + expression -- expression as passed to "assemble_command" +) + +Adds an DISTINCT ON expression to the selector. The selector is modified and returned. + +--]]-- function selector_prototype:add_distinct_on(expression) if self._distinct then error("Can not combine DISTINCT with DISTINCT ON.") @@ -108,7 +156,15 @@ add(self._distinct_on, expression) return self end +--//-- +--[[-- +db_selector = -- same selector returned +:set_distinct() + +Sets selector to perform a SELECT DISTINCT instead of SELECT (ALL). The selector is modified and returned. This mode can not be combined with DISTINCT ON. + +--]]-- function selector_prototype:set_distinct() if #self._distinct_on > 0 then error("Can not combine DISTINCT with DISTINCT ON.") @@ -116,7 +172,21 @@ self._distinct = true return self end +--//-- +--[[-- +db_selector = -- same selector returned +:add_from( + expression, -- expression as passed to "assemble_command" + alias, -- optional alias expression as passed to "assemble_command" + condition -- optional condition expression as passed to "assemble_command" +) + +Adds expressions for FROM clause to the selector. The selector is modified and returned. If an additional condition is given, an INNER JOIN will be used, otherwise a CROSS JOIN. + +This method is identical to "join". + +--]]-- function selector_prototype:add_from(expression, alias, condition) local first = (#self._from == 0) if not first then @@ -149,32 +219,92 @@ end return self end +--//-- +--[[-- +db_selector = -- same selector returned +:add_where( + expression -- expression as passed to "assemble_command" +) + +Adds expressions for WHERE clause to the selector. The selector is modified and returned. Multiple calls cause expressions to be AND-combined. + +--]]-- function selector_prototype:add_where(expression) add(self._where, expression) return self end +--//-- +--[[-- +db_selector = -- same selector returned +:add_group_by( + expression -- expression as passed to "assemble_command" +) + +Adds expressions for GROUP BY clause to the selector. The selector is modified and returned. + +--]]-- function selector_prototype:add_group_by(expression) add(self._group_by, expression) return self end +--//-- +--[[-- +db_selector = -- same selector returned +:add_having( + expression -- expression as passed to "assemble_command" +) + +Adds expressions for HAVING clause to the selector. The selector is modified and returned. Multiple calls cause expressions to be AND-combined. + +--]]-- function selector_prototype:add_having(expression) add(self._having, expression) return self end +--//-- +--[[-- +db_selector = -- same selector returned +:add_combine( + expression -- expression as passed to "assemble_command" +) + +This function is used for UNION/INTERSECT/EXCEPT clauses. It does not need to be called directly. Use "union", "union_all", "intersect", "intersect_all", "except" and "except_all" instead. + +--]]-- function selector_prototype:add_combine(expression) add(self._combine, expression) return self end +--//-- +--[[-- +db_selector = -- same selector returned +:add_order_by( + expression -- expression as passed to "assemble_command" +) + +Adds expressions for ORDER BY clause to the selector. The selector is modified and returned. + +--]]-- function selector_prototype:add_order_by(expression) add(self._order_by, expression) return self end +--//-- +--[[-- +db_selector = -- same selector returned +:limit( + count -- integer used as LIMIT +) + +Limits the number of rows to a given number, by using LIMIT. The selector is modified and returned. + +--]]-- function selector_prototype:limit(count) if type(count) ~= "number" or count % 1 ~= 0 then error("LIMIT must be an integer.") @@ -182,7 +312,17 @@ self._limit = count return self end +--//-- +--[[-- +db_selector = -- same selector returned +:offset( + count -- integer used as OFFSET +) + +Skips a given number of rows, by using OFFSET. The selector is modified and returned. + +--]]-- function selector_prototype:offset(count) if type(count) ~= "number" or count % 1 ~= 0 then error("OFFSET must be an integer.") @@ -190,34 +330,90 @@ self._offset = count return self end +--//-- +--[[-- +db_selector = -- same selector returned +:for_share() + +Adds FOR SHARE to the statement, to share-lock all rows read. The selector is modified and returned. + +--]]-- function selector_prototype:for_share() self._read_lock.all = true return self end +--//-- +--[[-- +db_selector = -- same selector returned +:for_share_of( + expression -- expression as passed to "assemble_command" +) + +Adds FOR SHARE OF to the statement, to share-lock all rows read by the named table(s). The selector is modified and returned. + +--]]-- function selector_prototype:for_share_of(expression) add(self._read_lock, expression) return self end +--//-- +--[[-- +db_selector = -- same selector returned +:for_update() + +Adds FOR UPDATE to the statement, to exclusivly lock all rows read. The selector is modified and returned. + +--]]-- function selector_prototype:for_update() self._write_lock.all = true return self end +--//-- +--[[-- +db_selector = -- same selector returned +:for_update_of( + expression -- expression as passed to "assemble_command" +) + +Adds FOR SHARE OF to the statement, to exclusivly lock all rows read by the named table(s). The selector is modified and returned. + +--]]-- function selector_prototype:for_update_of(expression) add(self._write_lock, expression) return self end +--//-- +--[[-- +db_selector = -- same selector returned +:reset_fields() + +This method removes all fields added by method "add_field". The selector is modified and returned. + +--]]-- function selector_prototype:reset_fields() for idx in ipairs(self._fields) do self._fields[idx] = nil end return self end +--//-- +--[[-- +db_selector = -- same selector returned +:add_field( + expression, -- expression as passed to "assemble_command" + alias, -- optional alias expression as passed to "assemble_command" + option_list -- optional list of options (may contain strings "distinct" or "grouped") +) + +Adds fields to the selector. The selector is modified and returned. The third argument can be a list of options. If option "distinct" is given, then "add_distinct_on" will be executed for the given field or alias. If option "grouped" is given, then "add_group_by" will be executed for the given field or alias. + +--]]-- function selector_prototype:add_field(expression, alias, options) if alias then add(self._fields, {'$ AS "$"', {expression}, {alias}}) @@ -245,18 +441,58 @@ end return self end +--//-- +--[[-- +db_selector = -- same selector returned +:join( + expression, -- expression as passed to "assemble_command" + alias, -- optional alias expression as passed to "assemble_command" + condition -- optional condition expression as passed to "assemble_command" +) + +Adds expressions for FROM clause to the selector. The selector is modified and returned. If an additional condition is given, an INNER JOIN will be used, otherwise a CROSS JOIN. + +This method is identical to "add_from". + +--]]-- function selector_prototype:join(...) -- NOTE: alias for add_from return self:add_from(...) end +--//-- +--[[-- +db_selector = -- same selector returned +:from( + expression, -- expression as passed to "assemble_command" + alias, -- optional alias expression as passed to "assemble_command" + condition -- optional condition expression as passed to "assemble_command" +) + +Adds the first expression for FROM clause to the selector. The selector is modified and returned. If an additional condition is given, an INNER JOIN will be used, otherwise a CROSS JOIN. + +This method is identical to "add_from" or "join", except that an error is thrown, if there is already any FROM expression existent. + +--]]-- function selector_prototype:from(expression, alias, condition) if #self._from > 0 then error("From-clause already existing (hint: try join).") end return self:join(expression, alias, condition) end +--//-- +--[[-- +db_selector = -- same selector returned +:left_join( + expression, -- expression as passed to "assemble_command" + alias, -- optional alias expression as passed to "assemble_command" + condition -- optional condition expression as passed to "assemble_command" +) + +Adds expressions for FROM clause to the selector using a LEFT OUTER JOIN. The selector is modified and returned. + +--]]-- function selector_prototype:left_join(expression, alias, condition) local first = (#self._from == 0) if not first then @@ -277,42 +513,127 @@ end return self end +--//-- +--[[-- +db_selector = -- same selector returned +:union( + expression -- expression or selector without ORDER BY, LIMIT, FOR UPDATE or FOR SHARE +) + +This method adds an UNION clause to the given selector. The selector is modified and returned. The selector (or expression) passed as argument to this function shall not contain any ORDER BY, LIMIT, FOR UPDATE or FOR SHARE clauses. + +--]]-- function selector_prototype:union(expression) self:add_combine{"UNION $", {expression}} return self end +--//-- +--[[-- +db_selector = -- same selector returned +:union_all( + expression -- expression or selector without ORDER BY, LIMIT, FOR UPDATE or FOR SHARE +) + +This method adds an UNION ALL clause to the given selector. The selector is modified and returned. The selector (or expression) passed as argument to this function shall not contain any ORDER BY, LIMIT, FOR UPDATE or FOR SHARE clauses. + +--]]-- function selector_prototype:union_all(expression) self:add_combine{"UNION ALL $", {expression}} return self end +--//-- +--[[-- +db_selector = -- same selector returned +:intersect( + expression -- expression or selector without ORDER BY, LIMIT, FOR UPDATE or FOR SHARE +) + +This method adds an INTERSECT clause to the given selector. The selector is modified and returned. The selector (or expression) passed as argument to this function shall not contain any ORDER BY, LIMIT, FOR UPDATE or FOR SHARE clauses. + +--]]-- function selector_prototype:intersect(expression) self:add_combine{"INTERSECT $", {expression}} return self end +--//-- +--[[-- +db_selector = -- same selector returned +:intersect_all( + expression -- expression or selector without ORDER BY, LIMIT, FOR UPDATE or FOR SHARE +) + +This method adds an INTERSECT ALL clause to the given selector. The selector is modified and returned. The selector (or expression) passed as argument to this function shall not contain any ORDER BY, LIMIT, FOR UPDATE or FOR SHARE clauses. + +--]]-- function selector_prototype:intersect_all(expression) self:add_combine{"INTERSECT ALL $", {expression}} return self end +--//-- +--[[-- +db_selector = -- same selector returned +:except( + expression -- expression or selector without ORDER BY, LIMIT, FOR UPDATE or FOR SHARE +) + +This method adds an EXCEPT clause to the given selector. The selector is modified and returned. The selector (or expression) passed as argument to this function shall not contain any ORDER BY, LIMIT, FOR UPDATE or FOR SHARE clauses. + +--]]-- function selector_prototype:except(expression) self:add_combine{"EXCEPT $", {expression}} return self end +--//-- +--[[-- +db_selector = -- same selector returned +:except_all( + expression -- expression or selector without ORDER BY, LIMIT, FOR UPDATE or FOR SHARE +) + +This method adds an EXCEPT ALL clause to the given selector. The selector is modified and returned. The selector (or expression) passed as argument to this function shall not contain any ORDER BY, LIMIT, FOR UPDATE or FOR SHARE clauses. + +--]]-- function selector_prototype:except_all(expression) self:add_combine{"EXCEPT ALL $", {expression}} return self end +--//-- +--[[-- +db_selector = -- same selector returned +:set_class( + class -- database class (model) +) + +This method makes the selector to return database result lists or objects of the given database class (model). The selector is modified and returned. + +--]]-- function selector_prototype:set_class(class) self._class = class return self end +--//-- +--[[-- +db_selector = -- same selector returned +:attach( + mode, -- attachment type: "11" one to one, "1m" one to many, "m1" many to one + data2, -- other database result list or object, the results of this selector shall be attached with + field1, -- field name(s) in result list or object of this selector used for attaching + field2, -- field name(s) in "data2" used for attaching + ref1, -- name of reference field in the results of this selector after attaching + ref2 -- name of reference field in "data2" after attaching +) + +This method causes database result lists or objects of this selector to be attached with other database result lists after execution. This method does not need to be called directly. + +--]]-- function selector_prototype:attach(mode, data2, field1, field2, ref1, ref2) self._attach = { mode = mode, @@ -324,8 +645,7 @@ } return self end - --- TODO: many-to-many relations +--//-- function selector_metatable:__tostring() local parts = {sep = " "} @@ -381,6 +701,14 @@ return self._db_conn:assemble_command{"$", parts} end +--[[-- +db_error, -- database error object, or nil in case of success +result = -- database result list or object +:try_exec() + +This method executes the selector on its database. First return value is an error object or nil in case of success. Second return value is the result list or object. + +--]]-- function selector_prototype:try_exec() if self._mode == "empty_list" then if self._class then @@ -410,7 +738,15 @@ return nil end end +--//-- +--[[-- +result = -- database result list or object +:exec() + +This method executes the selector on its database. The result list or object is returned on success, otherwise an error is thrown. + +--]]-- function selector_prototype:exec() local db_error, result = self:try_exec() if db_error then @@ -419,8 +755,15 @@ return result end end +--//-- --- NOTE: This function caches the result! +--[[-- +count = -- number of rows returned +:count() + +This function wraps the given selector inside a subquery to count the number of rows returned by the database. NOTE: The result is cached inside the selector, thus the selector should NOT be modified afterwards. + +--]]-- function selector_prototype:count() if not self._count then local count_selector = self:get_db_conn():new_selector() @@ -431,6 +774,7 @@ end return self._count end +--//-- @@ -453,6 +797,20 @@ end end +--[[-- +mondelefant.attach( + mode, -- attachment type: "11" one to one, "1m" one to many, "m1" many to one + data1, -- first database result list or object + data2, -- second database result list or object + key1, -- field name(s) in first result list or object used for attaching + key2, -- field name(s) in second result list or object used for attaching + ref1, -- name of reference field to be set in first database result list or object + ref2 -- name of reference field to be set in second database result list or object +) + +This function attaches database result lists/objects with each other. It does not need to be called directly. + +--]]-- function attach(mode, data1, data2, key1, key2, ref1, ref2) local many1, many2 if mode == "11" then @@ -532,6 +890,7 @@ end end end +--//-- @@ -539,15 +898,37 @@ -- model system -- ------------------ +--[[-- +.primary_key + +Primary key of a database class (model). Defaults to "id". + +--]]-- class_prototype.primary_key = "id" +--//-- +--[[-- +db_handle = -- database connection handle used by this class +:get_db_conn() + +By implementing this method for a particular model or overwriting it in the default prototype "mondelefant.class_prototype", classes are connected with a particular database. This method needs to return a database connection handle. If it is not overwritten, an error is thrown, when invoking this method. + +--]]-- function class_prototype:get_db_conn() error( "Method mondelefant class(_prototype):get_db_conn() " .. "has to be implemented." ) end +--//-- +--[[-- +string = -- string of form '"schemaname"."tablename"' or '"tablename"' +:get_qualified_table() + +This method returns a string with the (double quoted) qualified table name used to store objects of this class. + +--]]-- function class_prototype:get_qualified_table() if not self.table then error "Table unknown." end if self.schema then @@ -556,7 +937,15 @@ return '"' .. self.table .. '"' end end +--]]-- +--[[-- +string = -- single quoted string of form "'schemaname.tablename'" or "'tablename'" +:get_qualified_table_literal() + +This method returns a string with an SQL literal representing the given table. It causes ambiguities when the table name contains a dot (".") character. + +--]]-- function class_prototype:get_qualified_table_literal() if not self.table then error "Table unknown." end if self.schema then @@ -565,7 +954,15 @@ return self.table end end +--//-- +--[[-- +list = -- list of column names of primary key +:get_primary_key_list() + +This method returns a list of column names of the primary key. + +--]]-- function class_prototype:get_primary_key_list() local primary_key = self.primary_key if type(primary_key) == "string" then @@ -574,7 +971,15 @@ return primary_key end end +--//-- +--[[-- +columns = -- list of columns +:get_columns() + +This method returns a list of column names of the table used for the class. + +--]]-- function class_prototype:get_columns() if self._columns then return self._columns @@ -600,6 +1005,15 @@ return columns end +--[[-- +selector = -- new selector for selecting objects of this class +:new_selector( + db_conn -- optional(!) database connection handle, defaults to result of :get_db_conn() +) + +This method creates a new selector for selecting objects of the class. + +--]]-- function class_prototype:new_selector(db_conn) local selector = (db_conn or self:get_db_conn()):new_selector() selector:set_class(self) @@ -607,20 +1021,44 @@ selector:add_field(self:get_qualified_table() .. ".*") return selector end +--//-- +--[[-- +db_list = -- database result being an empty list +:create_list() + +Creates an empty database result representing a list of objects of the given class. + +--]]-- function class_prototype:create_list() local list = self:get_db_conn():create_list() list._class = self return list end +--//-- +--[[-- +db_object = -- database object (instance of model) +:new() + +Creates a new object of the given class. + +--]]-- function class_prototype:new() local object = self:get_db_conn():create_object() object._class = self object._new = true return object end +--//-- +--[[-- +db_error = -- database error object, or nil in case of success +:try_save() + +This method saves changes to an object in the database. Returns nil on success, otherwise an error object is returned. + +--]]-- function class_prototype.object:try_save() if not self._class then error("Cannot save object: No class information available.") @@ -703,7 +1141,14 @@ end return nil end +--//-- +--[[-- +:save() + +This method saves changes to an object in the database. Throws error, unless successful. + +--]]-- function class_prototype.object:save() local db_error = self:try_save() if db_error then @@ -711,7 +1156,15 @@ end return self end +--//-- +--[[-- +db_error = -- database error object, or nil in case of success +:try_destroy() + +This method deletes an object in the database. Returns nil on success, otherwise an error object is returned. + +--]]-- function class_prototype.object:try_destroy() if not self._class then error("Cannot destroy object: No class information available.") @@ -731,7 +1184,14 @@ primary_key_compare } end +--//-- +--[[-- +:destroy() + +This method deletes an object in the database. Throws error, unless successful. + +--]]-- function class_prototype.object:destroy() local db_error = self:try_destroy() if db_error then @@ -739,7 +1199,22 @@ end return self end +--//-- +--[[-- +db_selector = +:get_reference_selector( + ref_name, -- name of reference (e.g. "children") + options, -- table options passed to the reference loader (e.g. { order = ... }) + ref_alias, -- optional alias for the reference (e.g. "ordered_children") + back_ref_alias -- back reference name (e.g. "parent") +) + +This method returns a special selector for selecting referenced objects. It is prepared in a way, that on execution of the selector, all returned objects are attached with the objects of the existent list. The "ref" and "back_ref" arguments passed to "add_reference" are used for the attachment, unless aliases are given with "ref_alias" and "back_ref_alias". If "options" are set, these options are passed to the reference loader. The default reference loader supports only one option named "order". If "order" is set to nil, the default order is used, if "order" is set to false, no ORDER BY statment is included in the selector, otherwise the given expression is used for ordering. + +This method is not only available for database result lists but also for database result objects. + +--]]-- function class_prototype.list:get_reference_selector( ref_name, options, ref_alias, back_ref_alias ) @@ -774,22 +1249,86 @@ ) return selector end +--//-- +--[[-- +db_list_or_object = +:load( + ref_name, -- name of reference (e.g. "children") + options, -- table options passed to the reference loader (e.g. { order = ... }) + ref_alias, -- optional alias for the reference (e.g. "ordered_children") + back_ref_alias -- back reference name (e.g. "parent") +) + +This method loads referenced objects and attaches them with the objects of the existent list. The "ref" and "back_ref" arguments passed to "add_reference" are used for the attachment, unless aliases are given with "ref_alias" and "back_ref_alias". If "options" are set, these options are passed to the reference loader. The default reference loader supports only one option named "order". If "order" is set to nil, the default order is used, if "order" is set to false, no ORDER BY statment is included in the selector, otherwise the given expression is used for ordering. + +This method is not only available for database result lists but also for database result objects. + +--]]-- function class_prototype.list.load(...) return class_prototype.list.get_reference_selector(...):exec() end +--//-- +--[[-- +db_object = +:get_reference_selector( + ref_name, -- name of reference (e.g. "children") + options, -- table options passed to the reference loader (e.g. { order = ... }) + ref_alias, -- optional alias for the reference (e.g. "ordered_children") + back_ref_alias -- back reference name (e.g. "parent") +) + +This method returns a special selector for selecting referenced objects. It is prepared in a way, that on execution of the selector, all returned objects are attached with the objects of the existent list. The "ref" and "back_ref" arguments passed to "add_reference" are used for the attachment, unless aliases are given with "ref_alias" and "back_ref_alias". If "options" are set, these options are passed to the reference loader. The default reference loader supports only one option named "order". If "order" is set to nil, the default order is used, if "order" is set to false, no ORDER BY statment is included in the selector, otherwise the given expression is used for ordering. + +This method is not only available for database result objects but also for database result lists. + +--]]-- function class_prototype.object:get_reference_selector(...) local list = self._class:create_list() list[1] = self return list:get_reference_selector(...) end +--//-- +--[[-- +db_list_or_object = +:load( + ref_name, -- name of reference (e.g. "children") + options, -- table options passed to the reference loader (e.g. { order = ... }) + ref_alias, -- optional alias for the reference (e.g. "ordered_children") + back_ref_alias -- back reference name (e.g. "parent") +) + +This method loads referenced objects and attaches them with the objects of the existent list. The "ref" and "back_ref" arguments passed to "add_reference" are used for the attachment, unless aliases are given with "ref_alias" and "back_ref_alias". If "options" are set, these options are passed to the reference loader. The default reference loader supports only one option named "order". If "order" is set to nil, the default order is used, if "order" is set to false, no ORDER BY statment is included in the selector, otherwise the given expression is used for ordering. + +This method is not only available for database result objects but also for database result lists. Calling this method for objects is unneccessary, unless additional options and/or an alias is used. + +--]]-- function class_prototype.object.load(...) return class_prototype.object.get_reference_selector(...):exec() end +--//-- +--[[-- +db_class = -- same class returned +:add_reference{ + mode = mode, -- "11", "1m", "m1", or "mm" (one/many to one/many) + to = to, -- referenced class (model), optionally as string or function returning the value (avoids autoload) + this_key = this_key, -- name of key in this class (model) + that_key = that_key, -- name of key in the other class (model) ("to" argument) + ref = ref, -- name of reference in this class, referring to the other class + back_ref = back_ref, -- name of reference in other class, referring to this class + default_order = default_order, -- expression as passed to "assemble_command" used for sorting + selector_generator = selector_generator, -- alternative function used as selector generator (use only, when you know what you are doing) + connected_by_table = connected_by_table, -- connecting table used for many to many relations + connected_by_this_key = connected_by_this_key, -- key in connecting table referring to "this_key" of this class (model) + connected_by_that_key = connected_by_that_key -- key in connecting table referring to "that_key" in other class (model) ("to" argument) +} +Denotes a reference from one database class to another database class (model to model relation). There are 4 possible types of references: one-to-one (mode = "11"), one-to-many (mode = "1m"), many-to-one ("m1"), and many-to-many ("mm"). References usually should be defined in both models, which are related to each other, with mirrored mode (i.e. "1m" in one model, and "m1" in the other). One-to-one and one-to-many references may have a "back_ref" setting, which causes that loaded objects of the referenced class, refer back to the originating object. One-to-many and many-to-many references may have a "default_order" setting, which selects the default order for selected objects. When adding a many-to-many reference, the argument "connected_by_table", "connected_by_this_key" and "connected_by_that_key" must be set additionally. + +--]]-- function class_prototype:add_reference(args) local selector_generator = args.selector_generator local mode = args.mode @@ -885,3 +1424,5 @@ end return self end +--//-- + diff -r c1f3eb9713a4 -r 3a6fe8663b26 libraries/mondelefant/mondelefant_native.autodoc.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libraries/mondelefant/mondelefant_native.autodoc.lua Fri Jun 04 19:00:34 2010 +0200 @@ -0,0 +1,265 @@ + +--[[-- +db_handle, -- database handle, or nil in case of error +errmsg, -- error message +errcode = -- error code +mondelefant.connect{ + engine = "postgresql", -- no other engine is supported + host = host, -- hostname or directory with leading slash where Unix-domain socket resides + hostaddr = hostaddr, -- IPv4, or IPv6 address if supported + port = port, -- TCP port or socket file name extension + dbname = dbname, -- name of database to connect with + user = user, -- login name + password = password, -- password + connect_timeout = connect_timeout, -- seconds to wait for connection to be established. Zero or nil means infinite + ... +} + +Opens a new database connection and returns a handle for that connection. + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_connect(lua_State *L) +--//-- + + +--[[-- +:close() + +Closes the database connection. This method may be called multiple times and is called automatically when the database handle is garbage collected. + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_conn_close(lua_State *L) +--//-- + + +--[[-- +status = -- true, if database connection has no malfunction +:is_ok() + +Returns false, if the database connection has a malfunction, otherwise true. + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_conn_is_ok(lua_State *L) +--//-- + + +--[[-- +status = -- status string +:get_transaction_status() + +Depending on the transaction status of the connection a string is returned: +- idle +- active +- intrans +- inerror +- unknown + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_conn_get_transaction_status(lua_State *L) +--//-- + + +--[[-- +db_list = -- database result being an empty list +:create_list() + +Creates an empty database result representing a list. The used meta-table is "result_metatable". The attribute "_connection" is set to the database handle, and the attribute "_type" is set to "list". + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_conn_create_list(lua_State *L) +--//-- + + +--[[-- +db_object = -- database result being an empty object (row) +:create_object() + +Creates an empty database result representing an object (row). The used meta-table is "result_metatable". The attribute "_connection" is set to the database handle, and the attribute "_type" is set to "object". Additionally the attributes "_data", "_dirty" and "_ref" are initialized with an empty table. TODO: Documentation of _data, _dirty and _ref. + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_conn_create_object(lua_State *L) +--//-- + + +--[[-- +quoted_encoded_string = -- encoded and quoted string +:quote_string( + unencoded_string -- string to encode and quote +) + +Prepares a string to be used safely within SQL queries. This function is database dependent (see "backslash_quote" server configuration option for PostgreSQL). + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_conn_quote_string(lua_State *L) +--//-- + + +--[[-- +quoted_encoded_data = -- encoded and quoted data (as Lua string) +:quote_string( + raw_data -- data (as Lua string) to encode and quote +) + +Prepares a binary string to be used safely within SQL queries (as "bytea" type). This function is database dependent. + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_conn_quote_binary(lua_State *L) +--//-- + + +--[[-- +sql_string = +:assemble_command{ + template, -- template string + arg1, -- value to be inserted + arg2, -- another value to be inserted + key1 = named_arg3, -- named value + key2 = named_arg4, -- another named value + ... +} + +This method returns a SQL command by inserting the given values into the template string. Placeholders are "?" or "$", optionally followed by alphanumeric characters (including underscores). Placeholder characters can be escaped by writing them twice. A question-mark ("?") denotes a single value to be inserted, a dollar-sign ("$") denotes a list of sub-structures to be inserted. If alphanumeric characters are following the placeholder character, then these characters form a key, which is used to lookup the value to be used, otherwise values of numeric indicies are used. + +TODO: documentation of input-converters + +List of sub-structures are tables with an optional "sep" value, which is used as seperator. Each (numerically indexed) entry of this table is passed to a recursive call of "assemble_command" and concatenated with the given seperator, or ", ", if no seperator is given. + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_conn_assemble_command(lua_State *L) +--//-- + + +--[[-- +db_error, -- error object, or nil in case of success +result1, -- result of first command +result2, -- result of second command +... = +:try_query( + command1, -- first command (to be processed by "assemble_command" method) + mode1, -- mode for first command: "list", "object" or "opt_object" + command2, -- second command (to be processed by "assemble_command" method) + mode2, -- mode for second command: "list", "object" or "opt_object" + .. +) + +This method executes one or multiple SQL commands and returns its results. Each command is pre-processed by the "assemble_command" method of the database handle. A mode can be selected for each command: "list" for normal queries, "object" for queries which have exactly one result row, or "opt_object" which have one or zero result rows. If an error occurs, an error object is returned as first result value. + +The mode of the last command may be ommitted and default to "list". + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_conn_try_query(lua_State *L) +--//-- + + +--[[-- +:escalate() + +Causes a Lua error to be thrown. If the database connection has "error_objects" set to true, then the object is thrown itself, otherwise a string is thrown. + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_errorobject_escalate(lua_State *L) +--//-- + + +--[[-- +bool = -- true or false +:is_kind_of( + error_code -- error code as used by this library +) + +Checks, if a given error is of a given kind. + +Example: +db_error:is_kind_of("IntegrityConstraintViolation") + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_errorobject_is_kind_of(lua_State *L) +--//-- + + +--[[-- +result1, -- result of first command +result2, -- result of second command +... = +:query( + command1, -- first command (to be processed by "assemble_command" method) + mode1, -- mode for first command: "list", "object" or "opt_object" + command2, -- second command (to be processed by "assemble_command" method) + mode2, -- mode for second command: "list", "object" or "opt_object" + .. +) + +Same as "try_query" but raises error, when occurring. + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_conn_query(lua_State *L) +--//-- + + +--[[-- +db_list_or_object = -- first argument is returned +mondelefant.set_class( + db_list_or_object, -- database result list or object + db_class -- database class (model) +) + +This function sets a class for a given database result list or object. If a result list is given as first argument, the class is also set for all elements of the list. + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_set_class(lua_State *L) +--//-- + + +--[[-- +db_class = -- new database class (model) +mondelefant.new_class() + +This function creates a new class (model) used for database result lists or objects. + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_new_class(lua_State *L) +--//-- + + +--[[-- +reference_data = -- table with reference information +:get_reference( + name -- reference name +) + +This function performs a lookup for the given name in the "reference" table. Prototypes are used, when lookup was unsuccessful. + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_class_get_reference(lua_State *L) +--//-- + + +--[[-- +reference_name = -- reference name +:get_foreign_key_reference_name( + foreign_key -- foreign key +) + +This function performs a lookup for the given name in the "foreign_keys" table. Prototypes are used, when lookup was unsuccessful. + +--]]-- +-- implemented in mondelefant_native.c as +-- static int mondelefant_class_get_reference(lua_State *L) +--//-- + diff -r c1f3eb9713a4 -r 3a6fe8663b26 libraries/mondelefant/mondelefant_native.c --- a/libraries/mondelefant/mondelefant_native.c Thu Apr 22 20:46:29 2010 +0200 +++ b/libraries/mondelefant/mondelefant_native.c Fri Jun 04 19:00:34 2010 +0200 @@ -5,24 +5,35 @@ #include #include -#define MONDELEFANT_REGKEY "e449ba8d9a53d353_mondelefant" +// NOTE: Comments with format "// " denote the Lua stack position + +// prefix for all Lua registry entries of this library: +#define MONDELEFANT_REGKEY "e449ba8d9a53d353_mondelefant_" -#define MONDELEFANT_MODULE_REGKEY (MONDELEFANT_REGKEY "_module") -#define MONDELEFANT_CONN_MT_REGKEY (MONDELEFANT_REGKEY "_connection") -#define MONDELEFANT_CONN_DATA_REGKEY (MONDELEFANT_REGKEY "_connection_data") -#define MONDELEFANT_RESULT_MT_REGKEY (MONDELEFANT_REGKEY "_result") -#define MONDELEFANT_ERROROBJECT_MT_REGKEY (MONDELEFANT_REGKEY "_errorobject") -#define MONDELEFANT_CLASS_MT_REGKEY (MONDELEFANT_REGKEY "_class") -#define MONDELEFANT_CLASS_PROTO_REGKEY (MONDELEFANT_REGKEY "_class_proto") +// registry key of module "mondelefant_native": +#define MONDELEFANT_MODULE_REGKEY (MONDELEFANT_REGKEY "module") +// registry key of meta-table for database connections: +#define MONDELEFANT_CONN_MT_REGKEY (MONDELEFANT_REGKEY "connection") +// registry key of table storing connection specific data: +#define MONDELEFANT_CONN_DATA_REGKEY (MONDELEFANT_REGKEY "connection_data") +// registry key of meta-table for database result lists and objects: +#define MONDELEFANT_RESULT_MT_REGKEY (MONDELEFANT_REGKEY "result") +// registry key of meta-table for database error objects: +#define MONDELEFANT_ERROROBJECT_MT_REGKEY (MONDELEFANT_REGKEY "errorobject") +// registry key of meta-table for models (named classes here): +#define MONDELEFANT_CLASS_MT_REGKEY (MONDELEFANT_REGKEY "class") +// registry key of default prototype for models/classes: +#define MONDELEFANT_CLASS_PROTO_REGKEY (MONDELEFANT_REGKEY "class_proto") -#define MONDELEFANT_SERVER_ENCODING_ASCII 0 -#define MONDELEFANT_SERVER_ENCODING_UTF8 1 - +// C-structure for database connection userdata: typedef struct { PGconn *pgconn; int server_encoding; } mondelefant_conn_t; +#define MONDELEFANT_SERVER_ENCODING_ASCII 0 +#define MONDELEFANT_SERVER_ENCODING_UTF8 1 +// transform codepoint-position to byte-position for a given UTF-8 string: static size_t utf8_position_to_byte(const char *str, size_t utf8pos) { size_t bytepos; for (bytepos = 0; utf8pos > 0; bytepos++) { @@ -34,8 +45,10 @@ return bytepos; } +// PostgreSQL's OID for binary data type (bytea): #define MONDELEFANT_POSTGRESQL_BINARY_OID ((Oid)17) +// mapping a PostgreSQL type given by its OID to a string identifier: static const char *mondelefant_oid_to_typestr(Oid oid) { switch (oid) { case 16: return "bool"; @@ -79,9 +92,18 @@ } } +// This library maps PostgreSQL's error codes to CamelCase string +// identifiers, which consist of CamelCase identifiers and are seperated +// by dots (".") (no leading or trailing dots). +// There are additional error identifiers which do not have a corresponding +// PostgreSQL error associated with it. + +// matching start of local variable 'pgcode' against string 'incode', +// returning string 'outcode' on match: #define mondelefant_errcode_item(incode, outcode) \ if (!strncmp(pgcode, (incode), strlen(incode))) return outcode; else +// additional error identifiers without corresponding PostgreSQL error: #define MONDELEFANT_ERRCODE_UNKNOWN "unknown" #define MONDELEFANT_ERRCODE_CONNECTION "ConnectionException" #define MONDELEFANT_ERRCODE_RESULTCOUNT_LOW "WrongResultSetCount.ResultSetMissing" @@ -89,6 +111,7 @@ #define MONDELEFANT_ERRCODE_QUERY1_NO_ROWS "NoData.OneRowExpected" #define MONDELEFANT_ERRCODE_QUERY1_MULTIPLE_ROWS "CardinalityViolation.OneRowExpected" +// mapping PostgreSQL error code to error code as returned by this library: static const char *mondelefant_translate_errcode(const char *pgcode) { if (!pgcode) abort(); // should not happen mondelefant_errcode_item("02", "NoData") @@ -136,6 +159,9 @@ return "unknown"; } +// C-function, checking if a given error code (as defined by this library) +// is belonging to a certain class of errors (strings are equal or error +// code begins with error class followed by a dot): static int mondelefant_check_error_class( const char *errcode, const char *errclass ) { @@ -150,6 +176,7 @@ } } +// pushing first line of a string on Lua's stack (without trailing CR/LF): static void mondelefant_push_first_line(lua_State *L, const char *str) { char *str2; size_t i = 0; @@ -164,11 +191,14 @@ free(str2); } +// "connect" function of library, which establishes a database connection +// and returns a database connection handle: static int mondelefant_connect(lua_State *L) { - luaL_Buffer buf; - const char *conninfo; - PGconn *pgconn; - mondelefant_conn_t *conn; + luaL_Buffer buf; // Lua string buffer to create 'conninfo' (see below) + const char *conninfo; // string for PQconnectdb function + PGconn *pgconn; // PGconn object as returned by PQconnectdb function + mondelefant_conn_t *conn; // C-structure for userdata + // if engine is anything but "postgresql", then raise error: lua_settop(L, 1); lua_getfield(L, 1, "engine"); // 2 if (!lua_toboolean(L, 2)) { @@ -180,6 +210,8 @@ "Only database engine 'postgresql' is supported." ); } + // create 'conninfo' string for PQconnectdb function, which contains all + // options except "engine" option: lua_settop(L, 1); lua_pushnil(L); // slot for key at stack position 2 lua_pushnil(L); // slot for value at stack position 3 @@ -223,7 +255,9 @@ lua_replace(L, 2); lua_settop(L, 2); conninfo = lua_tostring(L, 2); + // call PQconnectdb function of libpq: pgconn = PQconnectdb(conninfo); + // throw errors, if neccessary: if (!pgconn) { return luaL_error(L, "Error in libpq while creating 'PGconn' structure." @@ -244,9 +278,12 @@ PQfinish(pgconn); return 3; } + // create userdata: lua_settop(L, 0); conn = lua_newuserdata(L, sizeof(*conn)); // 1 + // set 'pgconn' in C-struct of userdata: conn->pgconn = pgconn; + // set 'server_encoding' in C-struct of userdata: { const char *charset; charset = PQparameterStatus(pgconn, "server_encoding"); @@ -256,21 +293,25 @@ conn->server_encoding = MONDELEFANT_SERVER_ENCODING_ASCII; } } - + // set meta-table of userdata: luaL_getmetatable(L, MONDELEFANT_CONN_MT_REGKEY); // 2 lua_setmetatable(L, 1); - + // create entry in table storing connection specific data and associate + // created userdata with it: lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CONN_DATA_REGKEY); // 2 lua_pushvalue(L, 1); // 3 lua_newtable(L); // 4 lua_settable(L, 2); lua_settop(L, 1); - + // store key "engine" with value "postgresql" as connection specific data: lua_pushliteral(L, "postgresql"); lua_setfield(L, 1, "engine"); + // return userdata: return 1; } +// returns pointer to libpq handle 'pgconn' of userdata at given index +// (or throws error, if database connection has been closed): static mondelefant_conn_t *mondelefant_get_conn(lua_State *L, int index) { mondelefant_conn_t *conn; conn = luaL_checkudata(L, index, MONDELEFANT_CONN_MT_REGKEY); @@ -281,7 +322,9 @@ return conn; } +// meta-method "__index" of database handles (userdata): static int mondelefant_conn_index(lua_State *L) { + // try table for connection specific data: lua_settop(L, 2); lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CONN_DATA_REGKEY); // 3 lua_pushvalue(L, 1); // 4 @@ -290,6 +333,7 @@ lua_pushvalue(L, 2); // 4 lua_gettable(L, 3); // 4 if (!lua_isnil(L, 4)) return 1; + // try to use prototype stored in connection specific data: lua_settop(L, 3); lua_getfield(L, 3, "prototype"); // 4 if (lua_toboolean(L, 4)) { @@ -297,6 +341,7 @@ lua_gettable(L, 4); // 5 if (!lua_isnil(L, 5)) return 1; } + // try to use "postgresql_connection_prototype" of library: lua_settop(L, 2); lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_MODULE_REGKEY); // 3 lua_getfield(L, 3, "postgresql_connection_prototype"); // 4 @@ -305,6 +350,7 @@ lua_gettable(L, 4); // 5 if (!lua_isnil(L, 5)) return 1; } + // try to use "connection_prototype" of library: lua_settop(L, 3); lua_getfield(L, 3, "connection_prototype"); // 4 if (lua_toboolean(L, 4)) { @@ -312,21 +358,26 @@ lua_gettable(L, 4); // 5 if (!lua_isnil(L, 5)) return 1; } + // give up and return nothing: return 0; } +// meta-method "__newindex" of database handles (userdata): static int mondelefant_conn_newindex(lua_State *L) { + // store key-value pair in table for connection specific data: lua_settop(L, 3); lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CONN_DATA_REGKEY); // 4 lua_pushvalue(L, 1); // 5 lua_gettable(L, 4); // 5 - lua_remove(L, 4); // connection specific data-table at stack position 4 + lua_remove(L, 4); // connection specific data-table at stack position 4 lua_pushvalue(L, 2); lua_pushvalue(L, 3); lua_settable(L, 4); + // return nothing: return 0; } +// meta-method "__gc" of database handles: static int mondelefant_conn_free(lua_State *L) { mondelefant_conn_t *conn; conn = luaL_checkudata(L, 1, MONDELEFANT_CONN_MT_REGKEY); @@ -335,6 +386,7 @@ return 0; } +// method "close" of database handles: static int mondelefant_conn_close(lua_State *L) { mondelefant_conn_t *conn; lua_settop(L, 1); @@ -344,6 +396,7 @@ return 0; } +// method "is_okay" of database handles: static int mondelefant_conn_is_ok(lua_State *L) { mondelefant_conn_t *conn; lua_settop(L, 1); @@ -352,6 +405,7 @@ return 1; } +// method "get_transaction_status" of database handles: static int mondelefant_conn_get_transaction_status(lua_State *L) { mondelefant_conn_t *conn; lua_settop(L, 1); @@ -375,71 +429,97 @@ return 1; } +// method "create_list" of database handles: static int mondelefant_conn_create_list(lua_State *L) { + // ensure that first argument is a database connection: lua_settop(L, 2); luaL_checkudata(L, 1, MONDELEFANT_CONN_MT_REGKEY); + // if no second argument is given, use an empty table: if (!lua_toboolean(L, 2)) { lua_newtable(L); lua_replace(L, 2); // new result at stack position 2 } + // set meta-table for database result lists/objects: lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_RESULT_MT_REGKEY); // 3 lua_setmetatable(L, 2); + // set "_connection" attribute to self: lua_pushvalue(L, 1); // 3 lua_setfield(L, 2, "_connection"); + // set "_type" attribute to string "list": lua_pushliteral(L, "list"); // 3 lua_setfield(L, 2, "_type"); + // return created database result list: return 1; } +// method "create_object" of database handles: static int mondelefant_conn_create_object(lua_State *L) { + // ensure that first argument is a database connection: lua_settop(L, 2); luaL_checkudata(L, 1, MONDELEFANT_CONN_MT_REGKEY); + // if no second argument is given, use an empty table: if (!lua_toboolean(L, 2)) { lua_newtable(L); lua_replace(L, 2); // new result at stack position 2 } + // set meta-table for database result lists/objects: lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_RESULT_MT_REGKEY); // 3 lua_setmetatable(L, 2); + // set "_connection" attribute to self: lua_pushvalue(L, 1); // 3 lua_setfield(L, 2, "_connection"); + // set "_type" attribute to string "object": lua_pushliteral(L, "object"); // 3 lua_setfield(L, 2, "_type"); // "object" or "list" + // create empty tables for "_data", "_dirty" and "_ref" attributes: lua_newtable(L); // 3 lua_setfield(L, 2, "_data"); lua_newtable(L); // 3 lua_setfield(L, 2, "_dirty"); lua_newtable(L); // 3 lua_setfield(L, 2, "_ref"); // nil=no info, false=nil, else table + // return created database result object: return 1; } +// method "quote_string" of database handles: static int mondelefant_conn_quote_string(lua_State *L) { mondelefant_conn_t *conn; const char *input; size_t input_len; char *output; size_t output_len; + // get 'conn' attribute of C-struct of database connection: lua_settop(L, 2); conn = mondelefant_get_conn(L, 1); + // get second argument, which must be a string: input = luaL_checklstring(L, 2, &input_len); + // throw error, if string is too long: if (input_len > (SIZE_MAX / sizeof(char) - 3) / 2) { return luaL_error(L, "String to be escaped is too long."); } + // allocate memory for quoted string: output = malloc((2 * input_len + 3) * sizeof(char)); if (!output) { return luaL_error(L, "Could not allocate memory for string quoting."); } + // do escaping by calling PQescapeStringConn and enclosing result with + // single quotes: output[0] = '\''; output_len = PQescapeStringConn( conn->pgconn, output + 1, input, input_len, NULL ); output[output_len + 1] = '\''; output[output_len + 2] = 0; + // create Lua string: lua_pushlstring(L, output, output_len + 2); + // free allocated memory: free(output); + // return Lua string: return 1; } +// method "quote_binary" of database handles: static int mondelefant_conn_quote_binary(lua_State *L) { mondelefant_conn_t *conn; const char *input; @@ -447,39 +527,52 @@ char *output; size_t output_len; luaL_Buffer buf; + // get 'conn' attribute of C-struct of database connection: lua_settop(L, 2); conn = mondelefant_get_conn(L, 1); + // get second argument, which must be a string: input = luaL_checklstring(L, 2, &input_len); + // call PQescapeByteaConn, which allocates memory itself: output = (char *)PQescapeByteaConn( conn->pgconn, (const unsigned char *)input, input_len, &output_len ); + // if PQescapeByteaConn returned NULL, then throw error: if (!output) { return luaL_error(L, "Could not allocate memory for binary quoting."); } + // create Lua string enclosed by single quotes: luaL_buffinit(L, &buf); luaL_addchar(&buf, '\''); luaL_addlstring(&buf, output, output_len - 1); luaL_addchar(&buf, '\''); luaL_pushresult(&buf); + // free memory allocated by PQescapeByteaConn: PQfreemem(output); + // return Lua string: return 1; } +// method "assemble_command" of database handles: static int mondelefant_conn_assemble_command(lua_State *L) { mondelefant_conn_t *conn; int paramidx = 2; const char *template; size_t template_pos = 0; luaL_Buffer buf; + // get 'conn' attribute of C-struct of database connection: lua_settop(L, 2); conn = mondelefant_get_conn(L, 1); + // if second argument is a string, return this string: if (lua_isstring(L, 2)) { lua_tostring(L, 2); return 1; } - // extra feature for objects with __tostring meta-method: + // if second argument has __tostring meta-method, + // then use this method and return its result: if (luaL_callmeta(L, 2, "__tostring")) return 1; + // otherwise, require that second argument is a table: luaL_checktype(L, 2, LUA_TTABLE); + // get first element of table, which must be a string: lua_rawgeti(L, 2, 1); // 3 luaL_argcheck(L, lua_isstring(L, 3), @@ -487,23 +580,37 @@ "First entry of SQL command structure is not a string." ); template = lua_tostring(L, 3); + // get value of "input_converter" attribute of database connection: lua_pushliteral(L, "input_converter"); // 4 lua_gettable(L, 1); // input_converter at stack position 4 + // reserve space on Lua stack: lua_pushnil(L); // free space at stack position 5 lua_pushnil(L); // free space at stack position 6 + // initialize Lua buffer for result string: luaL_buffinit(L, &buf); + // fill buffer in loop: while (1) { + // variable declaration: char c; + // get next character: c = template[template_pos++]; + // break, when character is NULL byte: if (!c) break; - if (c == '?' || c == '$') { - if (template[template_pos] == c) { + // question-mark and dollar-sign are special characters: + if (c == '?' || c == '$') { // special character found + // check, if same character follows: + if (template[template_pos] == c) { // special character is escaped + // consume two characters of input and add one character to buffer: template_pos++; luaL_addchar(&buf, c); - } else { + } else { // special character is not escaped luaL_Buffer keybuf; int subcmd; + // set 'subcmd' = true, if special character was a dollar-sign, + // set 'subcmd' = false, if special character was a question-mark: subcmd = (c == '$'); + // read any number of alpha numeric chars or underscores + // and store them on Lua stack: luaL_buffinit(L, &keybuf); while (1) { c = template[template_pos]; @@ -517,21 +624,30 @@ template_pos++; } luaL_pushresult(&keybuf); + // check, if any characters matched: if (lua_objlen(L, -1)) { + // if any alpha numeric chars or underscores were found, + // push them on stack as a Lua string and use them to lookup + // value from second argument: lua_pushvalue(L, -1); // save key on stack - lua_gettable(L, 2); // fetch value + lua_gettable(L, 2); // fetch value (raw-value) } else { + // otherwise push nil and use numeric lookup based on 'paramidx': lua_pop(L, 1); lua_pushnil(L); // put nil on key position - lua_rawgeti(L, 2, paramidx++); // fetch value + lua_rawgeti(L, 2, paramidx++); // fetch value (raw-value) } - // stack: ..., , key, pre-value - if (subcmd) { + // Lua stack contains: ..., , key, raw-value + // branch according to type of special character ("?" or "$"): + if (subcmd) { // dollar-sign size_t i; size_t count; - lua_replace(L, 5); // sub-structure at stack position 5 - lua_pop(L, 1); // drop stored key - // stack: ..., + // store fetched value (which is supposed to be sub-structure) + // on Lua stack position 5 and drop key: + lua_replace(L, 5); + lua_pop(L, 1); + // Lua stack contains: ..., + // check, if fetched value is really a sub-structure: luaL_argcheck(L, !lua_isnil(L, 5), 2, @@ -542,9 +658,13 @@ 2, "SQL sub-structure must be a table." ); - // stack: ..., + // Lua stack contains: ..., + // get value of "sep" attribute of sub-structure, + // and place it on Lua stack position 6: lua_getfield(L, 5, "sep"); - lua_replace(L, 6); // seperator at stack position 6 + lua_replace(L, 6); + // if seperator is nil, then use ", " as default, + // if seperator is neither nil nor a string, then throw error: if (lua_isnil(L, 6)) { lua_pushstring(L, ", "); lua_replace(L, 6); @@ -555,21 +675,26 @@ "Seperator of SQL sub-structure has to be a string." ); } + // iterate over items of sub-structure: count = lua_objlen(L, 5); for (i = 0; i < count; i++) { + // add seperator, unless this is the first run: if (i) { lua_pushvalue(L, 6); luaL_addvalue(&buf); } + // recursivly apply assemble function and add results to buffer: lua_pushcfunction(L, mondelefant_conn_assemble_command); lua_pushvalue(L, 1); lua_rawgeti(L, 5, i+1); lua_call(L, 2, 1); luaL_addvalue(&buf); } - } else { + } else { // question-mark if (lua_toboolean(L, 4)) { - // call input_converter with connection handle, value and info + // call input_converter with connection handle, raw-value and + // an info-table which contains a "field_name" entry with the + // used key: lua_pushvalue(L, 4); lua_pushvalue(L, 1); lua_pushvalue(L, -3); @@ -577,54 +702,71 @@ lua_pushvalue(L, -6); lua_setfield(L, -2, "field_name"); lua_call(L, 3, 1); - // stack: ..., , key, pre-value, final-value + // Lua stack contains: ..., , key, raw-value, final-value + // remove key and raw-value: lua_remove(L, -2); lua_remove(L, -2); - // stack: ..., , final-value + // Lua stack contains: ..., , final-value + // throw error, if final-value is not a string: if (!lua_isstring(L, -1)) { return luaL_error(L, "input_converter returned non-string."); } } else { + // remove key from stack: lua_remove(L, -2); - // stack: ..., , pre-value - if (lua_isnil(L, -1)) { + // Lua stack contains: ..., , raw-value + // branch according to type of value: + if (lua_isnil(L, -1)) { // value is nil + // push string "NULL" to stack: lua_pushliteral(L, "NULL"); - } else if (lua_type(L, -1) == LUA_TBOOLEAN) { + } else if (lua_type(L, -1) == LUA_TBOOLEAN) { // value is boolean + // push strings "TRUE" or "FALSE" to stack: lua_pushstring(L, lua_toboolean(L, -1) ? "TRUE" : "FALSE"); - } else if (lua_isstring(L, -1)) { + } else if (lua_isstring(L, -1)) { // value is string or number // NOTE: In this version of lua a number will be converted + // push output of "quote_string" method of database + // connection to stack: lua_tostring(L, -1); lua_pushcfunction(L, mondelefant_conn_quote_string); lua_pushvalue(L, 1); lua_pushvalue(L, -3); lua_call(L, 2, 1); - } else { + } else { // value is of other type + // throw error: return luaL_error(L, "Unable to convert SQL value due to unknown type " "or missing input_converter." ); } - // stack: ..., , pre-value, final-value + // Lua stack contains: ..., , raw-value, final-value + // remove raw-value: lua_remove(L, -2); - // stack: ..., , final-value + // Lua stack contains: ..., , final-value } + // append final-value to buffer: luaL_addvalue(&buf); } } - } else { + } else { // character is not special + // just copy character: luaL_addchar(&buf, c); } } + // return string in buffer: luaL_pushresult(&buf); return 1; } +// max number of SQL statements executed by one "query" method call: #define MONDELEFANT_MAX_COMMAND_COUNT 64 +// max number of columns in a database result: #define MONDELEFANT_MAX_COLUMN_COUNT 1024 +// enum values for 'modes' array in C-function below: #define MONDELEFANT_QUERY_MODE_LIST 1 #define MONDELEFANT_QUERY_MODE_OBJECT 2 #define MONDELEFANT_QUERY_MODE_OPT_OBJECT 3 +// method "try_query" of database handles: static int mondelefant_conn_try_query(lua_State *L) { mondelefant_conn_t *conn; int command_count; @@ -634,12 +776,17 @@ int sent_success; PGresult *res; int rows, cols, row, col; + // get 'conn' attribute of C-struct of database connection: conn = mondelefant_get_conn(L, 1); + // calculate number of commands (2 arguments for one command): command_count = lua_gettop(L) / 2; - lua_pushnil(L); // needed, if last mode was omitted + // push nil on stack, which is needed, if last mode was ommitted: + lua_pushnil(L); + // throw error, if number of commands is too high: if (command_count > MONDELEFANT_MAX_COMMAND_COUNT) { return luaL_error(L, "Exceeded maximum command count in one query."); } + // create SQL string, store query modes and push SQL string on stack: luaL_buffinit(L, &buf); for (command_idx = 0; command_idx < command_count; command_idx++) { int mode; @@ -671,6 +818,7 @@ } luaL_pushresult(&buf); // stack position unknown lua_replace(L, 2); // SQL command string to stack position 2 + // call sql_tracer, if set: lua_settop(L, 2); lua_getfield(L, 1, "sql_tracer"); // tracer at stack position 3 if (lua_toboolean(L, 3)) { @@ -678,14 +826,23 @@ lua_pushvalue(L, 2); // 5 lua_call(L, 2, 1); // trace callback at stack position 3 } + // NOTE: If no tracer was found, then nil or false is stored at stack + // position 3. + // call PQsendQuery function and store result in 'sent_success' variable: sent_success = PQsendQuery(conn->pgconn, lua_tostring(L, 2)); + // create preliminary result table: lua_newtable(L); // results in table at stack position 4 + // iterate over results using function PQgetResult to fill result table: for (command_idx = 0; ; command_idx++) { int mode; char binary[MONDELEFANT_MAX_COLUMN_COUNT]; ExecStatusType pgstatus; + // fetch mode which was given for the command: mode = modes[command_idx]; + // if PQsendQuery call was successful, then fetch result data: if (sent_success) { + // NOTE: PQgetResult called one extra time. Break only, if all + // queries have been processed and PQgetResult returned NULL. res = PQgetResult(conn->pgconn); if (command_idx >= command_count && !res) break; if (res) { @@ -694,6 +851,7 @@ cols = PQnfields(res); } } + // handle errors: if ( !sent_success || command_idx >= command_count || !res || (pgstatus != PGRES_TUPLES_OK && pgstatus != PGRES_COMMAND_OK) || @@ -822,8 +980,8 @@ } return 1; } - rows = PQntuples(res); - cols = PQnfields(res); + // call "create_list" or "create_object" method of database handle, + // result will be at stack position 5: if (modes[command_idx] == MONDELEFANT_QUERY_MODE_LIST) { lua_pushcfunction(L, mondelefant_conn_create_list); // 5 lua_pushvalue(L, 1); // 6 @@ -833,7 +991,8 @@ lua_pushvalue(L, 1); // 6 lua_call(L, 1, 1); // 5 } - lua_newtable(L); // column_info at atack position 6 + // set "_column_info": + lua_newtable(L); // 6 for (col = 0; col < cols; col++) { lua_newtable(L); // 7 lua_pushstring(L, PQfname(res, col)); @@ -870,7 +1029,8 @@ } lua_rawseti(L, 6, col+1); } - lua_setfield(L, 5, "_column_info"); // stack at position 5 with result + lua_setfield(L, 5, "_column_info"); + // set "_rows_affected": { char *tmp; tmp = PQcmdTuples(res); @@ -879,6 +1039,7 @@ lua_setfield(L, 5, "_rows_affected"); } } + // set "_oid": { Oid tmp; tmp = PQoidValue(res); @@ -887,6 +1048,8 @@ lua_setfield(L, 5, "_oid"); } } + // copy data as strings or nil, while performing binary unescaping + // automatically: if (modes[command_idx] == MONDELEFANT_QUERY_MODE_LIST) { for (row = 0; row < rows; row++) { lua_pushcfunction(L, mondelefant_conn_create_object); // 6 @@ -942,18 +1105,25 @@ lua_pop(L, 1); lua_pushnil(L); } + // save result in result list: lua_rawseti(L, 4, command_idx+1); + // extra assertion: if (lua_gettop(L) != 4) abort(); // should not happen + // free memory acquired by libpq: PQclear(res); } // trace callback at stack position 3 // result at stack position 4 (top of stack) + // if a trace callback is existent, then call: if (lua_toboolean(L, 3)) { lua_pushvalue(L, 3); lua_call(L, 0, 0); } - lua_replace(L, 3); // result at stack position 3 - lua_getfield(L, 1, "output_converter"); // output converter at stack position 4 + // put result at stack position 3: + lua_replace(L, 3); + // get output converter to stack position 4: + lua_getfield(L, 1, "output_converter"); + // apply output converters and fill "_data" table according to column names: for (command_idx = 0; command_idx < command_count; command_idx++) { int mode; mode = modes[command_idx]; @@ -1008,6 +1178,7 @@ } lua_settop(L, 4); } + // return nil as first result value, followed by result lists/objects: lua_settop(L, 3); lua_pushnil(L); for (command_idx = 0; command_idx < command_count; command_idx++) { @@ -1016,14 +1187,18 @@ return command_count+1; } +// method "escalate" of error objects: static int mondelefant_errorobject_escalate(lua_State *L) { + // check, if we may throw an error object instead of an error string: lua_settop(L, 1); lua_getfield(L, 1, "connection"); // 2 lua_getfield(L, 2, "error_objects"); // 3 if (lua_toboolean(L, 3)) { + // throw error object: lua_settop(L, 1); return lua_error(L); } else { + // throw error string: lua_getfield(L, 1, "message"); // 4 if (lua_isnil(L, 4)) { return luaL_error(L, "No error message given for escalation."); @@ -1032,6 +1207,7 @@ } } +// method "is_kind_of" of error objects: static int mondelefant_errorobject_is_kind_of(lua_State *L) { lua_settop(L, 2); lua_getfield(L, 1, "code"); // 3 @@ -1048,40 +1224,51 @@ return 1; } +// method "query" of database handles: static int mondelefant_conn_query(lua_State *L) { int argc; + // count number of arguments: argc = lua_gettop(L); - lua_pushvalue(L, 1); - lua_insert(L, 1); + // insert "try_query" function/method at stack position 1: lua_pushcfunction(L, mondelefant_conn_try_query); - lua_insert(L, 2); - lua_call(L, argc, LUA_MULTRET); // results (with error) starting at index 2 - if (lua_toboolean(L, 2)) { + lua_insert(L, 1); + // call "try_query" method: + lua_call(L, argc, LUA_MULTRET); // results (with error) starting at index 1 + // check, if error occurred: + if (lua_toboolean(L, 1)) { + // invoke escalate method of error object: lua_pushcfunction(L, mondelefant_errorobject_escalate); - lua_pushvalue(L, 2); + lua_pushvalue(L, 1); lua_call(L, 1, 0); // will raise an error return 0; // should not be executed } else { - return lua_gettop(L) - 2; + // return everything but nil error object: + return lua_gettop(L) - 1; } } +// library function "set_class": static int mondelefant_set_class(lua_State *L) { + // ensure that first argument is a database result list/object: lua_settop(L, 2); lua_getmetatable(L, 1); // 3 lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_RESULT_MT_REGKEY); // 4 luaL_argcheck(L, lua_equal(L, 3, 4), 1, "not a database result"); + // ensure that second argument is a database class (model): lua_settop(L, 2); lua_getmetatable(L, 2); // 3 lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CLASS_MT_REGKEY); // 4 luaL_argcheck(L, lua_equal(L, 3, 4), 2, "not a database class"); + // set attribute "_class" of result list/object to given class: lua_settop(L, 2); lua_pushvalue(L, 2); // 3 lua_setfield(L, 1, "_class"); + // test, if database result is a list (and not a single object): lua_getfield(L, 1, "_type"); // 3 lua_pushliteral(L, "list"); // 4 if (lua_rawequal(L, 3, 4)) { int i; + // set attribute "_class" of all elements to given class: for (i=0; i < lua_objlen(L, 1); i++) { lua_settop(L, 2); lua_rawgeti(L, 1, i+1); // 3 @@ -1089,21 +1276,27 @@ lua_setfield(L, 3, "_class"); } } + // return first argument: lua_settop(L, 1); return 1; } +// library function "new_class": static int mondelefant_new_class(lua_State *L) { + // use first argument as template or create new table: lua_settop(L, 1); if (!lua_toboolean(L, 1)) { lua_settop(L, 0); lua_newtable(L); // 1 } + // set meta-table for database classes (models): lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CLASS_MT_REGKEY); // 2 lua_setmetatable(L, 1); + // check, if "prototype" attribute is not set: lua_pushliteral(L, "prototype"); // 2 lua_rawget(L, 1); // 2 if (!lua_toboolean(L, 2)) { + // set "prototype" attribute to default prototype: lua_pushliteral(L, "prototype"); // 3 lua_getfield(L, LUA_REGISTRYINDEX, @@ -1111,6 +1304,7 @@ ); // 4 lua_rawset(L, 1); } + // set "object" attribute to empty table, unless it is already set: lua_settop(L, 1); lua_pushliteral(L, "object"); // 2 lua_rawget(L, 1); // 2 @@ -1119,6 +1313,7 @@ lua_newtable(L); // 4 lua_rawset(L, 1); } + // set "object_get" attribute to empty table, unless it is already set: lua_settop(L, 1); lua_pushliteral(L, "object_get"); // 2 lua_rawget(L, 1); // 2 @@ -1127,6 +1322,7 @@ lua_newtable(L); // 4 lua_rawset(L, 1); } + // set "object_set" attribute to empty table, unless it is already set: lua_settop(L, 1); lua_pushliteral(L, "object_set"); // 2 lua_rawget(L, 1); // 2 @@ -1135,6 +1331,7 @@ lua_newtable(L); // 4 lua_rawset(L, 1); } + // set "list" attribute to empty table, unless it is already set: lua_settop(L, 1); lua_pushliteral(L, "list"); // 2 lua_rawget(L, 1); // 2 @@ -1143,6 +1340,7 @@ lua_newtable(L); // 4 lua_rawset(L, 1); } + // set "references" attribute to empty table, unless it is already set: lua_settop(L, 1); lua_pushliteral(L, "references"); // 2 lua_rawget(L, 1); // 2 @@ -1151,6 +1349,7 @@ lua_newtable(L); // 4 lua_rawset(L, 1); } + // set "foreign_keys" attribute to empty table, unless it is already set: lua_settop(L, 1); lua_pushliteral(L, "foreign_keys"); // 2 lua_rawget(L, 1); // 2 @@ -1159,51 +1358,67 @@ lua_newtable(L); // 4 lua_rawset(L, 1); } + // return table: lua_settop(L, 1); return 1; } +// method "get_reference" of classes (models): static int mondelefant_class_get_reference(lua_State *L) { lua_settop(L, 2); while (lua_toboolean(L, 1)) { + // get "references" table: lua_getfield(L, 1, "references"); // 3 + // perform lookup: lua_pushvalue(L, 2); // 4 lua_gettable(L, 3); // 4 + // return result, if lookup was successful: if (!lua_isnil(L, 4)) return 1; + // replace current table by its prototype: lua_settop(L, 2); lua_pushliteral(L, "prototype"); // 3 lua_rawget(L, 1); // 3 lua_replace(L, 1); } + // return nothing: return 0; } +// method "iterate_over_references" of classes (models): static int mondelefant_class_iterate_over_references(lua_State *L) { return luaL_error(L, "Reference iterator not implemented yet."); // TODO } +// method "get_foreign_key_reference_name" of classes (models): static int mondelefant_class_get_foreign_key_reference_name(lua_State *L) { lua_settop(L, 2); while (lua_toboolean(L, 1)) { + // get "foreign_keys" table: lua_getfield(L, 1, "foreign_keys"); // 3 + // perform lookup: lua_pushvalue(L, 2); // 4 lua_gettable(L, 3); // 4 + // return result, if lookup was successful: if (!lua_isnil(L, 4)) return 1; + // replace current table by its prototype: lua_settop(L, 2); lua_pushliteral(L, "prototype"); // 3 lua_rawget(L, 1); // 3 lua_replace(L, 1); } + // return nothing: return 0; } +// meta-method "__index" of database result lists and objects: static int mondelefant_result_index(lua_State *L) { const char *result_type; - lua_settop(L, 2); + // only lookup, when key is a string not beginning with an underscore: if (lua_type(L, 2) != LUA_TSTRING || lua_tostring(L, 2)[0] == '_') { - lua_rawget(L, 1); - return 1; + return 0; } + // get value of "_class" attribute, or default class, when unset: + lua_settop(L, 2); lua_getfield(L, 1, "_class"); // 3 if (!lua_toboolean(L, 3)) { lua_settop(L, 2); @@ -1212,9 +1427,11 @@ MONDELEFANT_CLASS_PROTO_REGKEY ); // 3 } + // get value of "_type" attribute: lua_getfield(L, 1, "_type"); // 4 result_type = lua_tostring(L, 4); - if (result_type && !strcmp(result_type, "object")) { + // different lookup for lists and objects: + if (result_type && !strcmp(result_type, "object")) { // object lua_settop(L, 3); // try inherited attributes, methods or getter functions: while (lua_toboolean(L, 3)) { @@ -1303,7 +1520,7 @@ return 1; } return 0; - } else if (result_type && !strcmp(result_type, "list")) { + } else if (result_type && !strcmp(result_type, "list")) { // list lua_settop(L, 3); // try inherited list attributes or methods: while (lua_toboolean(L, 3)) { @@ -1317,16 +1534,20 @@ lua_replace(L, 3); } } + // return nothing: return 0; } +// meta-method "__newindex" of database result lists and objects: static int mondelefant_result_newindex(lua_State *L) { const char *result_type; + // perform rawset, unless key is a string not starting with underscore: lua_settop(L, 3); if (lua_type(L, 2) != LUA_TSTRING || lua_tostring(L, 2)[0] == '_') { lua_rawset(L, 1); return 1; } + // get value of "_class" attribute, or default class, when unset: lua_getfield(L, 1, "_class"); // 4 if (!lua_toboolean(L, 4)) { lua_settop(L, 3); @@ -1335,9 +1556,11 @@ MONDELEFANT_CLASS_PROTO_REGKEY ); // 4 } + // get value of "_type" attribute: lua_getfield(L, 1, "_type"); // 5 result_type = lua_tostring(L, 5); - if (result_type && !strcmp(result_type, "object")) { + // distinguish between lists and objects: + if (result_type && !strcmp(result_type, "object")) { // objects lua_settop(L, 4); // try object setter functions: while (lua_toboolean(L, 4)) { @@ -1412,15 +1635,17 @@ lua_settable(L, 5); } return 0; - } else { + } else { // non-objects (i.e. lists) + // perform rawset: lua_settop(L, 3); lua_rawset(L, 1); return 0; } - return 0; } +// meta-method "__index" of classes (models): static int mondelefant_class_index(lua_State *L) { + // perform lookup in prototype: lua_settop(L, 2); lua_pushliteral(L, "prototype"); // 3 lua_rawget(L, 1); // 3 @@ -1429,6 +1654,7 @@ return 1; } +// registration information for functions of library: static const struct luaL_Reg mondelefant_module_functions[] = { {"connect", mondelefant_connect}, {"set_class", mondelefant_set_class}, @@ -1436,6 +1662,7 @@ {NULL, NULL} }; +// registration information for meta-methods of database connections: static const struct luaL_Reg mondelefant_conn_mt_functions[] = { {"__gc", mondelefant_conn_free}, {"__index", mondelefant_conn_index}, @@ -1443,6 +1670,7 @@ {NULL, NULL} }; +// registration information for methods of database connections: static const struct luaL_Reg mondelefant_conn_methods[] = { {"close", mondelefant_conn_close}, {"is_ok", mondelefant_conn_is_ok}, @@ -1457,27 +1685,32 @@ {NULL, NULL} }; +// registration information for meta-methods of error objects: static const struct luaL_Reg mondelefant_errorobject_mt_functions[] = { {NULL, NULL} }; +// registration information for methods of error objects: static const struct luaL_Reg mondelefant_errorobject_methods[] = { {"escalate", mondelefant_errorobject_escalate}, {"is_kind_of", mondelefant_errorobject_is_kind_of}, {NULL, NULL} }; +// registration information for meta-methods of database result lists/objects: static const struct luaL_Reg mondelefant_result_mt_functions[] = { {"__index", mondelefant_result_index}, {"__newindex", mondelefant_result_newindex}, {NULL, NULL} }; +// registration information for methods of database result lists/objects: static const struct luaL_Reg mondelefant_class_mt_functions[] = { {"__index", mondelefant_class_index}, {NULL, NULL} }; +// registration information for methods of classes (models): static const struct luaL_Reg mondelefant_class_methods[] = { {"get_reference", mondelefant_class_get_reference}, {"iterate_over_references", mondelefant_class_iterate_over_references}, @@ -1486,14 +1719,17 @@ {NULL, NULL} }; +// registration information for methods of database result objects (not lists!): static const struct luaL_Reg mondelefant_object_methods[] = { {NULL, NULL} }; +// registration information for methods of database result lists (not single objects!): static const struct luaL_Reg mondelefant_list_methods[] = { {NULL, NULL} }; +// luaopen function to initialize/register library: int luaopen_mondelefant_native(lua_State *L) { lua_settop(L, 0); lua_newtable(L); // module at stack position 1