jbe/bsw@0: #include jbe/bsw@0: #include jbe/bsw@0: #include jbe/bsw@0: #include jbe/bsw@0: #include jbe/bsw@0: #include jbe/bsw@0: jbe@23: // NOTE: Comments with format "// " denote the Lua stack position jbe@23: jbe@23: // prefix for all Lua registry entries of this library: jbe@23: #define MONDELEFANT_REGKEY "e449ba8d9a53d353_mondelefant_" jbe/bsw@0: jbe@23: // registry key of module "mondelefant_native": jbe@23: #define MONDELEFANT_MODULE_REGKEY (MONDELEFANT_REGKEY "module") jbe@23: // registry key of meta-table for database connections: jbe@23: #define MONDELEFANT_CONN_MT_REGKEY (MONDELEFANT_REGKEY "connection") jbe@23: // registry key of table storing connection specific data: jbe@23: #define MONDELEFANT_CONN_DATA_REGKEY (MONDELEFANT_REGKEY "connection_data") jbe@23: // registry key of meta-table for database result lists and objects: jbe@23: #define MONDELEFANT_RESULT_MT_REGKEY (MONDELEFANT_REGKEY "result") jbe@23: // registry key of meta-table for database error objects: jbe@23: #define MONDELEFANT_ERROROBJECT_MT_REGKEY (MONDELEFANT_REGKEY "errorobject") jbe@23: // registry key of meta-table for models (named classes here): jbe@23: #define MONDELEFANT_CLASS_MT_REGKEY (MONDELEFANT_REGKEY "class") jbe@23: // registry key of default prototype for models/classes: jbe@23: #define MONDELEFANT_CLASS_PROTO_REGKEY (MONDELEFANT_REGKEY "class_proto") jbe/bsw@0: jbe@23: // C-structure for database connection userdata: jbe/bsw@0: typedef struct { jbe/bsw@0: PGconn *pgconn; jbe/bsw@0: int server_encoding; jbe/bsw@0: } mondelefant_conn_t; jbe@23: #define MONDELEFANT_SERVER_ENCODING_ASCII 0 jbe@23: #define MONDELEFANT_SERVER_ENCODING_UTF8 1 jbe/bsw@0: jbe@23: // transform codepoint-position to byte-position for a given UTF-8 string: jbe/bsw@0: static size_t utf8_position_to_byte(const char *str, size_t utf8pos) { jbe/bsw@0: size_t bytepos; jbe/bsw@0: for (bytepos = 0; utf8pos > 0; bytepos++) { jbe/bsw@0: uint8_t c; jbe/bsw@0: c = ((const uint8_t *)str)[bytepos]; jbe/bsw@0: if (!c) break; jbe/bsw@0: if (c <= 0x7f || c >= 0xc0) utf8pos--; jbe/bsw@0: } jbe/bsw@0: return bytepos; jbe/bsw@0: } jbe/bsw@0: jbe@23: // PostgreSQL's OID for binary data type (bytea): jbe/bsw@0: #define MONDELEFANT_POSTGRESQL_BINARY_OID ((Oid)17) jbe/bsw@0: jbe@23: // mapping a PostgreSQL type given by its OID to a string identifier: jbe/bsw@0: static const char *mondelefant_oid_to_typestr(Oid oid) { jbe/bsw@0: switch (oid) { jbe/bsw@0: case 16: return "bool"; jbe/bsw@0: case 17: return "bytea"; jbe/bsw@0: case 18: return "char"; jbe/bsw@0: case 19: return "name"; jbe/bsw@0: case 20: return "int8"; jbe/bsw@0: case 21: return "int2"; jbe/bsw@0: case 23: return "int4"; jbe/bsw@0: case 25: return "text"; jbe/bsw@0: case 26: return "oid"; jbe/bsw@0: case 27: return "tid"; jbe/bsw@0: case 28: return "xid"; jbe/bsw@0: case 29: return "cid"; jbe@176: case 114: return "json"; jbe/bsw@0: case 600: return "point"; jbe/bsw@0: case 601: return "lseg"; jbe/bsw@0: case 602: return "path"; jbe/bsw@0: case 603: return "box"; jbe/bsw@0: case 604: return "polygon"; jbe/bsw@0: case 628: return "line"; jbe/bsw@0: case 700: return "float4"; jbe/bsw@0: case 701: return "float8"; jbe/bsw@0: case 705: return "unknown"; jbe/bsw@0: case 718: return "circle"; jbe/bsw@0: case 790: return "money"; jbe/bsw@0: case 829: return "macaddr"; jbe/bsw@0: case 869: return "inet"; jbe/bsw@0: case 650: return "cidr"; jbe/bsw@0: case 1042: return "bpchar"; jbe/bsw@0: case 1043: return "varchar"; jbe/bsw@0: case 1082: return "date"; jbe/bsw@0: case 1083: return "time"; jbe/bsw@0: case 1114: return "timestamp"; jbe/bsw@0: case 1184: return "timestamptz"; jbe/bsw@0: case 1186: return "interval"; jbe/bsw@0: case 1266: return "timetz"; jbe/bsw@0: case 1560: return "bit"; jbe/bsw@0: case 1562: return "varbit"; jbe/bsw@0: case 1700: return "numeric"; jbe@176: case 3802: return "jsonb"; jbe/bsw@0: default: return NULL; jbe/bsw@0: } jbe/bsw@0: } jbe/bsw@0: jbe@23: // This library maps PostgreSQL's error codes to CamelCase string jbe@23: // identifiers, which consist of CamelCase identifiers and are seperated jbe@23: // by dots (".") (no leading or trailing dots). jbe@23: // There are additional error identifiers which do not have a corresponding jbe@23: // PostgreSQL error associated with it. jbe@23: jbe@23: // matching start of local variable 'pgcode' against string 'incode', jbe@23: // returning string 'outcode' on match: jbe/bsw@0: #define mondelefant_errcode_item(incode, outcode) \ jbe/bsw@0: if (!strncmp(pgcode, (incode), strlen(incode))) return outcode; else jbe/bsw@0: jbe@23: // additional error identifiers without corresponding PostgreSQL error: jbe/bsw@0: #define MONDELEFANT_ERRCODE_UNKNOWN "unknown" jbe/bsw@0: #define MONDELEFANT_ERRCODE_CONNECTION "ConnectionException" jbe/bsw@0: #define MONDELEFANT_ERRCODE_RESULTCOUNT_LOW "WrongResultSetCount.ResultSetMissing" jbe/bsw@0: #define MONDELEFANT_ERRCODE_RESULTCOUNT_HIGH "WrongResultSetCount.TooManyResults" jbe/bsw@0: #define MONDELEFANT_ERRCODE_QUERY1_NO_ROWS "NoData.OneRowExpected" jbe/bsw@0: #define MONDELEFANT_ERRCODE_QUERY1_MULTIPLE_ROWS "CardinalityViolation.OneRowExpected" jbe/bsw@0: jbe@23: // mapping PostgreSQL error code to error code as returned by this library: jbe/bsw@0: static const char *mondelefant_translate_errcode(const char *pgcode) { jbe/bsw@0: if (!pgcode) abort(); // should not happen jbe/bsw@0: mondelefant_errcode_item("02", "NoData") jbe/bsw@0: mondelefant_errcode_item("03", "SqlStatementNotYetComplete") jbe/bsw@0: mondelefant_errcode_item("08", "ConnectionException") jbe/bsw@0: mondelefant_errcode_item("09", "TriggeredActionException") jbe/bsw@0: mondelefant_errcode_item("0A", "FeatureNotSupported") jbe/bsw@0: mondelefant_errcode_item("0B", "InvalidTransactionInitiation") jbe/bsw@0: mondelefant_errcode_item("0F", "LocatorException") jbe/bsw@0: mondelefant_errcode_item("0L", "InvalidGrantor") jbe/bsw@0: mondelefant_errcode_item("0P", "InvalidRoleSpecification") jbe/bsw@0: mondelefant_errcode_item("21", "CardinalityViolation") jbe/bsw@0: mondelefant_errcode_item("22", "DataException") jbe/bsw@0: mondelefant_errcode_item("23001", "IntegrityConstraintViolation.RestrictViolation") jbe/bsw@0: mondelefant_errcode_item("23502", "IntegrityConstraintViolation.NotNullViolation") jbe/bsw@0: mondelefant_errcode_item("23503", "IntegrityConstraintViolation.ForeignKeyViolation") jbe/bsw@0: mondelefant_errcode_item("23505", "IntegrityConstraintViolation.UniqueViolation") jbe/bsw@0: mondelefant_errcode_item("23514", "IntegrityConstraintViolation.CheckViolation") jbe/bsw@0: mondelefant_errcode_item("23", "IntegrityConstraintViolation") jbe/bsw@0: mondelefant_errcode_item("24", "InvalidCursorState") jbe/bsw@0: mondelefant_errcode_item("25", "InvalidTransactionState") jbe/bsw@0: mondelefant_errcode_item("26", "InvalidSqlStatementName") jbe/bsw@0: mondelefant_errcode_item("27", "TriggeredDataChangeViolation") jbe/bsw@0: mondelefant_errcode_item("28", "InvalidAuthorizationSpecification") jbe/bsw@0: mondelefant_errcode_item("2B", "DependentPrivilegeDescriptorsStillExist") jbe/bsw@0: mondelefant_errcode_item("2D", "InvalidTransactionTermination") jbe/bsw@0: mondelefant_errcode_item("2F", "SqlRoutineException") jbe/bsw@0: mondelefant_errcode_item("34", "InvalidCursorName") jbe/bsw@0: mondelefant_errcode_item("38", "ExternalRoutineException") jbe/bsw@0: mondelefant_errcode_item("39", "ExternalRoutineInvocationException") jbe/bsw@0: mondelefant_errcode_item("3B", "SavepointException") jbe/bsw@0: mondelefant_errcode_item("3D", "InvalidCatalogName") jbe/bsw@0: mondelefant_errcode_item("3F", "InvalidSchemaName") jbe/bsw@0: mondelefant_errcode_item("40", "TransactionRollback") jbe/bsw@0: mondelefant_errcode_item("42", "SyntaxErrorOrAccessRuleViolation") jbe/bsw@0: mondelefant_errcode_item("44", "WithCheckOptionViolation") jbe/bsw@0: mondelefant_errcode_item("53", "InsufficientResources") jbe/bsw@0: mondelefant_errcode_item("54", "ProgramLimitExceeded") jbe/bsw@0: mondelefant_errcode_item("55", "ObjectNotInPrerequisiteState") jbe/bsw@0: mondelefant_errcode_item("57", "OperatorIntervention") jbe/bsw@0: mondelefant_errcode_item("58", "SystemError") jbe/bsw@0: mondelefant_errcode_item("F0", "ConfigurationFileError") jbe/bsw@0: mondelefant_errcode_item("P0", "PlpgsqlError") jbe/bsw@0: mondelefant_errcode_item("XX", "InternalError") jbe/bsw@0: return "unknown"; jbe/bsw@0: } jbe/bsw@0: jbe@23: // C-function, checking if a given error code (as defined by this library) jbe@23: // is belonging to a certain class of errors (strings are equal or error jbe@23: // code begins with error class followed by a dot): jbe/bsw@0: static int mondelefant_check_error_class( jbe/bsw@0: const char *errcode, const char *errclass jbe/bsw@0: ) { jbe/bsw@0: size_t i = 0; jbe/bsw@0: while (1) { jbe/bsw@0: if (errclass[i] == 0) { jbe/bsw@0: if (errcode[i] == 0 || errcode[i] == '.') return 1; jbe/bsw@0: else return 0; jbe/bsw@0: } jbe/bsw@0: if (errcode[i] != errclass[i]) return 0; jbe/bsw@0: i++; jbe/bsw@0: } jbe/bsw@0: } jbe/bsw@0: jbe@23: // pushing first line of a string on Lua's stack (without trailing CR/LF): jbe/bsw@0: static void mondelefant_push_first_line(lua_State *L, const char *str) { jbe/bsw@0: char *str2; jbe/bsw@0: size_t i = 0; jbe/bsw@0: if (!str) abort(); // should not happen jbe/bsw@0: str2 = strdup(str); jbe/bsw@0: while (1) { jbe/bsw@0: char c = str2[i]; jbe/bsw@0: if (c == '\n' || c == '\r' || c == 0) { str2[i] = 0; break; } jbe/bsw@0: i++; jbe/bsw@0: }; jbe/bsw@0: lua_pushstring(L, str2); jbe/bsw@0: free(str2); jbe/bsw@0: } jbe/bsw@0: jbe@23: // "connect" function of library, which establishes a database connection jbe@23: // and returns a database connection handle: jbe/bsw@0: static int mondelefant_connect(lua_State *L) { jbe@23: luaL_Buffer buf; // Lua string buffer to create 'conninfo' (see below) jbe@23: const char *conninfo; // string for PQconnectdb function jbe@23: PGconn *pgconn; // PGconn object as returned by PQconnectdb function jbe@23: mondelefant_conn_t *conn; // C-structure for userdata jbe@23: // if engine is anything but "postgresql", then raise error: jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: lua_getfield(L, 1, "engine"); // 2 jbe/bsw@0: if (!lua_toboolean(L, 2)) { jbe/bsw@0: return luaL_error(L, "No database engine selected."); jbe/bsw@0: } jbe/bsw@0: lua_pushliteral(L, "postgresql"); // 3 jbe/bsw@0: if (!lua_rawequal(L, 2, 3)) { jbe/bsw@0: return luaL_error(L, jbe/bsw@0: "Only database engine 'postgresql' is supported." jbe/bsw@0: ); jbe/bsw@0: } jbe@40: // copy conninfo string for PQconnectdb function from argument table to jbe@40: // stack position 2: jbe/bsw@0: lua_settop(L, 1); jbe@40: lua_getfield(L, 1, "conninfo"); // 2 jbe@40: // if no conninfo string was found, then assemble one from the named jbe@40: // options except "engine" option: jbe@40: if (!lua_toboolean(L, 2)) { jbe@40: lua_settop(L, 1); jbe@40: lua_pushnil(L); // slot for key at stack position 2 jbe@40: lua_pushnil(L); // slot for value at stack position 3 jbe@40: luaL_buffinit(L, &buf); jbe@40: { jbe@40: int need_seperator = 0; jbe@40: while (lua_pushvalue(L, 2), lua_next(L, 1)) { jbe@40: lua_replace(L, 3); jbe@40: lua_replace(L, 2); jbe@40: // NOTE: numbers will be converted to strings automatically here, jbe@40: // but perhaps this will change in future versions of lua jbe@40: luaL_argcheck(L, jbe@40: lua_isstring(L, 2) && lua_isstring(L, 3), 1, "non-string contained" jbe@40: ); jbe@40: lua_pushvalue(L, 2); jbe@40: lua_pushliteral(L, "engine"); jbe@40: if (!lua_rawequal(L, -2, -1)) { jbe@40: const char *value; jbe@40: size_t value_len; jbe@40: size_t value_pos = 0; jbe@40: lua_pop(L, 1); jbe@40: if (need_seperator) luaL_addchar(&buf, ' '); jbe@40: luaL_addvalue(&buf); jbe@40: luaL_addchar(&buf, '='); jbe@40: luaL_addchar(&buf, '\''); jbe@40: value = lua_tolstring(L, 3, &value_len); jbe@40: do { jbe@40: char c; jbe@40: c = value[value_pos++]; jbe@40: if (c == '\'') luaL_addchar(&buf, '\\'); jbe@40: luaL_addchar(&buf, c); jbe@40: } while (value_pos < value_len); jbe@40: luaL_addchar(&buf, '\''); jbe@40: need_seperator = 1; jbe@40: } else { jbe@40: lua_pop(L, 1); jbe@40: } jbe/bsw@0: } jbe/bsw@0: } jbe@40: luaL_pushresult(&buf); jbe@40: lua_replace(L, 2); jbe@40: lua_settop(L, 2); jbe/bsw@0: } jbe@40: // use conninfo string on stack position 2: jbe/bsw@0: conninfo = lua_tostring(L, 2); jbe@23: // call PQconnectdb function of libpq: jbe/bsw@0: pgconn = PQconnectdb(conninfo); jbe@23: // throw errors, if neccessary: jbe/bsw@0: if (!pgconn) { jbe/bsw@0: return luaL_error(L, jbe/bsw@0: "Error in libpq while creating 'PGconn' structure." jbe/bsw@0: ); jbe/bsw@0: } jbe/bsw@0: if (PQstatus(pgconn) != CONNECTION_OK) { jbe/bsw@0: const char *errmsg; jbe/bsw@0: lua_pushnil(L); jbe/bsw@0: errmsg = PQerrorMessage(pgconn); jbe/bsw@0: if (errmsg) { jbe/bsw@0: mondelefant_push_first_line(L, errmsg); jbe/bsw@0: } else { jbe/bsw@0: lua_pushliteral(L, jbe/bsw@0: "Error while connecting to database, but no error message given." jbe/bsw@0: ); jbe/bsw@0: } jbe/bsw@0: lua_pushliteral(L, MONDELEFANT_ERRCODE_CONNECTION); jbe/bsw@0: PQfinish(pgconn); jbe/bsw@0: return 3; jbe/bsw@0: } jbe@23: // create userdata: jbe/bsw@0: lua_settop(L, 0); jbe/bsw@0: conn = lua_newuserdata(L, sizeof(*conn)); // 1 jbe@23: // set 'pgconn' in C-struct of userdata: jbe/bsw@0: conn->pgconn = pgconn; jbe@23: // set 'server_encoding' in C-struct of userdata: jbe/bsw@0: { jbe/bsw@0: const char *charset; jbe/bsw@0: charset = PQparameterStatus(pgconn, "server_encoding"); jbe/bsw@0: if (charset && !strcmp(charset, "UTF8")) { jbe/bsw@0: conn->server_encoding = MONDELEFANT_SERVER_ENCODING_UTF8; jbe/bsw@0: } else { jbe/bsw@0: conn->server_encoding = MONDELEFANT_SERVER_ENCODING_ASCII; jbe/bsw@0: } jbe/bsw@0: } jbe@23: // set meta-table of userdata: jbe/bsw@0: luaL_getmetatable(L, MONDELEFANT_CONN_MT_REGKEY); // 2 jbe/bsw@0: lua_setmetatable(L, 1); jbe@23: // create entry in table storing connection specific data and associate jbe@23: // created userdata with it: jbe/bsw@0: lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CONN_DATA_REGKEY); // 2 jbe/bsw@0: lua_pushvalue(L, 1); // 3 jbe/bsw@0: lua_newtable(L); // 4 jbe/bsw@0: lua_settable(L, 2); jbe/bsw@0: lua_settop(L, 1); jbe@23: // store key "engine" with value "postgresql" as connection specific data: jbe/bsw@0: lua_pushliteral(L, "postgresql"); jbe/bsw@0: lua_setfield(L, 1, "engine"); jbe@23: // return userdata: jbe/bsw@0: return 1; jbe/bsw@0: } jbe/bsw@0: jbe@23: // returns pointer to libpq handle 'pgconn' of userdata at given index jbe@23: // (or throws error, if database connection has been closed): jbe/bsw@0: static mondelefant_conn_t *mondelefant_get_conn(lua_State *L, int index) { jbe/bsw@0: mondelefant_conn_t *conn; jbe/bsw@0: conn = luaL_checkudata(L, index, MONDELEFANT_CONN_MT_REGKEY); jbe/bsw@0: if (!conn->pgconn) { jbe/bsw@0: luaL_error(L, "PostgreSQL connection has been closed."); jbe/bsw@0: return NULL; jbe/bsw@0: } jbe/bsw@0: return conn; jbe/bsw@0: } jbe/bsw@0: jbe@23: // meta-method "__index" of database handles (userdata): jbe/bsw@0: static int mondelefant_conn_index(lua_State *L) { jbe@23: // try table for connection specific data: jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CONN_DATA_REGKEY); // 3 jbe/bsw@0: lua_pushvalue(L, 1); // 4 jbe/bsw@0: lua_gettable(L, 3); // 4 jbe/bsw@0: lua_remove(L, 3); // connection specific data-table at stack position 3 jbe/bsw@0: lua_pushvalue(L, 2); // 4 jbe/bsw@0: lua_gettable(L, 3); // 4 jbe/bsw@0: if (!lua_isnil(L, 4)) return 1; jbe@23: // try to use prototype stored in connection specific data: jbe/bsw@0: lua_settop(L, 3); jbe/bsw@0: lua_getfield(L, 3, "prototype"); // 4 jbe/bsw@0: if (lua_toboolean(L, 4)) { jbe/bsw@0: lua_pushvalue(L, 2); // 5 jbe/bsw@0: lua_gettable(L, 4); // 5 jbe/bsw@0: if (!lua_isnil(L, 5)) return 1; jbe/bsw@0: } jbe@23: // try to use "postgresql_connection_prototype" of library: jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_MODULE_REGKEY); // 3 jbe/bsw@0: lua_getfield(L, 3, "postgresql_connection_prototype"); // 4 jbe/bsw@0: if (lua_toboolean(L, 4)) { jbe/bsw@0: lua_pushvalue(L, 2); // 5 jbe/bsw@0: lua_gettable(L, 4); // 5 jbe/bsw@0: if (!lua_isnil(L, 5)) return 1; jbe/bsw@0: } jbe@23: // try to use "connection_prototype" of library: jbe/bsw@0: lua_settop(L, 3); jbe/bsw@0: lua_getfield(L, 3, "connection_prototype"); // 4 jbe/bsw@0: if (lua_toboolean(L, 4)) { jbe/bsw@0: lua_pushvalue(L, 2); // 5 jbe/bsw@0: lua_gettable(L, 4); // 5 jbe/bsw@0: if (!lua_isnil(L, 5)) return 1; jbe/bsw@0: } jbe@23: // give up and return nothing: jbe/bsw@0: return 0; jbe/bsw@0: } jbe/bsw@0: jbe@23: // meta-method "__newindex" of database handles (userdata): jbe/bsw@0: static int mondelefant_conn_newindex(lua_State *L) { jbe@23: // store key-value pair in table for connection specific data: jbe/bsw@0: lua_settop(L, 3); jbe/bsw@0: lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CONN_DATA_REGKEY); // 4 jbe/bsw@0: lua_pushvalue(L, 1); // 5 jbe/bsw@0: lua_gettable(L, 4); // 5 jbe@23: lua_remove(L, 4); // connection specific data-table at stack position 4 jbe/bsw@0: lua_pushvalue(L, 2); jbe/bsw@0: lua_pushvalue(L, 3); jbe/bsw@0: lua_settable(L, 4); jbe@23: // return nothing: jbe/bsw@0: return 0; jbe/bsw@0: } jbe/bsw@0: jbe@23: // meta-method "__gc" of database handles: jbe/bsw@0: static int mondelefant_conn_free(lua_State *L) { jbe/bsw@0: mondelefant_conn_t *conn; jbe/bsw@0: conn = luaL_checkudata(L, 1, MONDELEFANT_CONN_MT_REGKEY); jbe/bsw@0: if (conn->pgconn) PQfinish(conn->pgconn); jbe/bsw@0: conn->pgconn = NULL; jbe/bsw@0: return 0; jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "close" of database handles: jbe/bsw@0: static int mondelefant_conn_close(lua_State *L) { jbe/bsw@0: mondelefant_conn_t *conn; jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: conn = mondelefant_get_conn(L, 1); jbe/bsw@0: PQfinish(conn->pgconn); jbe/bsw@0: conn->pgconn = NULL; jbe/bsw@0: return 0; jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "is_okay" of database handles: jbe/bsw@0: static int mondelefant_conn_is_ok(lua_State *L) { jbe/bsw@0: mondelefant_conn_t *conn; jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: conn = mondelefant_get_conn(L, 1); jbe/bsw@0: lua_pushboolean(L, PQstatus(conn->pgconn) == CONNECTION_OK); jbe/bsw@0: return 1; jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "get_transaction_status" of database handles: jbe/bsw@0: static int mondelefant_conn_get_transaction_status(lua_State *L) { jbe/bsw@0: mondelefant_conn_t *conn; jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: conn = mondelefant_get_conn(L, 1); jbe/bsw@0: switch (PQtransactionStatus(conn->pgconn)) { jbe/bsw@0: case PQTRANS_IDLE: jbe/bsw@0: lua_pushliteral(L, "idle"); jbe/bsw@0: break; jbe/bsw@0: case PQTRANS_ACTIVE: jbe/bsw@0: lua_pushliteral(L, "active"); jbe/bsw@0: break; jbe/bsw@0: case PQTRANS_INTRANS: jbe/bsw@0: lua_pushliteral(L, "intrans"); jbe/bsw@0: break; jbe/bsw@0: case PQTRANS_INERROR: jbe/bsw@0: lua_pushliteral(L, "inerror"); jbe/bsw@0: break; jbe/bsw@0: default: jbe/bsw@0: lua_pushliteral(L, "unknown"); jbe/bsw@0: } jbe/bsw@0: return 1; jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "create_list" of database handles: jbe/bsw@0: static int mondelefant_conn_create_list(lua_State *L) { jbe@23: // ensure that first argument is a database connection: jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: luaL_checkudata(L, 1, MONDELEFANT_CONN_MT_REGKEY); jbe@23: // if no second argument is given, use an empty table: jbe/bsw@0: if (!lua_toboolean(L, 2)) { jbe/bsw@0: lua_newtable(L); jbe/bsw@0: lua_replace(L, 2); // new result at stack position 2 jbe/bsw@0: } jbe@23: // set meta-table for database result lists/objects: jbe/bsw@0: lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_RESULT_MT_REGKEY); // 3 jbe/bsw@0: lua_setmetatable(L, 2); jbe@23: // set "_connection" attribute to self: jbe/bsw@0: lua_pushvalue(L, 1); // 3 jbe/bsw@0: lua_setfield(L, 2, "_connection"); jbe@23: // set "_type" attribute to string "list": jbe/bsw@0: lua_pushliteral(L, "list"); // 3 jbe/bsw@0: lua_setfield(L, 2, "_type"); jbe@23: // return created database result list: jbe/bsw@0: return 1; jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "create_object" of database handles: jbe/bsw@0: static int mondelefant_conn_create_object(lua_State *L) { jbe@23: // ensure that first argument is a database connection: jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: luaL_checkudata(L, 1, MONDELEFANT_CONN_MT_REGKEY); jbe@23: // if no second argument is given, use an empty table: jbe/bsw@0: if (!lua_toboolean(L, 2)) { jbe/bsw@0: lua_newtable(L); jbe/bsw@0: lua_replace(L, 2); // new result at stack position 2 jbe/bsw@0: } jbe@23: // set meta-table for database result lists/objects: jbe/bsw@0: lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_RESULT_MT_REGKEY); // 3 jbe/bsw@0: lua_setmetatable(L, 2); jbe@23: // set "_connection" attribute to self: jbe/bsw@0: lua_pushvalue(L, 1); // 3 jbe/bsw@0: lua_setfield(L, 2, "_connection"); jbe@23: // set "_type" attribute to string "object": jbe/bsw@0: lua_pushliteral(L, "object"); // 3 jbe/bsw@0: lua_setfield(L, 2, "_type"); // "object" or "list" jbe@23: // create empty tables for "_data", "_dirty" and "_ref" attributes: jbe/bsw@0: lua_newtable(L); // 3 jbe/bsw@0: lua_setfield(L, 2, "_data"); jbe/bsw@0: lua_newtable(L); // 3 jbe/bsw@0: lua_setfield(L, 2, "_dirty"); jbe/bsw@0: lua_newtable(L); // 3 jbe/bsw@0: lua_setfield(L, 2, "_ref"); // nil=no info, false=nil, else table jbe@23: // return created database result object: jbe/bsw@0: return 1; jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "quote_string" of database handles: jbe/bsw@0: static int mondelefant_conn_quote_string(lua_State *L) { jbe/bsw@0: mondelefant_conn_t *conn; jbe/bsw@0: const char *input; jbe/bsw@0: size_t input_len; jbe/bsw@0: char *output; jbe/bsw@0: size_t output_len; jbe@23: // get 'conn' attribute of C-struct of database connection: jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: conn = mondelefant_get_conn(L, 1); jbe@23: // get second argument, which must be a string: jbe/bsw@0: input = luaL_checklstring(L, 2, &input_len); jbe@23: // throw error, if string is too long: jbe/bsw@0: if (input_len > (SIZE_MAX / sizeof(char) - 3) / 2) { jbe/bsw@0: return luaL_error(L, "String to be escaped is too long."); jbe/bsw@0: } jbe@23: // allocate memory for quoted string: jbe/bsw@0: output = malloc((2 * input_len + 3) * sizeof(char)); jbe/bsw@0: if (!output) { jbe/bsw@0: return luaL_error(L, "Could not allocate memory for string quoting."); jbe/bsw@0: } jbe@23: // do escaping by calling PQescapeStringConn and enclosing result with jbe@23: // single quotes: jbe/bsw@0: output[0] = '\''; jbe/bsw@0: output_len = PQescapeStringConn( jbe/bsw@0: conn->pgconn, output + 1, input, input_len, NULL jbe/bsw@0: ); jbe/bsw@0: output[output_len + 1] = '\''; jbe/bsw@0: output[output_len + 2] = 0; jbe@23: // create Lua string: jbe/bsw@0: lua_pushlstring(L, output, output_len + 2); jbe@23: // free allocated memory: jbe/bsw@0: free(output); jbe@23: // return Lua string: jbe/bsw@0: return 1; jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "quote_binary" of database handles: jbe/bsw@0: static int mondelefant_conn_quote_binary(lua_State *L) { jbe/bsw@0: mondelefant_conn_t *conn; jbe/bsw@0: const char *input; jbe/bsw@0: size_t input_len; jbe/bsw@0: char *output; jbe/bsw@0: size_t output_len; jbe/bsw@0: luaL_Buffer buf; jbe@23: // get 'conn' attribute of C-struct of database connection: jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: conn = mondelefant_get_conn(L, 1); jbe@23: // get second argument, which must be a string: jbe/bsw@0: input = luaL_checklstring(L, 2, &input_len); jbe@23: // call PQescapeByteaConn, which allocates memory itself: jbe/bsw@0: output = (char *)PQescapeByteaConn( jbe/bsw@0: conn->pgconn, (const unsigned char *)input, input_len, &output_len jbe/bsw@0: ); jbe@23: // if PQescapeByteaConn returned NULL, then throw error: jbe/bsw@0: if (!output) { jbe/bsw@0: return luaL_error(L, "Could not allocate memory for binary quoting."); jbe/bsw@0: } jbe@23: // create Lua string enclosed by single quotes: jbe/bsw@0: luaL_buffinit(L, &buf); jbe/bsw@0: luaL_addchar(&buf, '\''); jbe/bsw@0: luaL_addlstring(&buf, output, output_len - 1); jbe/bsw@0: luaL_addchar(&buf, '\''); jbe/bsw@0: luaL_pushresult(&buf); jbe@23: // free memory allocated by PQescapeByteaConn: jbe/bsw@0: PQfreemem(output); jbe@23: // return Lua string: jbe/bsw@0: return 1; jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "assemble_command" of database handles: jbe/bsw@0: static int mondelefant_conn_assemble_command(lua_State *L) { jbe/bsw@0: mondelefant_conn_t *conn; jbe/bsw@0: int paramidx = 2; jbe/bsw@0: const char *template; jbe/bsw@0: size_t template_pos = 0; jbe/bsw@0: luaL_Buffer buf; jbe@23: // get 'conn' attribute of C-struct of database connection: jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: conn = mondelefant_get_conn(L, 1); jbe@23: // if second argument is a string, return this string: jbe/bsw@0: if (lua_isstring(L, 2)) { jbe/bsw@0: lua_tostring(L, 2); jbe/bsw@0: return 1; jbe/bsw@0: } jbe@23: // if second argument has __tostring meta-method, jbe@23: // then use this method and return its result: jbe/bsw@0: if (luaL_callmeta(L, 2, "__tostring")) return 1; jbe@23: // otherwise, require that second argument is a table: jbe/bsw@0: luaL_checktype(L, 2, LUA_TTABLE); jbe@23: // get first element of table, which must be a string: jbe/bsw@0: lua_rawgeti(L, 2, 1); // 3 jbe/bsw@0: luaL_argcheck(L, jbe/bsw@0: lua_isstring(L, 3), jbe/bsw@0: 2, jbe/bsw@0: "First entry of SQL command structure is not a string." jbe/bsw@0: ); jbe/bsw@0: template = lua_tostring(L, 3); jbe@23: // get value of "input_converter" attribute of database connection: jbe/bsw@0: lua_pushliteral(L, "input_converter"); // 4 jbe/bsw@0: lua_gettable(L, 1); // input_converter at stack position 4 jbe@23: // reserve space on Lua stack: jbe/bsw@0: lua_pushnil(L); // free space at stack position 5 jbe/bsw@0: lua_pushnil(L); // free space at stack position 6 jbe@23: // initialize Lua buffer for result string: jbe/bsw@0: luaL_buffinit(L, &buf); jbe@23: // fill buffer in loop: jbe/bsw@0: while (1) { jbe@23: // variable declaration: jbe/bsw@0: char c; jbe@23: // get next character: jbe/bsw@0: c = template[template_pos++]; jbe@23: // break, when character is NULL byte: jbe/bsw@0: if (!c) break; jbe@23: // question-mark and dollar-sign are special characters: jbe@23: if (c == '?' || c == '$') { // special character found jbe@23: // check, if same character follows: jbe@23: if (template[template_pos] == c) { // special character is escaped jbe@23: // consume two characters of input and add one character to buffer: jbe/bsw@0: template_pos++; jbe/bsw@0: luaL_addchar(&buf, c); jbe@23: } else { // special character is not escaped jbe/bsw@0: luaL_Buffer keybuf; jbe/bsw@0: int subcmd; jbe@23: // set 'subcmd' = true, if special character was a dollar-sign, jbe@23: // set 'subcmd' = false, if special character was a question-mark: jbe/bsw@0: subcmd = (c == '$'); jbe@23: // read any number of alpha numeric chars or underscores jbe@23: // and store them on Lua stack: jbe/bsw@0: luaL_buffinit(L, &keybuf); jbe/bsw@0: while (1) { jbe/bsw@0: c = template[template_pos]; jbe/bsw@0: if ( jbe/bsw@0: (c < 'A' || c > 'Z') && jbe/bsw@0: (c < 'a' || c > 'z') && jbe/bsw@0: (c < '0' || c > '9') && jbe/bsw@0: (c != '_') jbe/bsw@0: ) break; jbe/bsw@0: luaL_addchar(&keybuf, c); jbe/bsw@0: template_pos++; jbe/bsw@0: } jbe/bsw@0: luaL_pushresult(&keybuf); jbe@23: // check, if any characters matched: jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: if (lua_rawlen(L, -1)) { jbe@64: #else jbe/bsw@0: if (lua_objlen(L, -1)) { jbe@64: #endif jbe@23: // if any alpha numeric chars or underscores were found, jbe@23: // push them on stack as a Lua string and use them to lookup jbe@23: // value from second argument: jbe/bsw@0: lua_pushvalue(L, -1); // save key on stack jbe@23: lua_gettable(L, 2); // fetch value (raw-value) jbe/bsw@0: } else { jbe@23: // otherwise push nil and use numeric lookup based on 'paramidx': jbe/bsw@0: lua_pop(L, 1); jbe/bsw@0: lua_pushnil(L); // put nil on key position jbe@23: lua_rawgeti(L, 2, paramidx++); // fetch value (raw-value) jbe/bsw@0: } jbe@23: // Lua stack contains: ..., , key, raw-value jbe@23: // branch according to type of special character ("?" or "$"): jbe@23: if (subcmd) { // dollar-sign jbe/bsw@0: size_t i; jbe/bsw@0: size_t count; jbe@23: // store fetched value (which is supposed to be sub-structure) jbe@23: // on Lua stack position 5 and drop key: jbe@23: lua_replace(L, 5); jbe@23: lua_pop(L, 1); jbe@23: // Lua stack contains: ..., jbe@23: // check, if fetched value is really a sub-structure: jbe/bsw@0: luaL_argcheck(L, jbe/bsw@0: !lua_isnil(L, 5), jbe/bsw@0: 2, jbe/bsw@0: "SQL sub-structure not found." jbe/bsw@0: ); jbe/bsw@0: luaL_argcheck(L, jbe/bsw@0: lua_type(L, 5) == LUA_TTABLE, jbe/bsw@0: 2, jbe/bsw@0: "SQL sub-structure must be a table." jbe/bsw@0: ); jbe@23: // Lua stack contains: ..., jbe@23: // get value of "sep" attribute of sub-structure, jbe@23: // and place it on Lua stack position 6: jbe/bsw@0: lua_getfield(L, 5, "sep"); jbe@23: lua_replace(L, 6); jbe@23: // if seperator is nil, then use ", " as default, jbe@23: // if seperator is neither nil nor a string, then throw error: jbe/bsw@0: if (lua_isnil(L, 6)) { jbe/bsw@0: lua_pushstring(L, ", "); jbe/bsw@0: lua_replace(L, 6); jbe/bsw@0: } else { jbe/bsw@0: luaL_argcheck(L, jbe/bsw@0: lua_isstring(L, 6), jbe/bsw@0: 2, jbe/bsw@0: "Seperator of SQL sub-structure has to be a string." jbe/bsw@0: ); jbe/bsw@0: } jbe@23: // iterate over items of sub-structure: jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: count = lua_rawlen(L, 5); jbe@64: #else jbe/bsw@0: count = lua_objlen(L, 5); jbe@64: #endif jbe/bsw@0: for (i = 0; i < count; i++) { jbe@23: // add seperator, unless this is the first run: jbe/bsw@0: if (i) { jbe/bsw@0: lua_pushvalue(L, 6); jbe/bsw@0: luaL_addvalue(&buf); jbe/bsw@0: } jbe@23: // recursivly apply assemble function and add results to buffer: jbe/bsw@0: lua_pushcfunction(L, mondelefant_conn_assemble_command); jbe/bsw@0: lua_pushvalue(L, 1); jbe/bsw@0: lua_rawgeti(L, 5, i+1); jbe/bsw@0: lua_call(L, 2, 1); jbe/bsw@0: luaL_addvalue(&buf); jbe/bsw@0: } jbe@23: } else { // question-mark jbe/bsw@0: if (lua_toboolean(L, 4)) { jbe@23: // call input_converter with connection handle, raw-value and jbe@23: // an info-table which contains a "field_name" entry with the jbe@23: // used key: jbe/bsw@0: lua_pushvalue(L, 4); jbe/bsw@0: lua_pushvalue(L, 1); jbe/bsw@0: lua_pushvalue(L, -3); jbe/bsw@0: lua_newtable(L); jbe/bsw@0: lua_pushvalue(L, -6); jbe/bsw@0: lua_setfield(L, -2, "field_name"); jbe/bsw@0: lua_call(L, 3, 1); jbe@23: // Lua stack contains: ..., , key, raw-value, final-value jbe@23: // remove key and raw-value: jbe/bsw@0: lua_remove(L, -2); jbe/bsw@0: lua_remove(L, -2); jbe@23: // Lua stack contains: ..., , final-value jbe@23: // throw error, if final-value is not a string: jbe/bsw@0: if (!lua_isstring(L, -1)) { jbe/bsw@0: return luaL_error(L, "input_converter returned non-string."); jbe/bsw@0: } jbe/bsw@0: } else { jbe@23: // remove key from stack: jbe/bsw@0: lua_remove(L, -2); jbe@23: // Lua stack contains: ..., , raw-value jbe@23: // branch according to type of value: jbe@23: if (lua_isnil(L, -1)) { // value is nil jbe@23: // push string "NULL" to stack: jbe/bsw@0: lua_pushliteral(L, "NULL"); jbe@23: } else if (lua_type(L, -1) == LUA_TBOOLEAN) { // value is boolean jbe@23: // push strings "TRUE" or "FALSE" to stack: jbe/bsw@0: lua_pushstring(L, lua_toboolean(L, -1) ? "TRUE" : "FALSE"); jbe@23: } else if (lua_isstring(L, -1)) { // value is string or number jbe/bsw@0: // NOTE: In this version of lua a number will be converted jbe@23: // push output of "quote_string" method of database jbe@23: // connection to stack: jbe/bsw@0: lua_tostring(L, -1); jbe/bsw@0: lua_pushcfunction(L, mondelefant_conn_quote_string); jbe/bsw@0: lua_pushvalue(L, 1); jbe/bsw@0: lua_pushvalue(L, -3); jbe/bsw@0: lua_call(L, 2, 1); jbe@23: } else { // value is of other type jbe@23: // throw error: jbe/bsw@0: return luaL_error(L, jbe/bsw@0: "Unable to convert SQL value due to unknown type " jbe/bsw@0: "or missing input_converter." jbe/bsw@0: ); jbe/bsw@0: } jbe@23: // Lua stack contains: ..., , raw-value, final-value jbe@23: // remove raw-value: jbe/bsw@0: lua_remove(L, -2); jbe@23: // Lua stack contains: ..., , final-value jbe/bsw@0: } jbe@23: // append final-value to buffer: jbe/bsw@0: luaL_addvalue(&buf); jbe/bsw@0: } jbe/bsw@0: } jbe@23: } else { // character is not special jbe@23: // just copy character: jbe/bsw@0: luaL_addchar(&buf, c); jbe/bsw@0: } jbe/bsw@0: } jbe@23: // return string in buffer: jbe/bsw@0: luaL_pushresult(&buf); jbe/bsw@0: return 1; jbe/bsw@0: } jbe/bsw@0: jbe@23: // max number of SQL statements executed by one "query" method call: jbe/bsw@0: #define MONDELEFANT_MAX_COMMAND_COUNT 64 jbe@23: // max number of columns in a database result: jbe/bsw@0: #define MONDELEFANT_MAX_COLUMN_COUNT 1024 jbe@23: // enum values for 'modes' array in C-function below: jbe/bsw@0: #define MONDELEFANT_QUERY_MODE_LIST 1 jbe/bsw@0: #define MONDELEFANT_QUERY_MODE_OBJECT 2 jbe/bsw@0: #define MONDELEFANT_QUERY_MODE_OPT_OBJECT 3 jbe/bsw@0: jbe@23: // method "try_query" of database handles: jbe/bsw@0: static int mondelefant_conn_try_query(lua_State *L) { jbe/bsw@0: mondelefant_conn_t *conn; jbe/bsw@0: int command_count; jbe/bsw@0: int command_idx; jbe/bsw@0: int modes[MONDELEFANT_MAX_COMMAND_COUNT]; jbe/bsw@0: luaL_Buffer buf; jbe/bsw@0: int sent_success; jbe/bsw@0: PGresult *res; jbe/bsw@0: int rows, cols, row, col; jbe@23: // get 'conn' attribute of C-struct of database connection: jbe/bsw@0: conn = mondelefant_get_conn(L, 1); jbe@23: // calculate number of commands (2 arguments for one command): jbe/bsw@0: command_count = lua_gettop(L) / 2; jbe@23: // push nil on stack, which is needed, if last mode was ommitted: jbe@23: lua_pushnil(L); jbe@23: // throw error, if number of commands is too high: jbe/bsw@0: if (command_count > MONDELEFANT_MAX_COMMAND_COUNT) { jbe/bsw@0: return luaL_error(L, "Exceeded maximum command count in one query."); jbe/bsw@0: } jbe@23: // create SQL string, store query modes and push SQL string on stack: jbe/bsw@0: luaL_buffinit(L, &buf); jbe/bsw@0: for (command_idx = 0; command_idx < command_count; command_idx++) { jbe/bsw@0: int mode; jbe/bsw@0: int mode_idx; // stack index of mode string jbe/bsw@0: if (command_idx) luaL_addchar(&buf, ' '); jbe/bsw@0: lua_pushcfunction(L, mondelefant_conn_assemble_command); jbe/bsw@0: lua_pushvalue(L, 1); jbe/bsw@0: lua_pushvalue(L, 2 + 2 * command_idx); jbe/bsw@0: lua_call(L, 2, 1); jbe/bsw@0: luaL_addvalue(&buf); jbe/bsw@0: luaL_addchar(&buf, ';'); jbe/bsw@0: mode_idx = 3 + 2 * command_idx; jbe/bsw@0: if (lua_isnil(L, mode_idx)) { jbe/bsw@0: mode = MONDELEFANT_QUERY_MODE_LIST; jbe/bsw@0: } else { jbe/bsw@0: const char *modestr; jbe/bsw@0: modestr = luaL_checkstring(L, mode_idx); jbe/bsw@0: if (!strcmp(modestr, "list")) { jbe/bsw@0: mode = MONDELEFANT_QUERY_MODE_LIST; jbe/bsw@0: } else if (!strcmp(modestr, "object")) { jbe/bsw@0: mode = MONDELEFANT_QUERY_MODE_OBJECT; jbe/bsw@0: } else if (!strcmp(modestr, "opt_object")) { jbe/bsw@0: mode = MONDELEFANT_QUERY_MODE_OPT_OBJECT; jbe/bsw@0: } else { jbe/bsw@0: return luaL_error(L, "Unknown query mode specified."); jbe/bsw@0: } jbe/bsw@0: } jbe/bsw@0: modes[command_idx] = mode; jbe/bsw@0: } jbe/bsw@0: luaL_pushresult(&buf); // stack position unknown jbe/bsw@0: lua_replace(L, 2); // SQL command string to stack position 2 jbe@23: // call sql_tracer, if set: jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_getfield(L, 1, "sql_tracer"); // tracer at stack position 3 jbe/bsw@0: if (lua_toboolean(L, 3)) { jbe/bsw@0: lua_pushvalue(L, 1); // 4 jbe/bsw@0: lua_pushvalue(L, 2); // 5 jbe/bsw@0: lua_call(L, 2, 1); // trace callback at stack position 3 jbe/bsw@0: } jbe@23: // NOTE: If no tracer was found, then nil or false is stored at stack jbe@23: // position 3. jbe@23: // call PQsendQuery function and store result in 'sent_success' variable: jbe/bsw@0: sent_success = PQsendQuery(conn->pgconn, lua_tostring(L, 2)); jbe@23: // create preliminary result table: jbe/bsw@0: lua_newtable(L); // results in table at stack position 4 jbe@23: // iterate over results using function PQgetResult to fill result table: jbe/bsw@0: for (command_idx = 0; ; command_idx++) { jbe/bsw@0: int mode; jbe/bsw@0: char binary[MONDELEFANT_MAX_COLUMN_COUNT]; jbe/bsw@0: ExecStatusType pgstatus; jbe@23: // fetch mode which was given for the command: jbe/bsw@0: mode = modes[command_idx]; jbe@23: // if PQsendQuery call was successful, then fetch result data: jbe/bsw@0: if (sent_success) { jbe@23: // NOTE: PQgetResult called one extra time. Break only, if all jbe@23: // queries have been processed and PQgetResult returned NULL. jbe/bsw@0: res = PQgetResult(conn->pgconn); jbe/bsw@0: if (command_idx >= command_count && !res) break; jbe/bsw@0: if (res) { jbe/bsw@0: pgstatus = PQresultStatus(res); jbe/bsw@0: rows = PQntuples(res); jbe/bsw@0: cols = PQnfields(res); jbe/bsw@0: } jbe/bsw@0: } jbe@23: // handle errors: jbe/bsw@0: if ( jbe/bsw@0: !sent_success || command_idx >= command_count || !res || jbe/bsw@0: (pgstatus != PGRES_TUPLES_OK && pgstatus != PGRES_COMMAND_OK) || jbe/bsw@0: (rows < 1 && mode == MONDELEFANT_QUERY_MODE_OBJECT) || jbe/bsw@0: (rows > 1 && mode != MONDELEFANT_QUERY_MODE_LIST) jbe/bsw@0: ) { jbe/bsw@0: const char *command; jbe/bsw@0: command = lua_tostring(L, 2); jbe/bsw@0: lua_newtable(L); // 5 jbe/bsw@0: lua_getfield(L, jbe/bsw@0: LUA_REGISTRYINDEX, jbe/bsw@0: MONDELEFANT_ERROROBJECT_MT_REGKEY jbe/bsw@0: ); jbe/bsw@0: lua_setmetatable(L, 5); jbe/bsw@0: lua_pushvalue(L, 1); jbe/bsw@0: lua_setfield(L, 5, "connection"); jbe/bsw@0: lua_pushinteger(L, command_idx + 1); jbe/bsw@0: lua_setfield(L, 5, "command_number"); jbe/bsw@0: lua_pushvalue(L, 2); jbe/bsw@0: lua_setfield(L, 5, "sql_command"); jbe/bsw@0: if (!res) { jbe/bsw@0: lua_pushliteral(L, MONDELEFANT_ERRCODE_RESULTCOUNT_LOW); jbe/bsw@0: lua_setfield(L, 5, "code"); jbe/bsw@0: lua_pushliteral(L, "Received too few database result sets."); jbe/bsw@0: lua_setfield(L, 5, "message"); jbe/bsw@0: } else if (command_idx >= command_count) { jbe/bsw@0: lua_pushliteral(L, MONDELEFANT_ERRCODE_RESULTCOUNT_HIGH); jbe/bsw@0: lua_setfield(L, 5, "code"); jbe/bsw@0: lua_pushliteral(L, "Received too many database result sets."); jbe/bsw@0: lua_setfield(L, 5, "message"); jbe/bsw@0: } else if ( jbe/bsw@0: pgstatus != PGRES_TUPLES_OK && pgstatus != PGRES_COMMAND_OK jbe/bsw@0: ) { jbe/bsw@0: const char *sqlstate; jbe/bsw@0: const char *errmsg; jbe/bsw@0: lua_pushstring(L, PQresultErrorField(res, PG_DIAG_SEVERITY)); jbe/bsw@0: lua_setfield(L, 5, "pg_severity"); jbe/bsw@0: sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE); jbe/bsw@0: if (sqlstate) { jbe/bsw@0: lua_pushstring(L, sqlstate); jbe/bsw@0: lua_setfield(L, 5, "pg_sqlstate"); jbe/bsw@0: lua_pushstring(L, mondelefant_translate_errcode(sqlstate)); jbe/bsw@0: lua_setfield(L, 5, "code"); jbe/bsw@0: } else { jbe/bsw@0: lua_pushliteral(L, MONDELEFANT_ERRCODE_UNKNOWN); jbe/bsw@0: lua_setfield(L, 5, "code"); jbe/bsw@0: } jbe/bsw@0: errmsg = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY); jbe/bsw@0: if (errmsg) { jbe/bsw@0: mondelefant_push_first_line(L, errmsg); jbe/bsw@0: lua_setfield(L, 5, "message"); jbe/bsw@0: lua_pushstring(L, errmsg); jbe/bsw@0: lua_setfield(L, 5, "pg_message_primary"); jbe/bsw@0: } else { jbe/bsw@0: lua_pushliteral(L, jbe/bsw@0: "Error while fetching result, but no error message given." jbe/bsw@0: ); jbe/bsw@0: lua_setfield(L, 5, "message"); jbe/bsw@0: } jbe/bsw@0: lua_pushstring(L, PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL)); jbe/bsw@0: lua_setfield(L, 5, "pg_message_detail"); jbe/bsw@0: lua_pushstring(L, PQresultErrorField(res, PG_DIAG_MESSAGE_HINT)); jbe/bsw@0: lua_setfield(L, 5, "pg_message_hint"); jbe/bsw@0: // NOTE: "position" and "pg_internal_position" are recalculated to jbe/bsw@0: // byte offsets, as Lua 5.1 is not Unicode aware. jbe/bsw@0: { jbe/bsw@0: char *tmp; jbe/bsw@0: tmp = PQresultErrorField(res, PG_DIAG_STATEMENT_POSITION); jbe/bsw@0: if (tmp) { jbe/bsw@0: int pos; jbe/bsw@0: pos = atoi(tmp) - 1; jbe/bsw@0: if (conn->server_encoding == MONDELEFANT_SERVER_ENCODING_UTF8) { jbe/bsw@0: pos = utf8_position_to_byte(command, pos); jbe/bsw@0: } jbe/bsw@0: lua_pushinteger(L, pos + 1); jbe/bsw@0: lua_setfield(L, 5, "position"); jbe/bsw@0: } jbe/bsw@0: } jbe/bsw@0: { jbe/bsw@0: const char *internal_query; jbe/bsw@0: internal_query = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY); jbe/bsw@0: lua_pushstring(L, internal_query); jbe/bsw@0: lua_setfield(L, 5, "pg_internal_query"); jbe/bsw@0: char *tmp; jbe/bsw@0: tmp = PQresultErrorField(res, PG_DIAG_INTERNAL_POSITION); jbe/bsw@0: if (tmp) { jbe/bsw@0: int pos; jbe/bsw@0: pos = atoi(tmp) - 1; jbe/bsw@0: if (conn->server_encoding == MONDELEFANT_SERVER_ENCODING_UTF8) { jbe/bsw@0: pos = utf8_position_to_byte(internal_query, pos); jbe/bsw@0: } jbe/bsw@0: lua_pushinteger(L, pos + 1); jbe/bsw@0: lua_setfield(L, 5, "pg_internal_position"); jbe/bsw@0: } jbe/bsw@0: } jbe/bsw@0: lua_pushstring(L, PQresultErrorField(res, PG_DIAG_CONTEXT)); jbe/bsw@0: lua_setfield(L, 5, "pg_context"); jbe/bsw@0: lua_pushstring(L, PQresultErrorField(res, PG_DIAG_SOURCE_FILE)); jbe/bsw@0: lua_setfield(L, 5, "pg_source_file"); jbe/bsw@0: lua_pushstring(L, PQresultErrorField(res, PG_DIAG_SOURCE_LINE)); jbe/bsw@0: lua_setfield(L, 5, "pg_source_line"); jbe/bsw@0: lua_pushstring(L, PQresultErrorField(res, PG_DIAG_SOURCE_FUNCTION)); jbe/bsw@0: lua_setfield(L, 5, "pg_source_function"); jbe/bsw@0: } else if (rows < 1 && mode == MONDELEFANT_QUERY_MODE_OBJECT) { jbe/bsw@0: lua_pushliteral(L, MONDELEFANT_ERRCODE_QUERY1_NO_ROWS); jbe/bsw@0: lua_setfield(L, 5, "code"); jbe/bsw@0: lua_pushliteral(L, "Expected one row, but got empty set."); jbe/bsw@0: lua_setfield(L, 5, "message"); jbe/bsw@0: } else if (rows > 1 && mode != MONDELEFANT_QUERY_MODE_LIST) { jbe/bsw@0: lua_pushliteral(L, MONDELEFANT_ERRCODE_QUERY1_MULTIPLE_ROWS); jbe/bsw@0: lua_setfield(L, 5, "code"); jbe/bsw@0: lua_pushliteral(L, "Got more than one result row."); jbe/bsw@0: lua_setfield(L, 5, "message"); jbe/bsw@0: } else { jbe/bsw@0: // should not happen jbe/bsw@0: abort(); jbe/bsw@0: } jbe/bsw@0: if (res) { jbe/bsw@0: PQclear(res); jbe/bsw@0: while ((res = PQgetResult(conn->pgconn))) PQclear(res); jbe/bsw@0: } jbe/bsw@0: if (lua_toboolean(L, 3)) { jbe/bsw@0: lua_pushvalue(L, 3); jbe/bsw@0: lua_pushvalue(L, 5); jbe/bsw@0: lua_call(L, 1, 0); jbe/bsw@0: } jbe/bsw@0: return 1; jbe/bsw@0: } jbe@23: // call "create_list" or "create_object" method of database handle, jbe@23: // result will be at stack position 5: jbe/bsw@0: if (modes[command_idx] == MONDELEFANT_QUERY_MODE_LIST) { jbe/bsw@0: lua_pushcfunction(L, mondelefant_conn_create_list); // 5 jbe/bsw@0: lua_pushvalue(L, 1); // 6 jbe/bsw@0: lua_call(L, 1, 1); // 5 jbe/bsw@0: } else { jbe/bsw@0: lua_pushcfunction(L, mondelefant_conn_create_object); // 5 jbe/bsw@0: lua_pushvalue(L, 1); // 6 jbe/bsw@0: lua_call(L, 1, 1); // 5 jbe/bsw@0: } jbe@23: // set "_column_info": jbe@23: lua_newtable(L); // 6 jbe/bsw@0: for (col = 0; col < cols; col++) { jbe/bsw@0: lua_newtable(L); // 7 jbe/bsw@0: lua_pushstring(L, PQfname(res, col)); jbe/bsw@0: lua_setfield(L, 7, "field_name"); jbe/bsw@0: { jbe/bsw@0: Oid tmp; jbe/bsw@0: tmp = PQftable(res, col); jbe/bsw@0: if (tmp == InvalidOid) lua_pushnil(L); jbe/bsw@0: else lua_pushinteger(L, tmp); jbe/bsw@0: lua_setfield(L, 7, "table_oid"); jbe/bsw@0: } jbe/bsw@0: { jbe/bsw@0: int tmp; jbe/bsw@0: tmp = PQftablecol(res, col); jbe/bsw@0: if (tmp == 0) lua_pushnil(L); jbe/bsw@0: else lua_pushinteger(L, tmp); jbe/bsw@0: lua_setfield(L, 7, "table_column_number"); jbe/bsw@0: } jbe/bsw@0: { jbe/bsw@0: Oid tmp; jbe/bsw@0: tmp = PQftype(res, col); jbe/bsw@0: binary[col] = (tmp == MONDELEFANT_POSTGRESQL_BINARY_OID); jbe/bsw@0: lua_pushinteger(L, tmp); jbe/bsw@0: lua_setfield(L, 7, "type_oid"); jbe/bsw@0: lua_pushstring(L, mondelefant_oid_to_typestr(tmp)); jbe/bsw@0: lua_setfield(L, 7, "type"); jbe/bsw@0: } jbe/bsw@0: { jbe/bsw@0: int tmp; jbe/bsw@0: tmp = PQfmod(res, col); jbe/bsw@0: if (tmp == -1) lua_pushnil(L); jbe/bsw@0: else lua_pushinteger(L, tmp); jbe/bsw@0: lua_setfield(L, 7, "type_modifier"); jbe/bsw@0: } jbe/bsw@0: lua_rawseti(L, 6, col+1); jbe/bsw@0: } jbe@23: lua_setfield(L, 5, "_column_info"); jbe@23: // set "_rows_affected": jbe/bsw@0: { jbe/bsw@0: char *tmp; jbe/bsw@0: tmp = PQcmdTuples(res); jbe/bsw@0: if (tmp[0]) { jbe/bsw@0: lua_pushinteger(L, atoi(tmp)); jbe/bsw@0: lua_setfield(L, 5, "_rows_affected"); jbe/bsw@0: } jbe/bsw@0: } jbe@23: // set "_oid": jbe/bsw@0: { jbe/bsw@0: Oid tmp; jbe/bsw@0: tmp = PQoidValue(res); jbe/bsw@0: if (tmp != InvalidOid) { jbe/bsw@0: lua_pushinteger(L, tmp); jbe/bsw@0: lua_setfield(L, 5, "_oid"); jbe/bsw@0: } jbe/bsw@0: } jbe@23: // copy data as strings or nil, while performing binary unescaping jbe@23: // automatically: jbe/bsw@0: if (modes[command_idx] == MONDELEFANT_QUERY_MODE_LIST) { jbe/bsw@0: for (row = 0; row < rows; row++) { jbe/bsw@0: lua_pushcfunction(L, mondelefant_conn_create_object); // 6 jbe/bsw@0: lua_pushvalue(L, 1); // 7 jbe/bsw@0: lua_call(L, 1, 1); // 6 jbe/bsw@0: for (col = 0; col < cols; col++) { jbe/bsw@0: if (PQgetisnull(res, row, col)) { jbe/bsw@0: lua_pushnil(L); jbe/bsw@0: } else if (binary[col]) { jbe/bsw@0: size_t binlen; jbe/bsw@0: char *binval; jbe/bsw@0: binval = (char *)PQunescapeBytea( jbe/bsw@0: (unsigned char *)PQgetvalue(res, row, col), &binlen jbe/bsw@0: ); jbe/bsw@0: if (!binval) { jbe/bsw@0: return luaL_error(L, jbe/bsw@0: "Could not allocate memory for binary unescaping." jbe/bsw@0: ); jbe/bsw@0: } jbe/bsw@0: lua_pushlstring(L, binval, binlen); jbe/bsw@0: PQfreemem(binval); jbe/bsw@0: } else { jbe/bsw@0: lua_pushstring(L, PQgetvalue(res, row, col)); jbe/bsw@0: } jbe/bsw@0: lua_rawseti(L, 6, col+1); jbe/bsw@0: } jbe/bsw@0: lua_rawseti(L, 5, row+1); jbe/bsw@0: } jbe/bsw@0: } else if (rows == 1) { jbe/bsw@0: for (col = 0; col < cols; col++) { jbe/bsw@0: if (PQgetisnull(res, 0, col)) { jbe/bsw@0: lua_pushnil(L); jbe/bsw@0: } else if (binary[col]) { jbe/bsw@0: size_t binlen; jbe/bsw@0: char *binval; jbe/bsw@0: binval = (char *)PQunescapeBytea( jbe/bsw@0: (unsigned char *)PQgetvalue(res, 0, col), &binlen jbe/bsw@0: ); jbe/bsw@0: if (!binval) { jbe/bsw@0: return luaL_error(L, jbe/bsw@0: "Could not allocate memory for binary unescaping." jbe/bsw@0: ); jbe/bsw@0: } jbe/bsw@0: lua_pushlstring(L, binval, binlen); jbe/bsw@0: PQfreemem(binval); jbe/bsw@0: } else { jbe/bsw@0: lua_pushstring(L, PQgetvalue(res, 0, col)); jbe/bsw@0: } jbe/bsw@0: lua_rawseti(L, 5, col+1); jbe/bsw@0: } jbe/bsw@0: } else { jbe/bsw@0: // no row in optrow mode jbe/bsw@0: lua_pop(L, 1); jbe/bsw@0: lua_pushnil(L); jbe/bsw@0: } jbe@23: // save result in result list: jbe/bsw@0: lua_rawseti(L, 4, command_idx+1); jbe@23: // extra assertion: jbe/bsw@0: if (lua_gettop(L) != 4) abort(); // should not happen jbe@23: // free memory acquired by libpq: jbe/bsw@0: PQclear(res); jbe/bsw@0: } jbe/bsw@0: // trace callback at stack position 3 jbe/bsw@0: // result at stack position 4 (top of stack) jbe@23: // if a trace callback is existent, then call: jbe/bsw@0: if (lua_toboolean(L, 3)) { jbe/bsw@0: lua_pushvalue(L, 3); jbe/bsw@0: lua_call(L, 0, 0); jbe/bsw@0: } jbe@23: // put result at stack position 3: jbe@23: lua_replace(L, 3); jbe@23: // get output converter to stack position 4: jbe@23: lua_getfield(L, 1, "output_converter"); jbe@374: // get mutability state saver to stack position 5: jbe@375: lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_MODULE_REGKEY); jbe@375: lua_getfield(L, -1, "save_mutability_state"); jbe@375: lua_replace(L, -2); jbe@23: // apply output converters and fill "_data" table according to column names: jbe/bsw@0: for (command_idx = 0; command_idx < command_count; command_idx++) { jbe/bsw@0: int mode; jbe/bsw@0: mode = modes[command_idx]; jbe@374: lua_rawgeti(L, 3, command_idx+1); // raw result at stack position 6 jbe@374: if (lua_toboolean(L, 6)) { jbe@374: lua_getfield(L, 6, "_column_info"); // column_info list at position 7 jbe@64: #if LUA_VERSION_NUM >= 502 jbe@374: cols = lua_rawlen(L, 7); jbe@64: #else jbe@374: cols = lua_objlen(L, 7); jbe@64: #endif jbe/bsw@0: if (mode == MONDELEFANT_QUERY_MODE_LIST) { jbe@64: #if LUA_VERSION_NUM >= 502 jbe@374: rows = lua_rawlen(L, 6); jbe@64: #else jbe@374: rows = lua_objlen(L, 6); jbe@64: #endif jbe/bsw@0: for (row = 0; row < rows; row++) { jbe@374: lua_rawgeti(L, 6, row+1); // row at stack position 8 jbe@374: lua_getfield(L, 8, "_data"); // _data table at stack position 9 jbe@375: lua_getfield(L, 8, "_dirty"); // _dirty table at stack position 10 jbe/bsw@0: for (col = 0; col < cols; col++) { jbe@375: lua_rawgeti(L, 7, col+1); // this column info at position 11 jbe@375: lua_getfield(L, 11, "field_name"); // 12 jbe/bsw@0: if (lua_toboolean(L, 4)) { jbe/bsw@0: lua_pushvalue(L, 4); // output-converter jbe/bsw@0: lua_pushvalue(L, 1); // connection jbe@374: lua_rawgeti(L, 8, col+1); // raw-value jbe@375: lua_pushvalue(L, 11); // this column info jbe@375: lua_call(L, 3, 1); // converted value at position 13 jbe/bsw@0: } else { jbe@375: lua_rawgeti(L, 8, col+1); // raw-value at position 13 jbe/bsw@0: } jbe@375: if (lua_toboolean(L, 5)) { // handle mutable values? jbe@375: lua_pushvalue(L, 12); // copy of field name jbe@375: lua_pushvalue(L, 5); // mutability state saver function jbe@375: lua_pushvalue(L, 13); // copy of value jbe@375: lua_call(L, 1, 1); // calculated mutability state of value jbe@375: lua_rawset(L, 10); // store mutability state in _dirty table jbe@375: } jbe@375: lua_pushvalue(L, 13); // 14 jbe@374: lua_rawseti(L, 8, col+1); jbe@374: lua_rawset(L, 9); jbe@374: lua_settop(L, 9); jbe/bsw@0: } jbe@374: lua_settop(L, 7); jbe/bsw@0: } jbe/bsw@0: } else { jbe@374: lua_getfield(L, 6, "_data"); // _data table at stack position 8 jbe@375: lua_getfield(L, 6, "_dirty"); // _dirty table at stack position 9 jbe/bsw@0: for (col = 0; col < cols; col++) { jbe@375: lua_rawgeti(L, 7, col+1); // this column info at position 10 jbe@375: lua_getfield(L, 10, "field_name"); // 11 jbe/bsw@0: if (lua_toboolean(L, 4)) { jbe/bsw@0: lua_pushvalue(L, 4); // output-converter jbe/bsw@0: lua_pushvalue(L, 1); // connection jbe@374: lua_rawgeti(L, 6, col+1); // raw-value jbe@375: lua_pushvalue(L, 10); // this column info jbe@375: lua_call(L, 3, 1); // converted value at position 12 jbe/bsw@0: } else { jbe@375: lua_rawgeti(L, 6, col+1); // raw-value at position 12 jbe/bsw@0: } jbe@375: if (lua_toboolean(L, 5)) { // handle mutable values? jbe@375: lua_pushvalue(L, 11); // copy of field name jbe@375: lua_pushvalue(L, 5); // mutability state saver function jbe@375: lua_pushvalue(L, 12); // copy of value jbe@375: lua_call(L, 1, 1); // calculated mutability state of value jbe@375: lua_rawset(L, 9); // store mutability state in _dirty table jbe@375: } jbe@375: lua_pushvalue(L, 12); // 13 jbe@374: lua_rawseti(L, 6, col+1); jbe@374: lua_rawset(L, 8); jbe@374: lua_settop(L, 8); jbe/bsw@0: } jbe/bsw@0: } jbe/bsw@0: } jbe@374: lua_settop(L, 5); jbe/bsw@0: } jbe@23: // return nil as first result value, followed by result lists/objects: jbe/bsw@0: lua_settop(L, 3); jbe/bsw@0: lua_pushnil(L); jbe/bsw@0: for (command_idx = 0; command_idx < command_count; command_idx++) { jbe/bsw@0: lua_rawgeti(L, 3, command_idx+1); jbe/bsw@0: } jbe/bsw@0: return command_count+1; jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "escalate" of error objects: jbe/bsw@0: static int mondelefant_errorobject_escalate(lua_State *L) { jbe@23: // check, if we may throw an error object instead of an error string: jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: lua_getfield(L, 1, "connection"); // 2 jbe/bsw@0: lua_getfield(L, 2, "error_objects"); // 3 jbe/bsw@0: if (lua_toboolean(L, 3)) { jbe@23: // throw error object: jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: return lua_error(L); jbe/bsw@0: } else { jbe@23: // throw error string: jbe/bsw@0: lua_getfield(L, 1, "message"); // 4 jbe/bsw@0: if (lua_isnil(L, 4)) { jbe/bsw@0: return luaL_error(L, "No error message given for escalation."); jbe/bsw@0: } jbe/bsw@0: return lua_error(L); jbe/bsw@0: } jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "is_kind_of" of error objects: jbe/bsw@0: static int mondelefant_errorobject_is_kind_of(lua_State *L) { jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_getfield(L, 1, "code"); // 3 jbe/bsw@0: if (lua_isstring(L, 3)) { jbe/bsw@0: lua_pushboolean(L, jbe/bsw@0: mondelefant_check_error_class( jbe/bsw@0: lua_tostring(L, 3), luaL_checkstring(L, 2) jbe/bsw@0: ) jbe/bsw@0: ); jbe/bsw@0: } else { jbe/bsw@0: // only happens for errors where code is not set jbe/bsw@0: lua_pushboolean(L, 0); jbe/bsw@0: } jbe/bsw@0: return 1; jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "query" of database handles: jbe/bsw@0: static int mondelefant_conn_query(lua_State *L) { jbe/bsw@0: int argc; jbe@23: // count number of arguments: jbe/bsw@0: argc = lua_gettop(L); jbe@23: // insert "try_query" function/method at stack position 1: jbe/bsw@0: lua_pushcfunction(L, mondelefant_conn_try_query); jbe@23: lua_insert(L, 1); jbe@23: // call "try_query" method: jbe@23: lua_call(L, argc, LUA_MULTRET); // results (with error) starting at index 1 jbe@23: // check, if error occurred: jbe@23: if (lua_toboolean(L, 1)) { jbe@23: // invoke escalate method of error object: jbe/bsw@0: lua_pushcfunction(L, mondelefant_errorobject_escalate); jbe@23: lua_pushvalue(L, 1); jbe/bsw@0: lua_call(L, 1, 0); // will raise an error jbe/bsw@0: return 0; // should not be executed jbe/bsw@0: } else { jbe@23: // return everything but nil error object: jbe@23: return lua_gettop(L) - 1; jbe/bsw@0: } jbe/bsw@0: } jbe/bsw@0: jbe@23: // library function "set_class": jbe/bsw@0: static int mondelefant_set_class(lua_State *L) { jbe@23: // ensure that first argument is a database result list/object: jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_getmetatable(L, 1); // 3 jbe/bsw@0: lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_RESULT_MT_REGKEY); // 4 jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: luaL_argcheck(L, lua_compare(L, 3, 4, LUA_OPEQ), 1, "not a database result"); jbe@64: #else jbe/bsw@0: luaL_argcheck(L, lua_equal(L, 3, 4), 1, "not a database result"); jbe@64: #endif jbe@23: // ensure that second argument is a database class (model): jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_getmetatable(L, 2); // 3 jbe/bsw@0: lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CLASS_MT_REGKEY); // 4 jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: luaL_argcheck(L, lua_compare(L, 3, 4, LUA_OPEQ), 2, "not a database class"); jbe@64: #else jbe/bsw@0: luaL_argcheck(L, lua_equal(L, 3, 4), 2, "not a database class"); jbe@64: #endif jbe@23: // set attribute "_class" of result list/object to given class: jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_pushvalue(L, 2); // 3 jbe/bsw@0: lua_setfield(L, 1, "_class"); jbe@23: // test, if database result is a list (and not a single object): jbe/bsw@0: lua_getfield(L, 1, "_type"); // 3 jbe/bsw@0: lua_pushliteral(L, "list"); // 4 jbe/bsw@0: if (lua_rawequal(L, 3, 4)) { jbe/bsw@0: int i; jbe@23: // set attribute "_class" of all elements to given class: jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: for (i=0; i < lua_rawlen(L, 1); i++) { jbe@64: #else jbe/bsw@0: for (i=0; i < lua_objlen(L, 1); i++) { jbe@64: #endif jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_rawgeti(L, 1, i+1); // 3 jbe/bsw@0: lua_pushvalue(L, 2); // 4 jbe/bsw@0: lua_setfield(L, 3, "_class"); jbe/bsw@0: } jbe/bsw@0: } jbe@23: // return first argument: jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: return 1; jbe/bsw@0: } jbe/bsw@0: jbe@23: // library function "new_class": jbe/bsw@0: static int mondelefant_new_class(lua_State *L) { jbe@23: // use first argument as template or create new table: jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: if (!lua_toboolean(L, 1)) { jbe/bsw@0: lua_settop(L, 0); jbe/bsw@0: lua_newtable(L); // 1 jbe/bsw@0: } jbe@23: // set meta-table for database classes (models): jbe/bsw@0: lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CLASS_MT_REGKEY); // 2 jbe/bsw@0: lua_setmetatable(L, 1); jbe@23: // check, if "prototype" attribute is not set: jbe/bsw@0: lua_pushliteral(L, "prototype"); // 2 jbe/bsw@0: lua_rawget(L, 1); // 2 jbe/bsw@0: if (!lua_toboolean(L, 2)) { jbe@23: // set "prototype" attribute to default prototype: jbe/bsw@0: lua_pushliteral(L, "prototype"); // 3 jbe/bsw@0: lua_getfield(L, jbe/bsw@0: LUA_REGISTRYINDEX, jbe/bsw@0: MONDELEFANT_CLASS_PROTO_REGKEY jbe/bsw@0: ); // 4 jbe/bsw@0: lua_rawset(L, 1); jbe/bsw@0: } jbe@23: // set "object" attribute to empty table, unless it is already set: jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: lua_pushliteral(L, "object"); // 2 jbe/bsw@0: lua_rawget(L, 1); // 2 jbe/bsw@0: if (!lua_toboolean(L, 2)) { jbe/bsw@0: lua_pushliteral(L, "object"); // 3 jbe/bsw@0: lua_newtable(L); // 4 jbe/bsw@0: lua_rawset(L, 1); jbe/bsw@0: } jbe@23: // set "object_get" attribute to empty table, unless it is already set: jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: lua_pushliteral(L, "object_get"); // 2 jbe/bsw@0: lua_rawget(L, 1); // 2 jbe/bsw@0: if (!lua_toboolean(L, 2)) { jbe/bsw@0: lua_pushliteral(L, "object_get"); // 3 jbe/bsw@0: lua_newtable(L); // 4 jbe/bsw@0: lua_rawset(L, 1); jbe/bsw@0: } jbe@23: // set "object_set" attribute to empty table, unless it is already set: jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: lua_pushliteral(L, "object_set"); // 2 jbe/bsw@0: lua_rawget(L, 1); // 2 jbe/bsw@0: if (!lua_toboolean(L, 2)) { jbe/bsw@0: lua_pushliteral(L, "object_set"); // 3 jbe/bsw@0: lua_newtable(L); // 4 jbe/bsw@0: lua_rawset(L, 1); jbe/bsw@0: } jbe@23: // set "list" attribute to empty table, unless it is already set: jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: lua_pushliteral(L, "list"); // 2 jbe/bsw@0: lua_rawget(L, 1); // 2 jbe/bsw@0: if (!lua_toboolean(L, 2)) { jbe/bsw@0: lua_pushliteral(L, "list"); // 3 jbe/bsw@0: lua_newtable(L); // 4 jbe/bsw@0: lua_rawset(L, 1); jbe/bsw@0: } jbe@23: // set "references" attribute to empty table, unless it is already set: jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: lua_pushliteral(L, "references"); // 2 jbe/bsw@0: lua_rawget(L, 1); // 2 jbe/bsw@0: if (!lua_toboolean(L, 2)) { jbe/bsw@0: lua_pushliteral(L, "references"); // 3 jbe/bsw@0: lua_newtable(L); // 4 jbe/bsw@0: lua_rawset(L, 1); jbe/bsw@0: } jbe@23: // set "foreign_keys" attribute to empty table, unless it is already set: jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: lua_pushliteral(L, "foreign_keys"); // 2 jbe/bsw@0: lua_rawget(L, 1); // 2 jbe/bsw@0: if (!lua_toboolean(L, 2)) { jbe/bsw@0: lua_pushliteral(L, "foreign_keys"); // 3 jbe/bsw@0: lua_newtable(L); // 4 jbe/bsw@0: lua_rawset(L, 1); jbe/bsw@0: } jbe@23: // return table: jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: return 1; jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "get_reference" of classes (models): jbe/bsw@0: static int mondelefant_class_get_reference(lua_State *L) { jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: while (lua_toboolean(L, 1)) { jbe@23: // get "references" table: jbe/bsw@0: lua_getfield(L, 1, "references"); // 3 jbe@23: // perform lookup: jbe/bsw@0: lua_pushvalue(L, 2); // 4 jbe/bsw@0: lua_gettable(L, 3); // 4 jbe@23: // return result, if lookup was successful: jbe/bsw@0: if (!lua_isnil(L, 4)) return 1; jbe@23: // replace current table by its prototype: jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_pushliteral(L, "prototype"); // 3 jbe/bsw@0: lua_rawget(L, 1); // 3 jbe/bsw@0: lua_replace(L, 1); jbe/bsw@0: } jbe@23: // return nothing: jbe/bsw@0: return 0; jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "iterate_over_references" of classes (models): jbe/bsw@0: static int mondelefant_class_iterate_over_references(lua_State *L) { jbe/bsw@0: return luaL_error(L, "Reference iterator not implemented yet."); // TODO jbe/bsw@0: } jbe/bsw@0: jbe@23: // method "get_foreign_key_reference_name" of classes (models): jbe/bsw@0: static int mondelefant_class_get_foreign_key_reference_name(lua_State *L) { jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: while (lua_toboolean(L, 1)) { jbe@23: // get "foreign_keys" table: jbe/bsw@0: lua_getfield(L, 1, "foreign_keys"); // 3 jbe@23: // perform lookup: jbe/bsw@0: lua_pushvalue(L, 2); // 4 jbe/bsw@0: lua_gettable(L, 3); // 4 jbe@23: // return result, if lookup was successful: jbe/bsw@0: if (!lua_isnil(L, 4)) return 1; jbe@23: // replace current table by its prototype: jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_pushliteral(L, "prototype"); // 3 jbe/bsw@0: lua_rawget(L, 1); // 3 jbe/bsw@0: lua_replace(L, 1); jbe/bsw@0: } jbe@23: // return nothing: jbe/bsw@0: return 0; jbe/bsw@0: } jbe/bsw@0: jbe@23: // meta-method "__index" of database result lists and objects: jbe/bsw@0: static int mondelefant_result_index(lua_State *L) { jbe/bsw@0: const char *result_type; jbe@23: // only lookup, when key is a string not beginning with an underscore: jbe/bsw@0: if (lua_type(L, 2) != LUA_TSTRING || lua_tostring(L, 2)[0] == '_') { jbe@23: return 0; jbe/bsw@0: } jbe@23: // get value of "_class" attribute, or default class, when unset: jbe@23: lua_settop(L, 2); jbe/bsw@0: lua_getfield(L, 1, "_class"); // 3 jbe/bsw@0: if (!lua_toboolean(L, 3)) { jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_getfield(L, jbe/bsw@0: LUA_REGISTRYINDEX, jbe/bsw@0: MONDELEFANT_CLASS_PROTO_REGKEY jbe/bsw@0: ); // 3 jbe/bsw@0: } jbe@23: // get value of "_type" attribute: jbe/bsw@0: lua_getfield(L, 1, "_type"); // 4 jbe/bsw@0: result_type = lua_tostring(L, 4); jbe@23: // different lookup for lists and objects: jbe@23: if (result_type && !strcmp(result_type, "object")) { // object jbe/bsw@0: lua_settop(L, 3); jbe@376: lua_pushnil(L); jbe@376: lua_insert(L, 3); // optional document column name on stack position 3 jbe@376: // try inherited attributes, methods or getter functions jbe@376: // (and determine optional document column, if existent): jbe@376: while (lua_toboolean(L, 4)) { // class on stack position 4 (due to insert) jbe@376: lua_getfield(L, 4, "object"); // 5 jbe@376: lua_pushvalue(L, 2); // 6 jbe@376: lua_gettable(L, 5); // 6 jbe@376: if (!lua_isnil(L, 6)) return 1; jbe@376: lua_settop(L, 4); jbe@376: lua_getfield(L, 4, "object_get"); // 5 jbe@376: lua_pushvalue(L, 2); // 6 jbe@376: lua_gettable(L, 5); // 6 jbe@376: if (lua_toboolean(L, 6)) { jbe@376: lua_pushvalue(L, 1); // 7 jbe@376: lua_call(L, 1, 1); // 6 jbe/bsw@0: return 1; jbe/bsw@0: } jbe@376: lua_settop(L, 4); jbe@376: if (lua_isnil(L, 3)) { jbe@376: lua_getfield(L, 4, "document_column"); // 5 jbe@376: lua_replace(L, 3); jbe@376: } jbe@376: lua_pushliteral(L, "prototype"); // 5 jbe@376: lua_rawget(L, 4); // 5 jbe@376: lua_replace(L, 4); jbe/bsw@0: } jbe@376: lua_settop(L, 3); jbe/bsw@0: // try primary keys of referenced objects: jbe/bsw@0: lua_pushcfunction(L, jbe/bsw@0: mondelefant_class_get_foreign_key_reference_name jbe@376: ); // 4 jbe@376: lua_getfield(L, 1, "_class"); // 5 jbe@376: lua_pushvalue(L, 2); // 6 jbe@376: lua_call(L, 2, 1); // 4 jbe@376: if (!lua_isnil(L, 4)) { jbe@376: // reference name at stack position 4 jbe@376: lua_pushcfunction(L, mondelefant_class_get_reference); // 5 jbe@376: lua_getfield(L, 1, "_class"); // 6 jbe@376: lua_pushvalue(L, 4); // 7 jbe@376: lua_call(L, 2, 1); // reference info at stack position 5 jbe@376: lua_getfield(L, 1, "_ref"); // 6 jbe@376: lua_getfield(L, 4, "ref"); // 7 jbe@376: lua_gettable(L, 6); // 7 jbe@376: if (!lua_isnil(L, 7)) { jbe@376: if (lua_toboolean(L, 7)) { jbe@376: lua_getfield(L, 5, "that_key"); // 8 jbe@376: if (lua_isnil(L, 8)) { jbe/bsw@0: return luaL_error(L, "Missing 'that_key' entry in model reference."); jbe/bsw@0: } jbe@376: lua_gettable(L, 7); // 8 jbe/bsw@0: } else { jbe/bsw@0: lua_pushnil(L); jbe/bsw@0: } jbe/bsw@0: return 1; jbe/bsw@0: } jbe/bsw@0: } jbe@376: lua_settop(L, 3); jbe/bsw@0: // try normal data field info: jbe@377: lua_getfield(L, 1, "_data"); // _data table on stack position 4 jbe@376: lua_pushvalue(L, 2); // 5 jbe@376: lua_gettable(L, 4); // 5 jbe@376: if (!lua_isnil(L, 5)) return 1; jbe@377: lua_settop(L, 4); // _data table kept on stack position 4 for later use jbe/bsw@0: // try cached referenced object (or cached NULL reference): jbe@377: lua_getfield(L, 1, "_ref"); // 5 jbe@377: lua_pushvalue(L, 2); // 6 jbe@377: lua_gettable(L, 5); // 6 jbe@377: if (lua_isboolean(L, 6) && !lua_toboolean(L, 6)) { jbe/bsw@0: lua_pushnil(L); jbe/bsw@0: return 1; jbe@377: } else if (!lua_isnil(L, 6)) { jbe/bsw@0: return 1; jbe/bsw@0: } jbe@377: lua_settop(L, 4); jbe/bsw@0: // try to load a referenced object: jbe@377: lua_pushcfunction(L, mondelefant_class_get_reference); // 5 jbe@377: lua_getfield(L, 1, "_class"); // 6 jbe@377: lua_pushvalue(L, 2); // 7 jbe@377: lua_call(L, 2, 1); // 5 jbe@377: if (!lua_isnil(L, 5)) { jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_getfield(L, 1, "load"); // 3 jbe/bsw@0: lua_pushvalue(L, 1); // 4 (self) jbe/bsw@0: lua_pushvalue(L, 2); // 5 jbe/bsw@0: lua_call(L, 2, 0); jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_getfield(L, 1, "_ref"); // 3 jbe/bsw@0: lua_pushvalue(L, 2); // 4 jbe/bsw@0: lua_gettable(L, 3); // 4 jbe/bsw@0: if (lua_isboolean(L, 4) && !lua_toboolean(L, 4)) lua_pushnil(L); // TODO: use special object instead of false jbe/bsw@0: return 1; jbe/bsw@0: } jbe@377: lua_settop(L, 4); jbe@376: // try proxy access to document in special column: jbe@376: if (lua_toboolean(L, 3)) { jbe@377: lua_insert(L, 3); // switch stack values on position 3 and 4 jbe@377: // _data table is on stack position 3 jbe@377: // document column name is on stack position 4 jbe@376: lua_gettable(L, 3); // 4 jbe@376: if (!lua_isnil(L, 4)) { jbe@376: lua_pushvalue(L, 2); // 5 jbe@376: lua_gettable(L, 4); // 5 jbe@376: if (!lua_isnil(L, 5)) return 1; jbe@376: } jbe@376: } jbe/bsw@0: return 0; jbe@23: } else if (result_type && !strcmp(result_type, "list")) { // list jbe/bsw@0: lua_settop(L, 3); jbe/bsw@0: // try inherited list attributes or methods: jbe/bsw@0: while (lua_toboolean(L, 3)) { jbe/bsw@0: lua_getfield(L, 3, "list"); // 4 jbe/bsw@0: lua_pushvalue(L, 2); // 5 jbe/bsw@0: lua_gettable(L, 4); // 5 jbe/bsw@0: if (!lua_isnil(L, 5)) return 1; jbe/bsw@0: lua_settop(L, 3); jbe/bsw@0: lua_pushliteral(L, "prototype"); // 4 jbe/bsw@0: lua_rawget(L, 3); // 4 jbe/bsw@0: lua_replace(L, 3); jbe/bsw@0: } jbe/bsw@0: } jbe@23: // return nothing: jbe/bsw@0: return 0; jbe/bsw@0: } jbe/bsw@0: jbe@23: // meta-method "__newindex" of database result lists and objects: jbe/bsw@0: static int mondelefant_result_newindex(lua_State *L) { jbe/bsw@0: const char *result_type; jbe@23: // perform rawset, unless key is a string not starting with underscore: jbe/bsw@0: lua_settop(L, 3); jbe/bsw@0: if (lua_type(L, 2) != LUA_TSTRING || lua_tostring(L, 2)[0] == '_') { jbe/bsw@0: lua_rawset(L, 1); jbe/bsw@0: return 1; jbe/bsw@0: } jbe@23: // get value of "_class" attribute, or default class, when unset: jbe/bsw@0: lua_getfield(L, 1, "_class"); // 4 jbe/bsw@0: if (!lua_toboolean(L, 4)) { jbe/bsw@0: lua_settop(L, 3); jbe/bsw@0: lua_getfield(L, jbe/bsw@0: LUA_REGISTRYINDEX, jbe/bsw@0: MONDELEFANT_CLASS_PROTO_REGKEY jbe/bsw@0: ); // 4 jbe/bsw@0: } jbe@23: // get value of "_type" attribute: jbe/bsw@0: lua_getfield(L, 1, "_type"); // 5 jbe/bsw@0: result_type = lua_tostring(L, 5); jbe@378: // distinguish between lists and objects: jbe@378: if (result_type && !strcmp(result_type, "object")) { // objects jbe@378: lua_settop(L, 4); jbe@378: lua_pushnil(L); jbe@378: lua_insert(L, 4); // optional document column name on stack position 4 jbe@378: // try object setter functions: jbe@378: // (and determine optional document column, if existent): jbe@378: while (lua_toboolean(L, 5)) { // class on stack position 5 (due to insert) jbe@378: lua_getfield(L, 5, "object_set"); // 6 jbe@378: lua_pushvalue(L, 2); // 7 jbe@378: lua_gettable(L, 6); // 7 jbe@378: if (lua_toboolean(L, 7)) { jbe@378: lua_pushvalue(L, 1); // 8 jbe@378: lua_pushvalue(L, 3); // 9 jbe@378: lua_call(L, 2, 0); jbe@378: return 0; jbe@378: } jbe@378: lua_settop(L, 5); jbe@378: lua_pushliteral(L, "prototype"); // 6 jbe@378: lua_rawget(L, 5); // 6 jbe@378: lua_replace(L, 5); jbe@378: } jbe@378: lua_settop(L, 4); jbe@378: lua_getfield(L, 1, "_data"); // _data table on stack position 5 jbe/bsw@0: // check, if a object reference is changed: jbe@378: lua_pushcfunction(L, mondelefant_class_get_reference); // 6 jbe@378: lua_getfield(L, 1, "_class"); // 7 jbe@378: lua_pushvalue(L, 2); // 8 jbe@378: lua_call(L, 2, 1); // 6 jbe@378: if (!lua_isnil(L, 6)) { jbe/bsw@0: // store object in _ref table (use false for nil): // TODO: use special object instead of false jbe@378: lua_getfield(L, 1, "_ref"); // 7 jbe@378: lua_pushvalue(L, 2); // 8 jbe@378: if (lua_isnil(L, 3)) lua_pushboolean(L, 0); // 9 jbe@378: else lua_pushvalue(L, 3); // 9 jbe@378: lua_settable(L, 7); jbe@378: lua_settop(L, 6); jbe/bsw@0: // delete referencing key from _data table: jbe@378: lua_getfield(L, 6, "this_key"); // 7 jbe@378: if (lua_isnil(L, 7)) { jbe/bsw@0: return luaL_error(L, "Missing 'this_key' entry in model reference."); jbe/bsw@0: } jbe@378: //lua_getfield(L, 1, "_data"); // 6 jbe@378: lua_pushvalue(L, 7); // 8 jbe@378: lua_pushnil(L); // 9 jbe@378: lua_settable(L, 5); jbe@378: lua_settop(L, 7); jbe@378: lua_getfield(L, 1, "_dirty"); // 8 jbe@378: lua_pushvalue(L, 7); // 9 jbe@378: lua_pushboolean(L, 1); // 10 jbe@378: lua_settable(L, 5); jbe/bsw@0: return 0; jbe/bsw@0: } jbe@378: lua_settop(L, 5); jbe/bsw@0: // store value in data field info: jbe@378: lua_pushvalue(L, 2); // 6 jbe@378: lua_pushvalue(L, 3); // 7 jbe@378: lua_settable(L, 5); jbe/bsw@0: lua_settop(L, 3); jbe/bsw@0: // mark field as dirty (needs to be UPDATEd on save): jbe/bsw@0: lua_getfield(L, 1, "_dirty"); // 4 jbe/bsw@0: lua_pushvalue(L, 2); // 5 jbe/bsw@0: lua_pushboolean(L, 1); // 6 jbe/bsw@0: lua_settable(L, 4); jbe/bsw@0: lua_settop(L, 3); jbe/bsw@0: // reset reference cache, if neccessary: jbe/bsw@0: lua_pushcfunction(L, jbe/bsw@0: mondelefant_class_get_foreign_key_reference_name jbe/bsw@0: ); // 4 jbe/bsw@0: lua_getfield(L, 1, "_class"); // 5 jbe/bsw@0: lua_pushvalue(L, 2); // 6 jbe/bsw@0: lua_call(L, 2, 1); // 4 jbe/bsw@0: if (!lua_isnil(L, 4)) { jbe/bsw@0: lua_getfield(L, 1, "_ref"); // 5 jbe/bsw@0: lua_pushvalue(L, 4); // 6 jbe/bsw@0: lua_pushnil(L); // 7 jbe/bsw@0: lua_settable(L, 5); jbe/bsw@0: } jbe/bsw@0: return 0; jbe@23: } else { // non-objects (i.e. lists) jbe@23: // perform rawset: jbe/bsw@0: lua_settop(L, 3); jbe/bsw@0: lua_rawset(L, 1); jbe/bsw@0: return 0; jbe/bsw@0: } jbe/bsw@0: } jbe/bsw@0: jbe@23: // meta-method "__index" of classes (models): jbe/bsw@0: static int mondelefant_class_index(lua_State *L) { jbe@23: // perform lookup in prototype: jbe/bsw@0: lua_settop(L, 2); jbe/bsw@0: lua_pushliteral(L, "prototype"); // 3 jbe/bsw@0: lua_rawget(L, 1); // 3 jbe/bsw@0: lua_pushvalue(L, 2); // 4 jbe/bsw@0: lua_gettable(L, 3); // 4 jbe/bsw@0: return 1; jbe/bsw@0: } jbe/bsw@0: jbe@23: // registration information for functions of library: jbe/bsw@0: static const struct luaL_Reg mondelefant_module_functions[] = { jbe/bsw@0: {"connect", mondelefant_connect}, jbe/bsw@0: {"set_class", mondelefant_set_class}, jbe/bsw@0: {"new_class", mondelefant_new_class}, jbe/bsw@0: {NULL, NULL} jbe/bsw@0: }; jbe/bsw@0: jbe@23: // registration information for meta-methods of database connections: jbe/bsw@0: static const struct luaL_Reg mondelefant_conn_mt_functions[] = { jbe/bsw@0: {"__gc", mondelefant_conn_free}, jbe/bsw@0: {"__index", mondelefant_conn_index}, jbe/bsw@0: {"__newindex", mondelefant_conn_newindex}, jbe/bsw@0: {NULL, NULL} jbe/bsw@0: }; jbe/bsw@0: jbe@23: // registration information for methods of database connections: jbe/bsw@0: static const struct luaL_Reg mondelefant_conn_methods[] = { jbe/bsw@0: {"close", mondelefant_conn_close}, jbe/bsw@0: {"is_ok", mondelefant_conn_is_ok}, jbe/bsw@0: {"get_transaction_status", mondelefant_conn_get_transaction_status}, jbe/bsw@0: {"create_list", mondelefant_conn_create_list}, jbe/bsw@0: {"create_object", mondelefant_conn_create_object}, jbe/bsw@0: {"quote_string", mondelefant_conn_quote_string}, jbe/bsw@0: {"quote_binary", mondelefant_conn_quote_binary}, jbe/bsw@0: {"assemble_command", mondelefant_conn_assemble_command}, jbe/bsw@0: {"try_query", mondelefant_conn_try_query}, jbe/bsw@0: {"query", mondelefant_conn_query}, jbe/bsw@0: {NULL, NULL} jbe/bsw@0: }; jbe/bsw@0: jbe@23: // registration information for meta-methods of error objects: jbe/bsw@0: static const struct luaL_Reg mondelefant_errorobject_mt_functions[] = { jbe/bsw@0: {NULL, NULL} jbe/bsw@0: }; jbe/bsw@0: jbe@23: // registration information for methods of error objects: jbe/bsw@0: static const struct luaL_Reg mondelefant_errorobject_methods[] = { jbe/bsw@0: {"escalate", mondelefant_errorobject_escalate}, jbe/bsw@0: {"is_kind_of", mondelefant_errorobject_is_kind_of}, jbe/bsw@0: {NULL, NULL} jbe/bsw@0: }; jbe/bsw@0: jbe@23: // registration information for meta-methods of database result lists/objects: jbe/bsw@0: static const struct luaL_Reg mondelefant_result_mt_functions[] = { jbe/bsw@0: {"__index", mondelefant_result_index}, jbe/bsw@0: {"__newindex", mondelefant_result_newindex}, jbe/bsw@0: {NULL, NULL} jbe/bsw@0: }; jbe/bsw@0: jbe@23: // registration information for methods of database result lists/objects: jbe/bsw@0: static const struct luaL_Reg mondelefant_class_mt_functions[] = { jbe/bsw@0: {"__index", mondelefant_class_index}, jbe/bsw@0: {NULL, NULL} jbe/bsw@0: }; jbe/bsw@0: jbe@23: // registration information for methods of classes (models): jbe/bsw@0: static const struct luaL_Reg mondelefant_class_methods[] = { jbe/bsw@0: {"get_reference", mondelefant_class_get_reference}, jbe/bsw@0: {"iterate_over_references", mondelefant_class_iterate_over_references}, jbe/bsw@0: {"get_foreign_key_reference_name", jbe/bsw@0: mondelefant_class_get_foreign_key_reference_name}, jbe/bsw@0: {NULL, NULL} jbe/bsw@0: }; jbe/bsw@0: jbe@23: // registration information for methods of database result objects (not lists!): jbe/bsw@0: static const struct luaL_Reg mondelefant_object_methods[] = { jbe/bsw@0: {NULL, NULL} jbe/bsw@0: }; jbe/bsw@0: jbe@23: // registration information for methods of database result lists (not single objects!): jbe/bsw@0: static const struct luaL_Reg mondelefant_list_methods[] = { jbe/bsw@0: {NULL, NULL} jbe/bsw@0: }; jbe/bsw@0: jbe@23: // luaopen function to initialize/register library: jbe/bsw@0: int luaopen_mondelefant_native(lua_State *L) { jbe/bsw@0: lua_settop(L, 0); jbe/bsw@0: lua_newtable(L); // module at stack position 1 jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: luaL_setfuncs(L, mondelefant_module_functions, 0); jbe@64: #else jbe/bsw@0: luaL_register(L, NULL, mondelefant_module_functions); jbe@64: #endif jbe/bsw@0: jbe/bsw@0: lua_pushvalue(L, 1); // 2 jbe/bsw@0: lua_setfield(L, LUA_REGISTRYINDEX, MONDELEFANT_MODULE_REGKEY); jbe/bsw@0: jbe/bsw@0: lua_newtable(L); // 2 jbe/bsw@0: // NOTE: only PostgreSQL is supported yet: jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: luaL_setfuncs(L, mondelefant_conn_methods, 0); jbe@64: #else jbe/bsw@0: luaL_register(L, NULL, mondelefant_conn_methods); jbe@64: #endif jbe/bsw@0: lua_setfield(L, 1, "postgresql_connection_prototype"); jbe/bsw@0: lua_newtable(L); // 2 jbe/bsw@0: lua_setfield(L, 1, "connection_prototype"); jbe/bsw@0: jbe/bsw@0: luaL_newmetatable(L, MONDELEFANT_CONN_MT_REGKEY); // 2 jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: luaL_setfuncs(L, mondelefant_conn_mt_functions, 0); jbe@64: #else jbe/bsw@0: luaL_register(L, NULL, mondelefant_conn_mt_functions); jbe@64: #endif jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: luaL_newmetatable(L, MONDELEFANT_RESULT_MT_REGKEY); // 2 jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: luaL_setfuncs(L, mondelefant_result_mt_functions, 0); jbe@64: #else jbe/bsw@0: luaL_register(L, NULL, mondelefant_result_mt_functions); jbe@64: #endif jbe/bsw@0: lua_setfield(L, 1, "result_metatable"); jbe/bsw@0: luaL_newmetatable(L, MONDELEFANT_CLASS_MT_REGKEY); // 2 jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: luaL_setfuncs(L, mondelefant_class_mt_functions, 0); jbe@64: #else jbe/bsw@0: luaL_register(L, NULL, mondelefant_class_mt_functions); jbe@64: #endif jbe/bsw@0: lua_setfield(L, 1, "class_metatable"); jbe/bsw@0: jbe/bsw@0: lua_newtable(L); // 2 jbe@272: #if LUA_VERSION_NUM >= 502 jbe@272: luaL_setfuncs(L, mondelefant_class_methods, 0); jbe@272: #else jbe@272: luaL_register(L, NULL, mondelefant_class_methods); jbe@272: #endif jbe/bsw@0: lua_newtable(L); // 3 jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: luaL_setfuncs(L, mondelefant_object_methods, 0); jbe@64: #else jbe/bsw@0: luaL_register(L, NULL, mondelefant_object_methods); jbe@64: #endif jbe/bsw@0: lua_setfield(L, 2, "object"); jbe/bsw@0: lua_newtable(L); // 3 jbe/bsw@0: lua_setfield(L, 2, "object_get"); jbe/bsw@0: lua_newtable(L); // 3 jbe/bsw@0: lua_setfield(L, 2, "object_set"); jbe/bsw@0: lua_newtable(L); // 3 jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: luaL_setfuncs(L, mondelefant_list_methods, 0); jbe@64: #else jbe/bsw@0: luaL_register(L, NULL, mondelefant_list_methods); jbe@64: #endif jbe/bsw@0: lua_setfield(L, 2, "list"); jbe/bsw@0: lua_newtable(L); // 3 jbe/bsw@0: lua_setfield(L, 2, "references"); jbe/bsw@0: lua_newtable(L); // 3 jbe/bsw@0: lua_setfield(L, 2, "foreign_keys"); jbe/bsw@0: lua_pushvalue(L, 2); // 3 jbe/bsw@0: lua_setfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CLASS_PROTO_REGKEY); jbe/bsw@0: lua_setfield(L, 1, "class_prototype"); jbe/bsw@0: jbe/bsw@0: lua_newtable(L); // 2 jbe/bsw@0: lua_pushliteral(L, "k"); // 3 jbe/bsw@0: lua_setfield(L, 2, "__mode"); jbe/bsw@0: lua_newtable(L); // 3 jbe/bsw@0: lua_pushvalue(L, 2); // 4 jbe/bsw@0: lua_setmetatable(L, 3); jbe/bsw@0: lua_setfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CONN_DATA_REGKEY); jbe/bsw@0: lua_settop(L, 1); jbe/bsw@0: jbe/bsw@0: luaL_newmetatable(L, MONDELEFANT_ERROROBJECT_MT_REGKEY); // 2 jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: luaL_setfuncs(L, mondelefant_errorobject_mt_functions, 0); jbe@64: #else jbe/bsw@0: luaL_register(L, NULL, mondelefant_errorobject_mt_functions); jbe@64: #endif jbe/bsw@0: lua_newtable(L); // 3 jbe@64: #if LUA_VERSION_NUM >= 502 jbe@64: luaL_setfuncs(L, mondelefant_errorobject_methods, 0); jbe@64: #else jbe/bsw@0: luaL_register(L, NULL, mondelefant_errorobject_methods); jbe@64: #endif jbe/bsw@0: lua_setfield(L, 2, "__index"); jbe/bsw@0: lua_setfield(L, 1, "errorobject_metatable"); jbe/bsw@0: jbe/bsw@0: return 1; jbe/bsw@0: }