├── pllua.control ├── sql ├── biginttest.sql ├── subtransaction.sql ├── error_info.sql ├── pgfunctest.sql └── plluatest.sql ├── expected ├── biginttest.out ├── subtransaction.out ├── error_info.out ├── pgfunctest.out ├── pgfunctest_1.out └── plluatest.out ├── lua_int64.h ├── pllua_pgfunc.h ├── pllua_subxact.h ├── rtupdesc.h ├── pllua_xact_cleanup.h ├── pllua_debug.h ├── rowstamp.h ├── Makefile ├── rtupdescstk.h ├── pllua.sql.in ├── rtupdesc.c ├── pllua--1.0.sql ├── pllua_errors.h ├── pllua_debug.c ├── pllua.h ├── plluacommon.h ├── pllua_xact_cleanup.c ├── pllua_subxact.c ├── .travis.yml ├── rtupdescstk.c ├── pllua.c ├── lua_int64.c ├── pllua_errors.c ├── pllua_pgfunc.c ├── README.md └── plluaspi.c /pllua.control: -------------------------------------------------------------------------------- 1 | # pllua extension 2 | default_version = '1.0' 3 | comment = 'Lua as a procedural language' 4 | module_pathname = '$libdir/pllua' 5 | relocatable = false 6 | schema = 'pllua' 7 | -------------------------------------------------------------------------------- /sql/biginttest.sql: -------------------------------------------------------------------------------- 1 | CREATE FUNCTION int64_minus_one(value bigint) 2 | RETURNS bigint AS $$ 3 | return value - 1; 4 | $$ LANGUAGE pllua; 5 | select int64_minus_one(9223372036854775807); 6 | -------------------------------------------------------------------------------- /expected/biginttest.out: -------------------------------------------------------------------------------- 1 | CREATE FUNCTION int64_minus_one(value bigint) 2 | RETURNS bigint AS $$ 3 | return value - 1; 4 | $$ LANGUAGE pllua; 5 | select int64_minus_one(9223372036854775807); 6 | int64_minus_one 7 | --------------------- 8 | 9223372036854775806 9 | (1 row) 10 | 11 | -------------------------------------------------------------------------------- /lua_int64.h: -------------------------------------------------------------------------------- 1 | #ifndef LUA_INT64_H 2 | #define LUA_INT64_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | 11 | void register_int64(lua_State * L); 12 | 13 | int64 get64lua(lua_State * L,int index); 14 | void setInt64lua(lua_State * L,int64 value); 15 | 16 | 17 | 18 | #endif // LUA_INT64_H 19 | -------------------------------------------------------------------------------- /pllua_pgfunc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * pgfunc interface 3 | * Author: Eugene Sergeev 4 | * Please check copyright notice at the bottom of pllua.h 5 | */ 6 | 7 | #ifndef PLLUA_PGFUNC_H 8 | #define PLLUA_PGFUNC_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | int get_pgfunc(lua_State * L); 17 | void register_funcinfo_mt(lua_State * L); 18 | 19 | 20 | #endif // PLLUA_PGFUNC_H 21 | -------------------------------------------------------------------------------- /pllua_subxact.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Subtransaction wrapper 3 | * Author: Eugene Sergeev 4 | * Please check copyright notice at the bottom of pllua.h 5 | */ 6 | #ifndef PLLUA_SUBXACT_H 7 | #define PLLUA_SUBXACT_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | int use_subtransaction(lua_State * L); 16 | int subt_luaB_pcall (lua_State *L); 17 | int subt_luaB_xpcall (lua_State *L); 18 | 19 | #endif // PLLUA_SUBXACT_H 20 | -------------------------------------------------------------------------------- /rtupdesc.h: -------------------------------------------------------------------------------- 1 | #ifndef RTUPDESC_H 2 | #define RTUPDESC_H 3 | 4 | #include "plluacommon.h" 5 | #include "rtupdescstk.h" 6 | 7 | typedef struct _RTupDesc{ 8 | int ref_count; 9 | RTDNodePtr weakNodeStk; 10 | TupleDesc tupdesc; 11 | } RTupDesc; 12 | 13 | RTupDesc* rtupdesc_ctor(lua_State * state, TupleDesc tupdesc); 14 | 15 | RTupDesc* rtupdesc_ref(RTupDesc* rtupdesc); 16 | 17 | RTupDesc* rtupdesc_unref(RTupDesc* rtupdesc); 18 | 19 | 20 | TupleDesc rtupdesc_gettup(RTupDesc* rtupdesc); 21 | 22 | void rtupdesc_freedesc(RTupDesc* rtupdesc); 23 | 24 | void rtupdesc_dtor(RTupDesc* rtupdesc); 25 | 26 | int rtupdesc_obj_count(void); 27 | 28 | 29 | #endif // RTUPDESC_H 30 | -------------------------------------------------------------------------------- /pllua_xact_cleanup.h: -------------------------------------------------------------------------------- 1 | /* 2 | * functions for resource management 3 | * Author: Eugene Sergeev 4 | * Please check copyright notice at the bottom of pllua.h 5 | */ 6 | 7 | #ifndef PLLUA_XACT_CLEANUP_H 8 | #define PLLUA_XACT_CLEANUP_H 9 | 10 | #include "plluacommon.h" 11 | 12 | typedef void (*RSDtorCallback) (void *data); 13 | 14 | MemoryContext get_common_ctx(void); 15 | void pllua_init_common_ctx(void); 16 | void pllua_delete_common_ctx(void); 17 | void pllua_xact_cb(XactEvent event, void *arg); 18 | 19 | void *register_resource(void *d, RSDtorCallback dtor); 20 | void *unregister_resource(void* d); 21 | 22 | 23 | #endif // PLLUA_XACT_CLEANUP_H 24 | -------------------------------------------------------------------------------- /pllua_debug.h: -------------------------------------------------------------------------------- 1 | /* 2 | * debug helpers 3 | * Author: Eugene Sergeev 4 | * Please check copyright notice at the bottom of pllua.h 5 | */ 6 | 7 | #ifndef PLLUA_DEBUG_H 8 | #define PLLUA_DEBUG_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | void setLINE(char *location); 16 | const char * getLINE(void); 17 | 18 | 19 | #include 20 | #define STRINGIFY(x) #x 21 | #define TOSTRING(x) STRINGIFY(x) 22 | #define AT __FILE__ ":" TOSTRING(__LINE__) 23 | 24 | void stackDump (lua_State *L); 25 | 26 | #define lua_settable(...) setLINE(AT);lua_settable(__VA_ARGS__) 27 | #define lua_rawseti(...) setLINE(AT);lua_rawseti(__VA_ARGS__) 28 | #define lua_rawset(...) setLINE(AT);lua_rawset(__VA_ARGS__) 29 | #define lua_setmetatable(...) setLINE(AT);lua_setmetatable(__VA_ARGS__) 30 | 31 | 32 | #define out(...) ereport(INFO, (errmsg(__VA_ARGS__))) 33 | #define dolog(...) ereport(LOG, (errmsg(__VA_ARGS__))) 34 | 35 | #endif // PLLUA_DEBUG_H 36 | -------------------------------------------------------------------------------- /rowstamp.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _ROWSTAMP_H_ 3 | #define _ROWSTAMP_H_ 4 | 5 | /* 6 | * Row version check changed in 8.3 7 | */ 8 | 9 | #if PG_VERSION_NUM < 80300 10 | #define ROWSTAMP_PRE83 11 | #endif 12 | 13 | /* 14 | * Row version info 15 | */ 16 | typedef struct RowStamp { 17 | TransactionId xmin; 18 | #ifdef ROWSTAMP_PRE83 19 | CommandId cmin; 20 | #else 21 | ItemPointerData tid; 22 | #endif 23 | } RowStamp; 24 | 25 | static void rowstamp_set(RowStamp *stamp, HeapTuple tup) { 26 | stamp->xmin = HeapTupleHeaderGetXmin(tup->t_data); 27 | #ifdef ROWSTAMP_PRE83 28 | stamp->cmin = HeapTupleHeaderGetCmin(tup->t_data); 29 | #else 30 | stamp->tid = tup->t_self; 31 | #endif 32 | } 33 | 34 | static bool rowstamp_check(RowStamp *stamp, HeapTuple tup) { 35 | return stamp->xmin == HeapTupleHeaderGetXmin(tup->t_data) 36 | #ifdef ROWSTAMP_PRE83 37 | && stamp->cmin == HeapTupleHeaderGetCmin(tup->t_data); 38 | #else 39 | && ItemPointerEquals(&stamp->tid, &tup->t_self); 40 | #endif 41 | } 42 | 43 | #endif 44 | 45 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for PL/Lua 2 | 3 | PG_CONFIG ?= pg_config 4 | PKG_LIBDIR := $(shell $(PG_CONFIG) --pkglibdir) 5 | 6 | # Lua specific 7 | 8 | # General 9 | LUA_INCDIR ?= /usr/include/lua5.1 10 | LUALIB ?= -L/usr/local/lib -llua5.1 11 | 12 | # LuaJIT 13 | #LUA_INCDIR = /usr/local/include/luajit-2.0 14 | #LUALIB = -L/usr/local/lib -lluajit-5.1 15 | 16 | # Debian/Ubuntu 17 | #LUA_INCDIR = /usr/include/lua5.1 18 | #LUALIB = -llua5.1 19 | 20 | # Fink 21 | #LUA_INCDIR = /sw/include -I/sw/include/postgresql 22 | #LUALIB = -L/sw/lib -llua 23 | 24 | # Lua for Windows 25 | #LUA_INCDIR = C:/PROGRA~1/Lua/5.1/include 26 | #LUALIB = -LC:/PROGRA~1/Lua/5.1/lib -llua5.1 27 | 28 | # no need to edit below here 29 | MODULE_big = pllua 30 | EXTENSION = pllua 31 | DATA = pllua--1.0.sql 32 | #DATA_built = pllua.sql 33 | 34 | REGRESS = \ 35 | plluatest \ 36 | biginttest \ 37 | pgfunctest \ 38 | subtransaction \ 39 | error_info 40 | 41 | OBJS = \ 42 | pllua.o \ 43 | pllua_debug.o \ 44 | pllua_xact_cleanup.o \ 45 | plluaapi.o \ 46 | plluaspi.o \ 47 | lua_int64.o \ 48 | rtupdesc.o \ 49 | rtupdescstk.o \ 50 | pllua_pgfunc.o \ 51 | pllua_subxact.o \ 52 | pllua_errors.o 53 | 54 | PG_CPPFLAGS = -I$(LUA_INCDIR) #-DPLLUA_DEBUG 55 | SHLIB_LINK = $(LUALIB) 56 | 57 | PGXS := $(shell $(PG_CONFIG) --pgxs) 58 | include $(PGXS) 59 | -------------------------------------------------------------------------------- /rtupdescstk.h: -------------------------------------------------------------------------------- 1 | /* 2 | * functions for tupledesc resource management 3 | * Author: Eugene Sergeev 4 | * Please check copyright notice at the bottom of pllua.h 5 | */ 6 | 7 | #ifndef RTUPDESCSTK_H 8 | #define RTUPDESCSTK_H 9 | 10 | #include "plluacommon.h" 11 | 12 | #include 13 | struct stackType; 14 | 15 | typedef struct RTDnode { 16 | void *data; 17 | struct RTDnode *next; 18 | struct RTDnode *prev; 19 | struct stackType *tail; 20 | } RTDNode, *RTDNodePtr; 21 | 22 | typedef struct stackType { 23 | int ref_count; 24 | lua_State *L; 25 | RTDNodePtr top; 26 | void *resptr; 27 | struct stackType **cleanup_ptr; /*func ptr to this struct*/ 28 | } RTupDescStackType, *RTupDescStack; 29 | 30 | RTupDescStack rtds_set_current(void *s); 31 | RTupDescStack rtds_get_current(void); 32 | 33 | int rtds_get_length(RTupDescStack S); 34 | 35 | 36 | RTupDescStack rtds_initStack(lua_State *L); 37 | RTupDescStack rtds_initStack_weak(lua_State *L, RTupDescStack *wp); 38 | 39 | int rtds_isempty(RTupDescStack S); 40 | 41 | 42 | void *rtds_pop(RTupDescStack S); 43 | 44 | void rtds_tryclean(RTupDescStack S); 45 | 46 | RTDNodePtr rtds_push_current(void *d); 47 | void rtds_remove_node(RTDNodePtr np); 48 | 49 | void rtds_inuse(RTupDescStack S); 50 | void rtds_notinuse(RTupDescStack S); 51 | 52 | RTupDescStack rtds_unref(RTupDescStack S); 53 | RTupDescStack rtds_free_if_not_used(RTupDescStack S); 54 | 55 | 56 | 57 | 58 | #endif // RTUPDESCSTK_H 59 | -------------------------------------------------------------------------------- /sql/subtransaction.sql: -------------------------------------------------------------------------------- 1 | SET client_min_messages = warning; 2 | CREATE TABLE accounts 3 | ( 4 | id bigserial NOT NULL, 5 | account_name text, 6 | balance integer, 7 | CONSTRAINT accounts_pkey PRIMARY KEY (id), 8 | CONSTRAINT no_minus CHECK (balance >= 0) 9 | ); 10 | RESET client_min_messages; 11 | insert into accounts(account_name, balance) values('joe', 200); 12 | insert into accounts(account_name, balance) values('mary', 50); 13 | CREATE OR REPLACE FUNCTION pg_temp.sub_test() 14 | RETURNS SETOF text AS $$ 15 | local f = function() 16 | local p = server.prepare("UPDATE accounts SET balance = balance + $2 WHERE account_name = $1", {"text","int4"}) 17 | p:execute{'joe', 100} 18 | p:execute{'mary',-100} 19 | return true 20 | end 21 | local status, err = subtransaction(f) 22 | coroutine.yield(tostring(status)) 23 | 24 | f = function() 25 | local p = server.prepare("UPDATE accounts SET balance = balance + $2 WHERE account_name = $1", {"text","int4"}) 26 | p:execute{'joe', -100} 27 | p:execute{'mary', 100} 28 | return true 29 | end 30 | status, err = subtransaction(f) 31 | coroutine.yield(tostring(status)) 32 | $$ LANGUAGE pllua; 33 | select pg_temp.sub_test(); 34 | do $$ 35 | local status, result = subtransaction(function() 36 | server.execute('select 1,'); -- < special SQL syntax error 37 | end); 38 | print (status, result) 39 | status, result = pcall(function() 40 | server.execute('select 1,'); -- < special SQL syntax error 41 | end); 42 | print (status, result) 43 | print ('done') 44 | $$ language pllua; 45 | -------------------------------------------------------------------------------- /pllua.sql.in: -------------------------------------------------------------------------------- 1 | CREATE FUNCTION pllua_call_handler() 2 | RETURNS language_handler AS '$libdir/pllua' 3 | LANGUAGE C IMMUTABLE STRICT; 4 | 5 | -- comment out if VERSION < 9.0 6 | CREATE FUNCTION pllua_inline_handler(internal) 7 | RETURNS VOID AS '$libdir/pllua' 8 | LANGUAGE C IMMUTABLE STRICT; 9 | 10 | CREATE FUNCTION pllua_validator(oid) 11 | RETURNS VOID AS '$libdir/pllua' 12 | LANGUAGE C IMMUTABLE STRICT; 13 | 14 | CREATE TRUSTED LANGUAGE pllua 15 | HANDLER pllua_call_handler 16 | INLINE pllua_inline_handler -- comment out if VERSION < 9.0 17 | VALIDATOR pllua_validator; 18 | 19 | CREATE FUNCTION plluau_call_handler() 20 | RETURNS language_handler AS '$libdir/pllua' 21 | LANGUAGE C IMMUTABLE STRICT; 22 | 23 | -- comment out if VERSION < 9.0 24 | CREATE FUNCTION plluau_inline_handler(internal) 25 | RETURNS VOID AS '$libdir/pllua' 26 | LANGUAGE C IMMUTABLE STRICT; 27 | 28 | CREATE FUNCTION plluau_validator(oid) 29 | RETURNS VOID AS '$libdir/pllua' 30 | LANGUAGE C IMMUTABLE STRICT; 31 | 32 | CREATE LANGUAGE plluau 33 | HANDLER plluau_call_handler 34 | INLINE plluau_inline_handler -- comment out if VERSION < 9.0 35 | VALIDATOR plluau_validator; 36 | 37 | -- Optional: 38 | 39 | --CREATE SCHEMA pllua 40 | -- CREATE TABLE init (module text); 41 | 42 | -- PL template installation: 43 | 44 | --INSERT INTO pg_catalog.pg_pltemplate 45 | -- VALUES ('pllua', true, true, 'pllua_call_handler', 46 | -- 'pllua_inline_handler', 'pllua_validator', '$libdir/pllua', NULL); 47 | 48 | --INSERT INTO pg_catalog.pg_pltemplate 49 | -- VALUES ('plluau', false, true, 'plluau_call_handler', 50 | -- 'plluau_inline_handler', 'plluau_validator', '$libdir/pllua', NULL); 51 | 52 | -- vim: set syn=sql: 53 | -------------------------------------------------------------------------------- /expected/subtransaction.out: -------------------------------------------------------------------------------- 1 | SET client_min_messages = warning; 2 | CREATE TABLE accounts 3 | ( 4 | id bigserial NOT NULL, 5 | account_name text, 6 | balance integer, 7 | CONSTRAINT accounts_pkey PRIMARY KEY (id), 8 | CONSTRAINT no_minus CHECK (balance >= 0) 9 | ); 10 | RESET client_min_messages; 11 | insert into accounts(account_name, balance) values('joe', 200); 12 | insert into accounts(account_name, balance) values('mary', 50); 13 | CREATE OR REPLACE FUNCTION pg_temp.sub_test() 14 | RETURNS SETOF text AS $$ 15 | local f = function() 16 | local p = server.prepare("UPDATE accounts SET balance = balance + $2 WHERE account_name = $1", {"text","int4"}) 17 | p:execute{'joe', 100} 18 | p:execute{'mary',-100} 19 | return true 20 | end 21 | local status, err = subtransaction(f) 22 | coroutine.yield(tostring(status)) 23 | 24 | f = function() 25 | local p = server.prepare("UPDATE accounts SET balance = balance + $2 WHERE account_name = $1", {"text","int4"}) 26 | p:execute{'joe', -100} 27 | p:execute{'mary', 100} 28 | return true 29 | end 30 | status, err = subtransaction(f) 31 | coroutine.yield(tostring(status)) 32 | $$ LANGUAGE pllua; 33 | select pg_temp.sub_test(); 34 | sub_test 35 | ---------- 36 | false 37 | true 38 | (2 rows) 39 | 40 | do $$ 41 | local status, result = subtransaction(function() 42 | server.execute('select 1,'); -- < special SQL syntax error 43 | end); 44 | print (status, result) 45 | status, result = pcall(function() 46 | server.execute('select 1,'); -- < special SQL syntax error 47 | end); 48 | print (status, result) 49 | print ('done') 50 | $$ language pllua; 51 | INFO: false syntax error at end of input 52 | INFO: false syntax error at end of input 53 | INFO: done 54 | -------------------------------------------------------------------------------- /rtupdesc.c: -------------------------------------------------------------------------------- 1 | #include "rtupdesc.h" 2 | 3 | static int obj_count = 0; 4 | 5 | RTupDesc *rtupdesc_ctor(lua_State *state, TupleDesc tupdesc) 6 | { 7 | void* p; 8 | RTupDesc* rtupdesc = 0; 9 | 10 | MTOLUA(state); 11 | p = palloc(sizeof(RTupDesc)); 12 | if (p){ 13 | rtupdesc = (RTupDesc*)p; 14 | rtupdesc->ref_count = 1; 15 | rtupdesc->tupdesc = CreateTupleDescCopy(tupdesc); 16 | obj_count += 1; 17 | rtupdesc->weakNodeStk = rtds_push_current(p); 18 | } 19 | MTOPG; 20 | 21 | return rtupdesc; 22 | } 23 | 24 | 25 | RTupDesc *rtupdesc_ref(RTupDesc *rtupdesc) 26 | { 27 | if(rtupdesc) 28 | rtupdesc->ref_count += 1; 29 | return rtupdesc; 30 | } 31 | 32 | 33 | RTupDesc *rtupdesc_unref(RTupDesc *rtupdesc) 34 | { 35 | if(rtupdesc){ 36 | rtupdesc->ref_count -= 1; 37 | if (rtupdesc->ref_count == 0){ 38 | rtupdesc_dtor(rtupdesc); 39 | return NULL; 40 | } 41 | } 42 | return rtupdesc; 43 | } 44 | 45 | 46 | void rtupdesc_freedesc(RTupDesc *rtupdesc) 47 | { 48 | if (rtupdesc && rtupdesc->tupdesc){ 49 | FreeTupleDesc(rtupdesc->tupdesc); 50 | rtupdesc->tupdesc = NULL; 51 | obj_count -= 1; 52 | } 53 | } 54 | 55 | TupleDesc rtupdesc_gettup(RTupDesc *rtupdesc) 56 | { 57 | return (rtupdesc ? rtupdesc->tupdesc : NULL); 58 | } 59 | 60 | 61 | void rtupdesc_dtor(RTupDesc *rtupdesc) 62 | { 63 | 64 | if(rtupdesc){ 65 | rtds_remove_node(rtupdesc->weakNodeStk); 66 | 67 | if (rtupdesc->tupdesc){ 68 | FreeTupleDesc(rtupdesc->tupdesc); 69 | rtupdesc->tupdesc = NULL; 70 | obj_count -= 1; 71 | } 72 | 73 | pfree(rtupdesc); 74 | 75 | } 76 | 77 | } 78 | 79 | int rtupdesc_obj_count(void) 80 | { 81 | return obj_count; 82 | } 83 | -------------------------------------------------------------------------------- /sql/error_info.sql: -------------------------------------------------------------------------------- 1 | do $$ 2 | local testfunc = function () error("my error") end 3 | local f = function() 4 | local status, err = pcall(testfunc) 5 | if (err) then 6 | error(err) 7 | end 8 | end 9 | f() 10 | $$language pllua; 11 | 12 | create or replace function pg_temp.function_with_error() returns integer as $$ 13 | local testfunc = function () error("my error") end 14 | local f = function() 15 | local status, err = pcall(testfunc) 16 | if (err) then 17 | error(err) 18 | end 19 | end 20 | f() 21 | $$language plluau; 22 | 23 | create or replace function pg_temp.second_function() returns void as $$ 24 | local k = server.execute('select pg_temp.function_with_error()') [0] 25 | $$language plluau; 26 | 27 | do $$ 28 | server.execute('select pg_temp.second_function()') 29 | $$language pllua; 30 | 31 | do $$ 32 | local status, err = subtransaction(function() assert(1==2) end) 33 | if (err) then 34 | error(err) 35 | end 36 | $$language pllua; 37 | 38 | do $$ 39 | info({message="info message", hint="info hint", detail="info detail"}) 40 | $$language pllua; 41 | 42 | do $$ 43 | info("info message") 44 | $$language pllua; 45 | 46 | do $$ 47 | warning({message="warning message", hint="warning hint", detail="warning detail"}) 48 | $$language pllua; 49 | 50 | do $$ 51 | warning("warning message") 52 | $$language pllua; 53 | 54 | do $$ 55 | error({message="error message", hint="error hint", detail="error detail"}) 56 | $$language pllua; 57 | 58 | do $$ 59 | error("error message") 60 | $$language pllua; 61 | 62 | do $$ 63 | info() 64 | $$language pllua; 65 | 66 | do $$ 67 | warning() 68 | $$language pllua; 69 | 70 | do $$ 71 | error() 72 | $$language pllua; 73 | 74 | do $$ 75 | local status, err = subtransaction(function() local _ = fromstring('no_type_text','qwerty') end) 76 | if (err) then 77 | print(err) 78 | end 79 | $$ language pllua 80 | 81 | -------------------------------------------------------------------------------- /pllua--1.0.sql: -------------------------------------------------------------------------------- 1 | \echo Use "CREATE EXTENSION pllua" to load this file. \quit 2 | 3 | CREATE FUNCTION pllua_call_handler() 4 | RETURNS language_handler AS 'MODULE_PATHNAME' 5 | LANGUAGE C IMMUTABLE STRICT; 6 | 7 | -- comment out if VERSION < 9.0 8 | CREATE FUNCTION pllua_inline_handler(internal) 9 | RETURNS VOID AS 'MODULE_PATHNAME' 10 | LANGUAGE C IMMUTABLE STRICT; 11 | 12 | CREATE FUNCTION pllua_validator(oid) 13 | RETURNS VOID AS 'MODULE_PATHNAME' 14 | LANGUAGE C IMMUTABLE STRICT; 15 | 16 | CREATE TRUSTED LANGUAGE pllua 17 | HANDLER pllua_call_handler 18 | INLINE pllua_inline_handler -- comment out if VERSION < 9.0 19 | VALIDATOR pllua_validator; 20 | 21 | CREATE FUNCTION plluau_call_handler() 22 | RETURNS language_handler AS 'MODULE_PATHNAME' 23 | LANGUAGE C IMMUTABLE STRICT; 24 | 25 | -- comment out if VERSION < 9.0 26 | CREATE FUNCTION plluau_inline_handler(internal) 27 | RETURNS VOID AS 'MODULE_PATHNAME' 28 | LANGUAGE C IMMUTABLE STRICT; 29 | 30 | CREATE FUNCTION plluau_validator(oid) 31 | RETURNS VOID AS 'MODULE_PATHNAME' 32 | LANGUAGE C IMMUTABLE STRICT; 33 | 34 | CREATE LANGUAGE plluau 35 | HANDLER plluau_call_handler 36 | INLINE plluau_inline_handler -- comment out if VERSION < 9.0 37 | VALIDATOR plluau_validator; 38 | 39 | --within 'pllua' schema 40 | CREATE TABLE init (module text); 41 | 42 | -- PL template installation: 43 | INSERT INTO pg_catalog.pg_pltemplate 44 | SELECT 'pllua', true, true, 'pllua_call_handler', 45 | 'pllua_inline_handler', 'pllua_validator', 'MODULE_PATHNAME', NULL 46 | WHERE 'pllua' NOT IN (SELECT tmplname FROM pg_catalog.pg_pltemplate); 47 | 48 | INSERT INTO pg_catalog.pg_pltemplate 49 | SELECT 'plluau', false, true, 'plluau_call_handler', 50 | 'plluau_inline_handler', 'plluau_validator', 'MODULE_PATHNAME', NULL 51 | WHERE 'plluau' NOT IN (SELECT tmplname FROM pg_catalog.pg_pltemplate); 52 | 53 | -- vim: set syn=sql: 54 | -------------------------------------------------------------------------------- /pllua_errors.h: -------------------------------------------------------------------------------- 1 | #ifndef PLLUA_ERRORS_H 2 | #define PLLUA_ERRORS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #define PLLUA_PG_CATCH_RETHROW(source_code) do\ 11 | {\ 12 | MemoryContext ____oldContext = CurrentMemoryContext;\ 13 | PG_TRY();\ 14 | {\ 15 | source_code\ 16 | }\ 17 | PG_CATCH();\ 18 | {\ 19 | lua_pop(L, lua_gettop(L));\ 20 | push_spi_error(L, ____oldContext);\ 21 | return lua_error(L);\ 22 | }PG_END_TRY();\ 23 | }while(0) 24 | 25 | #if defined(PLLUA_DEBUG) 26 | #define luapg_error(L, tag) do{\ 27 | if (lua_type(L, -1) == LUA_TSTRING){ \ 28 | const char *err = pstrdup( lua_tostring((L), -1)); \ 29 | lua_pop(L, lua_gettop(L));\ 30 | ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), \ 31 | errmsg("[pllua]: error: %s", tag), \ 32 | errdetail("%s", err)));\ 33 | }else {\ 34 | luatable_topgerror(L);\ 35 | }\ 36 | }while(0) 37 | #else 38 | #define luapg_error(L, tag)do{\ 39 | if (lua_type(L, -1) == LUA_TSTRING){ \ 40 | const char *err = pstrdup( lua_tostring((L), -1)); \ 41 | lua_pop(L, lua_gettop(L));\ 42 | ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), \ 43 | errmsg("[pllua]: " tag " error"),\ 44 | errdetail("%s", err)));\ 45 | }else {\ 46 | luatable_topgerror(L);\ 47 | }\ 48 | }while(0) 49 | #endif 50 | 51 | /*shows error as "error text" instead of "[string "anonymous"]:2: error text*/ 52 | #define luaL_error luaL_error_skip_where 53 | 54 | int luaB_assert (lua_State *L); 55 | int luaB_error (lua_State *L); 56 | 57 | 58 | int luaL_error_skip_where (lua_State *L, const char *fmt, ...); 59 | 60 | void register_error_mt(lua_State * L); 61 | void set_error_mt(lua_State * L); 62 | 63 | void push_spi_error(lua_State *L, MemoryContext oldcontext); 64 | void luatable_topgerror(lua_State *L); 65 | void luatable_report(lua_State *L, int elevel); 66 | 67 | 68 | int traceback (lua_State *L) ; 69 | 70 | #endif // PLLUA_ERRORS_H 71 | -------------------------------------------------------------------------------- /pllua_debug.c: -------------------------------------------------------------------------------- 1 | #include "pllua_debug.h" 2 | 3 | /* PostgreSQL */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | #if PG_VERSION_NUM >= 90300 9 | #include 10 | #endif 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | 28 | 29 | static char *_location; 30 | void setLINE(char *location) 31 | { 32 | _location = location; 33 | } 34 | 35 | 36 | const char *getLINE(void) 37 | { 38 | return _location; 39 | } 40 | 41 | 42 | #define DMP 2 43 | 44 | void stackDump(lua_State *L) { 45 | int i=lua_gettop(L); 46 | ereport(INFO, (errmsg("%s", "---------------- Stack Dump ----------------"))); 47 | while( i ) { 48 | int t = lua_type(L, i); 49 | switch (t) { 50 | case LUA_TSTRING: 51 | ereport(INFO, (errmsg("%d:`%s'", i, lua_tostring(L, i)))); 52 | break; 53 | case LUA_TBOOLEAN: 54 | ereport(INFO, (errmsg("%d: %s",i,lua_toboolean(L, i) ? "true" : "false"))); 55 | break; 56 | case LUA_TNUMBER: 57 | ereport(INFO, (errmsg("%d: %g", i, lua_tonumber(L, i)))); 58 | break; 59 | case LUA_TTABLE: 60 | ereport(INFO, (errmsg("%d: table", i))); 61 | if (DMP==1){ 62 | /* table is in the stack at index 't' */ 63 | lua_pushnil(L); /* first key */ 64 | 65 | while (lua_next(L, i) != 0) { 66 | /* uses 'key' (at index -2) and 'value' (at index -1) */ 67 | ereport(INFO, (errmsg("===%s - %s\n", 68 | lua_tostring(L, -2), 69 | lua_typename(L, lua_type(L, -1))))); 70 | /* removes 'value'; keeps 'key' for next iteration */ 71 | lua_pop(L, 1); 72 | } 73 | }else if (DMP == 2){ 74 | int cnt = 0; 75 | lua_pushnil(L); /* first key */ 76 | 77 | while (lua_next(L, i) != 0) { 78 | ++cnt; 79 | /* removes 'value'; keeps 'key' for next iteration */ 80 | lua_pop(L, 1); 81 | } 82 | ereport(INFO, (errmsg("===length %i: table", cnt))); 83 | } 84 | break; 85 | default: 86 | ereport(INFO, (errmsg("%d: %s", i, lua_typename(L, t)))); 87 | break; 88 | } 89 | i--; 90 | } 91 | ereport(INFO, (errmsg("%s","--------------- Stack Dump Finished ---------------" ))); 92 | } 93 | -------------------------------------------------------------------------------- /pllua.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PL/Lua 3 | * Author: Luis Carvalho 4 | * Version: 1.0 5 | * Please check copyright notice at the bottom of this file 6 | * $Id: pllua.h,v 1.17 2009/09/19 16:20:45 carvalho Exp $ 7 | */ 8 | 9 | 10 | #include "plluacommon.h" 11 | 12 | /*used as a key for saving lua context lua_pushlightuserdata(p_lua_mem_cxt) 13 | instead of lua_pushlightuserdata((void*)L) (in SRF the L is a lua_newthread) */ 14 | int p_lua_mem_cxt(void); 15 | int p_lua_master_state(void); 16 | 17 | typedef struct luaP_Buffer { 18 | int size; 19 | Datum *value; 20 | char *null; 21 | } luaP_Buffer; 22 | 23 | /* utils */ 24 | void *luaP_toudata (lua_State *L, int ud, const char *tname); 25 | luaP_Buffer *luaP_getbuffer (lua_State *L, int n); 26 | /* call handler API */ 27 | lua_State *luaP_newstate (int trusted); 28 | void luaP_close (lua_State *L); 29 | Datum luaP_validator (lua_State *L, Oid oid); 30 | Datum luaP_callhandler (lua_State *L, FunctionCallInfo fcinfo); 31 | #if PG_VERSION_NUM >= 90000 32 | Datum luaP_inlinehandler (lua_State *L, const char *source); 33 | #endif 34 | /* general API */ 35 | void luaP_pushdatum (lua_State *L, Datum dat, Oid type); 36 | Datum luaP_todatum (lua_State *L, Oid type, int len, bool *isnull, int idx); 37 | 38 | void luaP_pushtuple_trg (lua_State *L, TupleDesc desc, HeapTuple tuple, 39 | Oid relid, int readonly); 40 | HeapTuple luaP_totuple (lua_State *L); 41 | HeapTuple luaP_casttuple (lua_State *L, TupleDesc tupdesc); 42 | /* SPI */ 43 | void luaP_pushdesctable(lua_State *L, TupleDesc desc); 44 | void luaP_registerspi(lua_State *L); 45 | void luaP_pushcursor (lua_State *L, Portal cursor); 46 | void luaP_pushrecord(lua_State *L, Datum record); 47 | Portal luaP_tocursor (lua_State *L, int pos); 48 | 49 | /* ========================================================================= 50 | 51 | Copyright (c) 2008 Luis Carvalho 52 | 53 | Permission is hereby granted, free of charge, to any person 54 | obtaining a copy of this software and associated documentation files 55 | (the "Software"), to deal in the Software without restriction, 56 | including without limitation the rights to use, copy, modify, 57 | merge, publish, distribute, sublicense, and/or sell copies of the 58 | Software, and to permit persons to whom the Software is furnished 59 | to do so, subject to the following conditions: 60 | 61 | The above copyright notice and this permission notice shall be 62 | included in all copies or substantial portions of the Software. 63 | 64 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 65 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 66 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 67 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 68 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 69 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 70 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 71 | SOFTWARE. 72 | 73 | ========================================================================= */ 74 | 75 | -------------------------------------------------------------------------------- /plluacommon.h: -------------------------------------------------------------------------------- 1 | #ifndef PLLUACOMMON_H 2 | #define PLLUACOMMON_H 3 | 4 | /* PostgreSQL */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #if PG_VERSION_NUM >= 90300 11 | #include 12 | #endif 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | /* Lua */ 29 | #include 30 | #include 31 | #include 32 | 33 | #if LUA_VERSION_NUM <= 501 34 | #define lua_pushglobaltable(L) lua_pushvalue(L, LUA_GLOBALSINDEX) 35 | #define lua_setuservalue lua_setfenv 36 | #define lua_getuservalue lua_getfenv 37 | #define lua_rawlen lua_objlen 38 | #define luaP_register(L,l) luaL_register(L, NULL, (l)) 39 | #else 40 | #define luaP_register(L,l) luaL_setfuncs(L, (l), 0) 41 | #endif 42 | 43 | #if !defined LUA_VERSION_NUM || LUA_VERSION_NUM==501 44 | /* 45 | ** Adapted from Lua 5.2.0 46 | */ 47 | void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); 48 | #endif 49 | 50 | #define PLLUA_VERSION "PL/Lua dev" 51 | 52 | #if defined(PLLUA_DEBUG) 53 | #include "pllua_debug.h" 54 | #define BEGINLUA int ____stk=lua_gettop(L) 55 | #define ENDLUA ____stk =lua_gettop(L)-____stk; if(0!=____stk) ereport(INFO, (errmsg("stk %s>%i",AT,____stk))) 56 | #define ENDLUAV(v) ____stk =(lua_gettop(L)-____stk-v); if(0!=____stk) ereport(INFO, (errmsg("stk %s>%i",AT,____stk))) 57 | #else 58 | #define BEGINLUA 59 | #define ENDLUA 60 | #define ENDLUAV(v) 61 | #endif 62 | 63 | 64 | #define lua_push_oidstring(L, oid) do\ 65 | {\ 66 | luaL_Buffer b;\ 67 | luaL_buffinit(L, &b);\ 68 | lua_pushinteger(L, oid);\ 69 | luaL_addstring(&b, "oid_");\ 70 | luaL_addvalue(&b);\ 71 | luaL_pushresult(&b);\ 72 | }while(0) 73 | 74 | typedef struct { 75 | const char* name; 76 | bool hasTraceback; 77 | } LVMInfo; 78 | 79 | /* get MemoryContext for state L */ 80 | MemoryContext luaP_getmemctxt (lua_State *L); 81 | 82 | lua_State *pllua_getmaster (lua_State *L); 83 | int pllua_getmaster_index(lua_State *L); 84 | 85 | #if PG_VERSION_NUM < 110000 86 | #define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) 87 | 88 | #define pg_create_context(name) \ 89 | AllocSetContextCreate(TopMemoryContext, \ 90 | name, ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, \ 91 | ALLOCSET_DEFAULT_MAXSIZE); 92 | #else 93 | #define pg_create_context(name) \ 94 | AllocSetContextCreate(TopMemoryContext, name, ALLOCSET_DEFAULT_SIZES); 95 | #endif 96 | 97 | #define lua_swap(L) lua_insert(L, -2) 98 | 99 | #define luaP_getfield(L, s) \ 100 | lua_pushlightuserdata((L), (void *)(s)); \ 101 | lua_rawget((L), LUA_REGISTRYINDEX) 102 | 103 | #define MTOLUA(state) {MemoryContext ___mcxt,___m;\ 104 | ___mcxt = luaP_getmemctxt(state); \ 105 | ___m = MemoryContextSwitchTo(___mcxt) 106 | 107 | #define MTOPG MemoryContextSwitchTo(___m);} 108 | Oid pg_to_regtype(const char *typ_name); 109 | 110 | #endif // PLLUACOMMON_H 111 | -------------------------------------------------------------------------------- /pllua_xact_cleanup.c: -------------------------------------------------------------------------------- 1 | #include "pllua_xact_cleanup.h" 2 | 3 | #define MTOCOMMON {MemoryContext ___m;\ 4 | ___m = MemoryContextSwitchTo(cmn_ctx) 5 | #define MTOPREV MemoryContextSwitchTo(___m);} 6 | 7 | struct RSStack; 8 | static MemoryContext cmn_ctx = NULL; 9 | 10 | typedef struct { 11 | void *data; 12 | RSDtorCallback dtor; 13 | } Resource; 14 | 15 | 16 | typedef struct RSNode { 17 | void *data; 18 | RSDtorCallback dtor; 19 | struct RSNode *next; 20 | struct RSNode *prev; 21 | struct RSStack *tail; 22 | } RSNode, *RSNodePtr; 23 | 24 | 25 | typedef struct RSStack { 26 | RSNodePtr top; 27 | } RSStack, *RSStaskPtr; 28 | 29 | static RSStaskPtr resource_stk = NULL; 30 | 31 | static RSNodePtr rsp_push(RSStaskPtr S, void *d, RSDtorCallback dtor) { 32 | RSNodePtr np; 33 | if (S == NULL) return NULL; 34 | MTOCOMMON; 35 | np = (RSNodePtr) palloc(sizeof(RSNode)); 36 | MTOPREV; 37 | np->tail = S; 38 | np -> data = d; 39 | np -> dtor = dtor; 40 | np -> next = S -> top; 41 | np -> prev = NULL; 42 | if (S->top != NULL) 43 | S -> top->prev = np; 44 | S -> top = np; 45 | return np; 46 | } 47 | 48 | void *unregister_resource(void* d) 49 | { 50 | RSStaskPtr S; 51 | RSNodePtr np; 52 | if (d == NULL) return NULL; 53 | np = (RSNodePtr)d; 54 | S = np->tail; 55 | if (S->top == np){ 56 | S->top = np->next; 57 | if (S->top){ 58 | S->top->prev = NULL; 59 | } 60 | }else{ 61 | if (np->prev) 62 | np->prev->next = np->next; 63 | if (np->next) 64 | np->next->prev = np->prev; 65 | } 66 | pfree(np); 67 | return NULL; 68 | } 69 | 70 | void *register_resource(void *d, RSDtorCallback dtor) 71 | { 72 | return (void*)rsp_push(resource_stk, d, dtor); 73 | } 74 | 75 | static int rsp_isempty(RSStaskPtr S) { 76 | return (S -> top == NULL); 77 | } 78 | 79 | static Resource rsp_pop(RSStaskPtr S) { 80 | Resource hold; 81 | RSNodePtr temp; 82 | 83 | hold.data = NULL; 84 | hold.dtor = NULL; 85 | 86 | if (rsp_isempty(S)) { 87 | return hold; 88 | } 89 | hold.data = S -> top -> data; 90 | hold.dtor = S -> top -> dtor; 91 | temp = S -> top; 92 | S -> top = S -> top -> next; 93 | if (S->top != NULL) 94 | S -> top->prev = NULL; 95 | pfree(temp); 96 | return hold; 97 | } 98 | 99 | 100 | 101 | static RSStaskPtr rsp_initStack() { 102 | RSStaskPtr sp; 103 | MTOCOMMON; 104 | sp = (RSStaskPtr) palloc(sizeof(RSStack)); 105 | MTOPREV; 106 | sp->top = NULL; 107 | return sp; 108 | } 109 | 110 | static void clean(RSStaskPtr S){ 111 | Resource res = rsp_pop(S); 112 | while (res.data || res.dtor) { 113 | if (res.dtor){ 114 | (res.dtor)(res.data); 115 | }else{ 116 | pfree(res.data); 117 | } 118 | res = rsp_pop(S); 119 | } 120 | } 121 | 122 | 123 | void pllua_xact_cb(XactEvent event, void *arg) 124 | { 125 | //TODO: check events 126 | (void)event; 127 | (void)arg; 128 | clean(resource_stk); 129 | } 130 | 131 | 132 | void pllua_init_common_ctx() 133 | { 134 | cmn_ctx = pg_create_context("PL/Lua common context"); 135 | 136 | resource_stk = rsp_initStack(); 137 | } 138 | 139 | 140 | void pllua_delete_common_ctx() 141 | { 142 | pfree(resource_stk); 143 | MemoryContextDelete(cmn_ctx); 144 | } 145 | 146 | 147 | MemoryContext get_common_ctx() 148 | { 149 | return cmn_ctx; 150 | } 151 | -------------------------------------------------------------------------------- /expected/error_info.out: -------------------------------------------------------------------------------- 1 | do $$ 2 | local testfunc = function () error("my error") end 3 | local f = function() 4 | local status, err = pcall(testfunc) 5 | if (err) then 6 | error(err) 7 | end 8 | end 9 | f() 10 | $$language pllua; 11 | ERROR: my error 12 | CONTEXT: 13 | stack traceback(trusted): 14 | [C]: in function 'error' 15 | [string "anonymous"]:6: in function 'f' 16 | [string "anonymous"]:9: in main chunk 17 | create or replace function pg_temp.function_with_error() returns integer as $$ 18 | local testfunc = function () error("my error") end 19 | local f = function() 20 | local status, err = pcall(testfunc) 21 | if (err) then 22 | error(err) 23 | end 24 | end 25 | f() 26 | $$language plluau; 27 | create or replace function pg_temp.second_function() returns void as $$ 28 | local k = server.execute('select pg_temp.function_with_error()') [0] 29 | $$language plluau; 30 | do $$ 31 | server.execute('select pg_temp.second_function()') 32 | $$language pllua; 33 | ERROR: my error 34 | CONTEXT: 35 | stack traceback(untrusted): 36 | [C]: in function 'error' 37 | [string "function_with_error"]:6: in function 'f' 38 | [string "function_with_error"]:9: in function <[string "function_with_error"]:1> 39 | [C]: in function 'execute' 40 | [string "second_function"]:2: in function <[string "second_function"]:1> 41 | SQL statement "select pg_temp.function_with_error()" 42 | SQL statement "select pg_temp.second_function()" 43 | SQL statement "select pg_temp.second_function()" 44 | stack traceback(trusted): 45 | [C]: in function 'execute' 46 | [string "anonymous"]:2: in main chunk 47 | do $$ 48 | local status, err = subtransaction(function() assert(1==2) end) 49 | if (err) then 50 | error(err) 51 | end 52 | $$language pllua; 53 | ERROR: assertion failed! 54 | CONTEXT: 55 | stack traceback(trusted): 56 | [C]: in function 'error' 57 | [string "anonymous"]:4: in main chunk 58 | do $$ 59 | info({message="info message", hint="info hint", detail="info detail"}) 60 | $$language pllua; 61 | INFO: info message 62 | DETAIL: info detail 63 | HINT: info hint 64 | do $$ 65 | info("info message") 66 | $$language pllua; 67 | INFO: info message 68 | do $$ 69 | warning({message="warning message", hint="warning hint", detail="warning detail"}) 70 | $$language pllua; 71 | WARNING: warning message 72 | DETAIL: warning detail 73 | HINT: warning hint 74 | do $$ 75 | warning("warning message") 76 | $$language pllua; 77 | WARNING: warning message 78 | do $$ 79 | error({message="error message", hint="error hint", detail="error detail"}) 80 | $$language pllua; 81 | ERROR: error message 82 | DETAIL: error detail 83 | HINT: error hint 84 | CONTEXT: 85 | stack traceback(trusted): 86 | [C]: in function 'error' 87 | [string "anonymous"]:2: in main chunk 88 | do $$ 89 | error("error message") 90 | $$language pllua; 91 | ERROR: error message 92 | CONTEXT: 93 | stack traceback(trusted): 94 | [C]: in function 'error' 95 | [string "anonymous"]:2: in main chunk 96 | do $$ 97 | info() 98 | $$language pllua; 99 | ERROR: [string "anonymous"]:2: bad argument #1 to 'info' (string expected, got no value) 100 | CONTEXT: 101 | stack traceback(trusted): 102 | [C]: in function 'info' 103 | [string "anonymous"]:2: in main chunk 104 | do $$ 105 | warning() 106 | $$language pllua; 107 | ERROR: [string "anonymous"]:2: bad argument #1 to 'warning' (string expected, got no value) 108 | CONTEXT: 109 | stack traceback(trusted): 110 | [C]: in function 'warning' 111 | [string "anonymous"]:2: in main chunk 112 | do $$ 113 | error() 114 | $$language pllua; 115 | ERROR: [string "anonymous"]:2: no exception data 116 | CONTEXT: 117 | stack traceback(trusted): 118 | [C]: in function 'error' 119 | [string "anonymous"]:2: in main chunk 120 | do $$ 121 | local status, err = subtransaction(function() local _ = fromstring('no_type_text','qwerty') end) 122 | if (err) then 123 | print(err) 124 | end 125 | $$ language pllua 126 | INFO: type "no_type_text" does not exist 127 | -------------------------------------------------------------------------------- /pllua_subxact.c: -------------------------------------------------------------------------------- 1 | #include "pllua_subxact.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include "pllua_debug.h" 7 | #include "plluacommon.h" 8 | #include "rtupdescstk.h" 9 | #include "pllua_errors.h" 10 | 11 | 12 | typedef struct 13 | { 14 | ResourceOwner resowner; 15 | MemoryContext mcontext; 16 | 17 | } SubTransactionBlock; 18 | 19 | 20 | static SubTransactionBlock stb_SubTranBlock(){ 21 | SubTransactionBlock stb; 22 | stb.mcontext = NULL; 23 | stb.resowner = NULL; 24 | return stb; 25 | } 26 | 27 | static void stb_enter(lua_State *L, SubTransactionBlock *block){ 28 | if (!IsTransactionOrTransactionBlock()) 29 | luaL_error(L, "out of transaction"); 30 | 31 | block->resowner = CurrentResourceOwner; 32 | block->mcontext = CurrentMemoryContext; 33 | BeginInternalSubTransaction(NULL); 34 | /* Do not want to leave the previous memory context */ 35 | MemoryContextSwitchTo(block->mcontext); 36 | } 37 | 38 | static void stb_exit(SubTransactionBlock *block, bool success){ 39 | if (success) 40 | ReleaseCurrentSubTransaction(); 41 | else 42 | RollbackAndReleaseCurrentSubTransaction(); 43 | 44 | MemoryContextSwitchTo(block->mcontext); 45 | CurrentResourceOwner = block->resowner; 46 | 47 | /* 48 | * AtEOSubXact_SPI() should not have popped any SPI context, but just 49 | * in case it did, make sure we remain connected. 50 | */ 51 | SPI_restore_connection(); 52 | } 53 | 54 | /* all exceptions should be thrown only through luaL_error 55 | * we might me be here if there is a postgres unhandled exception 56 | * and lua migth be in an inconsistant state that's why the process aborted 57 | */ 58 | #define WRAP_SUBTRANSACTION(source_code) do\ 59 | {\ 60 | RTupDescStack funcxt;\ 61 | RTupDescStack prev;\ 62 | SubTransactionBlock subtran;\ 63 | funcxt = rtds_initStack(L);\ 64 | rtds_inuse(funcxt);\ 65 | prev = rtds_set_current(funcxt);\ 66 | subtran = stb_SubTranBlock();\ 67 | stb_enter(L, &subtran);\ 68 | PG_TRY();\ 69 | {\ 70 | source_code\ 71 | }\ 72 | PG_CATCH();\ 73 | {\ 74 | ErrorData *edata;\ 75 | edata = CopyErrorData();\ 76 | ereport(FATAL, (errmsg("Unhandled exception: %s", edata->message)));\ 77 | }\ 78 | PG_END_TRY();\ 79 | stb_exit(&subtran, status == 0);\ 80 | if (status) rtds_unref(funcxt);\ 81 | rtds_set_current(prev);\ 82 | }while(0) 83 | 84 | 85 | int subt_luaB_pcall (lua_State *L) { 86 | int status = 0; 87 | 88 | luaL_checkany(L, 1); 89 | 90 | WRAP_SUBTRANSACTION( 91 | status = lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0); 92 | ); 93 | 94 | lua_pushboolean(L, (status == 0)); 95 | lua_insert(L, 1); 96 | return lua_gettop(L); /* return status + all results */ 97 | } 98 | 99 | int subt_luaB_xpcall (lua_State *L) { 100 | int status = 0; 101 | 102 | luaL_checkany(L, 2); 103 | lua_settop(L, 2); 104 | lua_insert(L, 1); /* put error function under function to be called */ 105 | 106 | WRAP_SUBTRANSACTION( 107 | status = lua_pcall(L, 0, LUA_MULTRET, 1); 108 | ); 109 | 110 | lua_pushboolean(L, (status == 0)); 111 | lua_replace(L, 1); 112 | return lua_gettop(L); /* return status + all results */ 113 | } 114 | 115 | int use_subtransaction(lua_State *L){ 116 | int status = 0; 117 | 118 | 119 | if (lua_gettop(L) < 1){ 120 | return luaL_error(L, "subtransaction has no function argument"); 121 | } 122 | if (lua_type(L, 1) != LUA_TFUNCTION){ 123 | return luaL_error(L, "subtransaction first arg must be a lua function"); 124 | } 125 | 126 | WRAP_SUBTRANSACTION( 127 | status = lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0); 128 | ); 129 | 130 | 131 | lua_pushboolean(L, (status == 0)); 132 | lua_insert(L, 1); 133 | return lua_gettop(L); /* return status + all results */ 134 | } 135 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - psql --version 3 | - sudo /etc/init.d/postgresql stop 4 | - sudo apt-get -y --purge remove postgresql libpq-dev libpq5 postgresql-client-common postgresql-common 5 | - sudo rm -rf /var/lib/postgresql 6 | - wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - 7 | - sudo sh -c "echo deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main $PGVERSION >> /etc/apt/sources.list.d/postgresql.list" 8 | - sudo apt-get update -qq 9 | - sudo apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::="--force-confnew" install postgresql-$PGVERSION postgresql-server-dev-$PGVERSION 10 | - sudo chmod 777 /etc/postgresql/$PGVERSION/main/pg_hba.conf 11 | - sudo echo "local all postgres trust" > /etc/postgresql/$PGVERSION/main/pg_hba.conf 12 | - sudo echo "local all all trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf 13 | - sudo echo "host all all 127.0.0.1/32 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf 14 | - sudo echo "host all all ::1/128 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf 15 | - sudo /etc/init.d/postgresql restart 16 | - | 17 | if [[ "$LUA" == "lua5.3" ]]; then 18 | wget http://www.lua.org/ftp/lua-5.3.2.tar.gz -O lua.tar.gz 19 | tar -xvzf lua.tar.gz 20 | cd lua-* 21 | (cd src && make SYSCFLAGS="-DLUA_USE_LINUX -ULUA_COMPAT_5_2" SYSLIBS="-Wl,-E -ldl -lreadline" LUA_A=liblua5.3.so MYCFLAGS="-fPIC" RANLIB=: AR="gcc -shared -ldl -o" liblua5.3.so) || exit 1 22 | sudo make INSTALL_TOP=/usr/ INSTALL_INC=${LUA_INCDIR} TO_LIB=liblua5.3.so linux install || exit 1 23 | cd .. 24 | else 25 | sudo apt-get install $LUA 26 | sudo apt-get install $LUA_DEV 27 | fi 28 | 29 | 30 | before_script: 31 | - createuser -U postgres -s travis 32 | 33 | 34 | env: 35 | matrix: 36 | - PGVERSION=9.2 LUA=lua5.1 LUA_DEV=liblua5.1-dev LUA_INCDIR=/usr/include/lua5.1 LUALIB=-llua5.1 37 | - PGVERSION=9.2 LUA=lua5.3 LUA_DEV=liblua5.3-dev LUA_INCDIR=/usr/include/lua5.3 LUALIB=-llua5.3 38 | - PGVERSION=9.2 LUA=luajit LUA_DEV=libluajit-5.1-dev LUA_INCDIR=/usr/include/luajit-2.0 LUALIB=-lluajit-5.1 39 | - PGVERSION=9.3 LUA=lua5.1 LUA_DEV=liblua5.1-dev LUA_INCDIR=/usr/include/lua5.1 LUALIB=-llua5.1 40 | - PGVERSION=9.3 LUA=lua5.3 LUA_DEV=liblua5.3-dev LUA_INCDIR=/usr/include/lua5.3 LUALIB=-llua5.3 41 | - PGVERSION=9.3 LUA=luajit LUA_DEV=libluajit-5.1-dev LUA_INCDIR=/usr/include/luajit-2.0 LUALIB=-lluajit-5.1 42 | - PGVERSION=9.4 LUA=lua5.1 LUA_DEV=liblua5.1-dev LUA_INCDIR=/usr/include/lua5.1 LUALIB=-llua5.1 43 | - PGVERSION=9.4 LUA=lua5.3 LUA_DEV=liblua5.3-dev LUA_INCDIR=/usr/include/lua5.3 LUALIB=-llua5.3 44 | - PGVERSION=9.4 LUA=luajit LUA_DEV=libluajit-5.1-dev LUA_INCDIR=/usr/include/luajit-2.0 LUALIB=-lluajit-5.1 45 | - PGVERSION=9.5 LUA=lua5.1 LUA_DEV=liblua5.1-dev LUA_INCDIR=/usr/include/lua5.1 LUALIB=-llua5.1 46 | - PGVERSION=9.5 LUA=lua5.3 LUA_DEV=liblua5.3-dev LUA_INCDIR=/usr/include/lua5.3 LUALIB=-llua5.3 47 | - PGVERSION=9.5 LUA=luajit LUA_DEV=libluajit-5.1-dev LUA_INCDIR=/usr/include/luajit-2.0 LUALIB=-lluajit-5.1 48 | - PGVERSION=9.6 LUA=lua5.1 LUA_DEV=liblua5.1-dev LUA_INCDIR=/usr/include/lua5.1 LUALIB=-llua5.1 49 | - PGVERSION=9.6 LUA=lua5.3 LUA_DEV=liblua5.3-dev LUA_INCDIR=/usr/include/lua5.3 LUALIB=-llua5.3 50 | - PGVERSION=9.6 LUA=luajit LUA_DEV=libluajit-5.1-dev LUA_INCDIR=/usr/include/luajit-2.0 LUALIB=-lluajit-5.1 51 | - PGVERSION=10 LUA=lua5.1 LUA_DEV=liblua5.1-dev LUA_INCDIR=/usr/include/lua5.1 LUALIB=-llua5.1 52 | - PGVERSION=10 LUA=lua5.3 LUA_DEV=liblua5.3-dev LUA_INCDIR=/usr/include/lua5.3 LUALIB=-llua5.3 53 | - PGVERSION=10 LUA=luajit LUA_DEV=libluajit-5.1-dev LUA_INCDIR=/usr/include/luajit-2.0 LUALIB=-lluajit-5.1 54 | - PGVERSION=11 LUA=lua5.1 LUA_DEV=liblua5.1-dev LUA_INCDIR=/usr/include/lua5.1 LUALIB=-llua5.1 55 | - PGVERSION=11 LUA=lua5.3 LUA_DEV=liblua5.3-dev LUA_INCDIR=/usr/include/lua5.3 LUALIB=-llua5.3 56 | - PGVERSION=11 LUA=luajit LUA_DEV=libluajit-5.1-dev LUA_INCDIR=/usr/include/luajit-2.0 LUALIB=-lluajit-5.1 57 | 58 | 59 | language: c 60 | compiler: 61 | - gcc 62 | 63 | script: 64 | - make && sudo make install && make installcheck 65 | 66 | after_script: 67 | - cat regression.diffs || true 68 | 69 | -------------------------------------------------------------------------------- /rtupdescstk.c: -------------------------------------------------------------------------------- 1 | #include "rtupdescstk.h" 2 | #include "rtupdesc.h" 3 | #include "pllua_xact_cleanup.h" 4 | 5 | static void* current_func_cxt = NULL; 6 | RTupDescStack rtds_set_current(void *s){ 7 | RTupDescStack prev = current_func_cxt; 8 | current_func_cxt = s; 9 | return prev; 10 | } 11 | 12 | RTupDescStack rtds_get_current(void){ 13 | return current_func_cxt; 14 | } 15 | 16 | static void clean(RTupDescStack S){ 17 | void *top = rtds_pop(S); 18 | while (top) { 19 | RTupDesc* rtupdesc = (RTupDesc*)top; 20 | rtupdesc_freedesc(rtupdesc); 21 | rtupdesc->weakNodeStk = NULL; 22 | top = rtds_pop(S); 23 | } 24 | } 25 | 26 | void rtds_tryclean(RTupDescStack S){ 27 | if (S == NULL) return; 28 | S->ref_count -= 1; 29 | if (S->ref_count != 0) return; 30 | clean(S); 31 | } 32 | 33 | void *rtds_pop(RTupDescStack S) { 34 | void *hold; 35 | RTDNodePtr temp; 36 | if (rtds_isempty(S)) { 37 | return NULL; 38 | } 39 | hold = S -> top -> data; 40 | temp = S -> top; 41 | S -> top = S -> top -> next; 42 | if (S->top != NULL) 43 | S -> top->prev = NULL; 44 | pfree(temp); 45 | return hold; 46 | } 47 | 48 | 49 | static RTDNodePtr rtds_push(RTupDescStack S, void *d) { 50 | RTDNodePtr np; 51 | if (S == NULL) return NULL; 52 | MTOLUA(S->L); 53 | np = (RTDNodePtr) palloc(sizeof(RTDNode)); 54 | MTOPG; 55 | np->tail = S; 56 | np -> data = d; 57 | np -> next = S -> top; 58 | np -> prev = NULL; 59 | if (S->top != NULL) 60 | S -> top->prev = np; 61 | S -> top = np; 62 | return np; 63 | } 64 | 65 | 66 | int rtds_isempty(RTupDescStack S) { 67 | return (S -> top == NULL); 68 | } 69 | 70 | static void force_free(/*RTupDescStack*/void *d){ 71 | RTupDescStack p; 72 | if (d == NULL) return; 73 | p = d; 74 | if (p->cleanup_ptr){ 75 | (*p->cleanup_ptr) = NULL; 76 | } 77 | clean(p); 78 | pfree(d); 79 | } 80 | 81 | RTupDescStack rtds_initStack(lua_State *L) { 82 | RTupDescStack sp; 83 | L = pllua_getmaster(L); 84 | MTOLUA(L); 85 | sp = (RTupDescStack) palloc(sizeof(RTupDescStackType)); 86 | MTOPG; 87 | sp->ref_count = 0; 88 | sp->L = L; 89 | sp->top = NULL; 90 | sp->resptr = register_resource(sp, force_free); 91 | sp->cleanup_ptr = NULL; 92 | return sp; 93 | } 94 | 95 | RTupDescStack rtds_initStack_weak(lua_State *L, RTupDescStack* wp) { 96 | RTupDescStack sp; 97 | L = pllua_getmaster(L); 98 | MTOLUA(L); 99 | sp = (RTupDescStack) palloc(sizeof(RTupDescStackType)); 100 | MTOPG; 101 | sp->ref_count = 0; 102 | sp->L = L; 103 | sp->cleanup_ptr = wp; 104 | sp->top = NULL; 105 | sp->resptr = register_resource(sp, force_free); 106 | return sp; 107 | } 108 | 109 | 110 | RTDNodePtr rtds_push_current(void *d) 111 | { 112 | return rtds_push(current_func_cxt,d); 113 | } 114 | 115 | 116 | RTupDescStack rtds_unref(RTupDescStack S) 117 | { 118 | if (S == NULL) return NULL; 119 | rtds_notinuse(S); 120 | return rtds_free_if_not_used(S); 121 | } 122 | 123 | 124 | void rtds_remove_node(RTDNodePtr np) 125 | { 126 | RTupDescStack S; 127 | if (np == NULL) return; 128 | S = np->tail; 129 | if (S->top == np){ 130 | S->top = np->next; 131 | if (S->top){ 132 | S->top->prev = NULL; 133 | } 134 | }else{ 135 | if (np->prev) 136 | np->prev->next = np->next; 137 | if (np->next) 138 | np->next->prev = np->prev; 139 | } 140 | pfree(np); 141 | 142 | } 143 | 144 | 145 | int rtds_get_length(RTupDescStack S) 146 | { 147 | int count = 0; 148 | RTDNodePtr node = S->top; 149 | while(node){ 150 | count += 1; 151 | node = node->next; 152 | } 153 | return count; 154 | } 155 | 156 | 157 | void rtds_inuse(RTupDescStack S) 158 | { 159 | S->ref_count += 1; 160 | } 161 | 162 | 163 | void rtds_notinuse(RTupDescStack S) 164 | { 165 | S->ref_count -= 1; 166 | } 167 | 168 | 169 | RTupDescStack rtds_free_if_not_used(RTupDescStack S) 170 | { 171 | if (S == NULL) return NULL; 172 | if (S->ref_count == 0){ 173 | clean(S); 174 | S->resptr = unregister_resource(S->resptr); 175 | pfree(S); 176 | return NULL; 177 | } 178 | return S; 179 | } 180 | -------------------------------------------------------------------------------- /sql/pgfunctest.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION i_void(internal) 2 | RETURNS internal AS 3 | $BODY$ 4 | -- mymodule.lua 5 | local M = {} -- public interface 6 | 7 | -- private 8 | local x = 1 9 | local function baz() print 'test' end 10 | 11 | function M.foo() print("foo", x) end 12 | 13 | function M.bar() 14 | M.foo() 15 | baz() 16 | print "bar" 17 | end 18 | 19 | function M.getAnswer() 20 | return 42 21 | end 22 | 23 | return M 24 | 25 | $BODY$ LANGUAGE pllua; 26 | 27 | CREATE OR REPLACE FUNCTION pg_temp.pgfunc_test() 28 | RETURNS SETOF text AS $$ 29 | local quote_ident = pgfunc("quote_ident(text)") 30 | coroutine.yield(quote_ident("int")) 31 | local right = pgfunc("right(text,int)") 32 | coroutine.yield(right('abcde', 2)) 33 | local factorial = pgfunc("factorial(int8)") 34 | coroutine.yield(tostring(factorial(50))) 35 | local i_void = pgfunc("i_void(internal)") 36 | coroutine.yield(i_void.getAnswer()) 37 | $$ LANGUAGE pllua; 38 | select pg_temp.pgfunc_test(); 39 | 40 | do $$ 41 | print(pgfunc('quote_nullable(text)')(nil)) 42 | $$ language pllua; 43 | 44 | create or replace function pg_temp.throw_error(text) returns void as $$ 45 | begin 46 | raise exception '%', $1; 47 | end 48 | $$ language plpgsql; 49 | 50 | \set VERBOSITY 'terse' 51 | do $$ 52 | pgfunc('pg_temp.throw_error(text)',{only_internal=false})("exception test") 53 | $$ language pllua; 54 | \set VERBOSITY 'default' 55 | 56 | do $$ 57 | local f = pgfunc('pg_temp.throw_error(text)',{only_internal=false}) 58 | print(pcall(f, "exception test")) 59 | $$ language pllua; 60 | 61 | create or replace function pg_temp.no_throw() returns json as $$ 62 | select '{"a":5, "b":10}'::json 63 | $$ language sql; 64 | 65 | do $$ 66 | local f = pgfunc('pg_temp.no_throw()',{only_internal=false, throwable=false}) 67 | print(f()) 68 | $$ language pllua; 69 | CREATE or replace FUNCTION pg_temp.arg_count(a1 integer,a2 integer,a3 integer,a4 integer,a5 integer 70 | ,a6 integer,a7 integer,a8 integer,a9 integer,a10 integer 71 | ,a11 integer,a12 integer,a13 integer,a14 integer,a15 integer ) returns integer AS 72 | $$ 73 | begin 74 | return a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12+a13+a14+a15; 75 | end 76 | $$ 77 | LANGUAGE plpgsql; 78 | do $$ 79 | local f = pgfunc([[pg_temp.arg_count(integer, integer, integer, integer, integer, 80 | integer, integer, integer, integer, integer, 81 | integer, integer, integer, integer, integer ) ]],{only_internal=false}); 82 | print(f(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)) 83 | $$ language pllua; 84 | CREATE or replace FUNCTION pg_temp.inoutf(a integer, INOUT b text, INOUT c text) AS 85 | $$ 86 | begin 87 | c = a||'c:'||c; 88 | b = 'b:'||b; 89 | end 90 | $$ 91 | LANGUAGE plpgsql; 92 | do $$ 93 | local f = pgfunc('pg_temp.inoutf(integer,text,text)',{only_internal=false}); 94 | local r = f(5, 'ABC', 'd') 95 | print(r.b) 96 | print(r.c) 97 | $$ language pllua; 98 | do $$ 99 | local f = pgfunc('generate_series(int,int)') 100 | print('4-6') 101 | for rr in f(4,6) do 102 | print(rr) 103 | end 104 | 105 | print('1-3') 106 | for rr in f(1,3) do 107 | print(rr) 108 | end 109 | 110 | $$ language pllua; 111 | do $$ 112 | local f = pgfunc('generate_series(int,int)') 113 | for rr in f(1,3) do 114 | 115 | for rr in f(41,43) do 116 | print(rr) 117 | end 118 | print(rr) 119 | end 120 | $$ language pllua; 121 | 122 | -- Type wrapper 123 | 124 | create extension hstore; 125 | do $$ 126 | local hstore = { 127 | fromstring = function(text) 128 | return fromstring('hstore',text) 129 | end, 130 | akeys = pgfunc('akeys(hstore)',{only_internal = false}), 131 | each = pgfunc('each(hstore)',{only_internal = false}) --orig:each(IN hs hstore, OUT key text, OUT value text) 132 | } 133 | 134 | local v = hstore.fromstring[[ 135 | "paperback" => "542", 136 | "publisher" => "postgresql.org", 137 | "language" => "English", 138 | "ISBN-13" => "978-0000000000", 139 | "weight" => "24.1 ounces" 140 | ]] 141 | 142 | print(v) 143 | 144 | for _,v in ipairs(hstore.akeys(v)) do 145 | print (v) 146 | end 147 | 148 | for hv in hstore.each(v) do 149 | print ("key = " .. hv.key .. " value = "..hv.value) 150 | end 151 | $$ language pllua; 152 | 153 | create or replace function getnull() returns text as $$ 154 | begin 155 | return null; 156 | end 157 | $$ language plpgsql; 158 | 159 | do $$ 160 | local a = pgfunc('getnull()',{only_internal = false}) 161 | print(a()) 162 | $$ language pllua; 163 | 164 | -------------------------------------------------------------------------------- /expected/pgfunctest.out: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION i_void(internal) 2 | RETURNS internal AS 3 | $BODY$ 4 | -- mymodule.lua 5 | local M = {} -- public interface 6 | 7 | -- private 8 | local x = 1 9 | local function baz() print 'test' end 10 | 11 | function M.foo() print("foo", x) end 12 | 13 | function M.bar() 14 | M.foo() 15 | baz() 16 | print "bar" 17 | end 18 | 19 | function M.getAnswer() 20 | return 42 21 | end 22 | 23 | return M 24 | 25 | $BODY$ LANGUAGE pllua; 26 | CREATE OR REPLACE FUNCTION pg_temp.pgfunc_test() 27 | RETURNS SETOF text AS $$ 28 | local quote_ident = pgfunc("quote_ident(text)") 29 | coroutine.yield(quote_ident("int")) 30 | local right = pgfunc("right(text,int)") 31 | coroutine.yield(right('abcde', 2)) 32 | local factorial = pgfunc("factorial(int8)") 33 | coroutine.yield(tostring(factorial(50))) 34 | local i_void = pgfunc("i_void(internal)") 35 | coroutine.yield(i_void.getAnswer()) 36 | $$ LANGUAGE pllua; 37 | select pg_temp.pgfunc_test(); 38 | pgfunc_test 39 | ------------------------------------------------------------------- 40 | "int" 41 | de 42 | 30414093201713378043612608166064768844377641568960512000000000000 43 | 42 44 | (4 rows) 45 | 46 | do $$ 47 | print(pgfunc('quote_nullable(text)')(nil)) 48 | $$ language pllua; 49 | INFO: NULL 50 | create or replace function pg_temp.throw_error(text) returns void as $$ 51 | begin 52 | raise exception '%', $1; 53 | end 54 | $$ language plpgsql; 55 | \set VERBOSITY 'terse' 56 | do $$ 57 | pgfunc('pg_temp.throw_error(text)',{only_internal=false})("exception test") 58 | $$ language pllua; 59 | ERROR: exception test 60 | \set VERBOSITY 'default' 61 | do $$ 62 | local f = pgfunc('pg_temp.throw_error(text)',{only_internal=false}) 63 | print(pcall(f, "exception test")) 64 | $$ language pllua; 65 | INFO: false exception test 66 | create or replace function pg_temp.no_throw() returns json as $$ 67 | select '{"a":5, "b":10}'::json 68 | $$ language sql; 69 | do $$ 70 | local f = pgfunc('pg_temp.no_throw()',{only_internal=false, throwable=false}) 71 | print(f()) 72 | $$ language pllua; 73 | INFO: {"a":5, "b":10} 74 | CREATE or replace FUNCTION pg_temp.arg_count(a1 integer,a2 integer,a3 integer,a4 integer,a5 integer 75 | ,a6 integer,a7 integer,a8 integer,a9 integer,a10 integer 76 | ,a11 integer,a12 integer,a13 integer,a14 integer,a15 integer ) returns integer AS 77 | $$ 78 | begin 79 | return a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12+a13+a14+a15; 80 | end 81 | $$ 82 | LANGUAGE plpgsql; 83 | do $$ 84 | local f = pgfunc([[pg_temp.arg_count(integer, integer, integer, integer, integer, 85 | integer, integer, integer, integer, integer, 86 | integer, integer, integer, integer, integer ) ]],{only_internal=false}); 87 | print(f(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)) 88 | $$ language pllua; 89 | INFO: 120 90 | CREATE or replace FUNCTION pg_temp.inoutf(a integer, INOUT b text, INOUT c text) AS 91 | $$ 92 | begin 93 | c = a||'c:'||c; 94 | b = 'b:'||b; 95 | end 96 | $$ 97 | LANGUAGE plpgsql; 98 | do $$ 99 | local f = pgfunc('pg_temp.inoutf(integer,text,text)',{only_internal=false}); 100 | local r = f(5, 'ABC', 'd') 101 | print(r.b) 102 | print(r.c) 103 | $$ language pllua; 104 | INFO: b:ABC 105 | INFO: 5c:d 106 | do $$ 107 | local f = pgfunc('generate_series(int,int)') 108 | print('4-6') 109 | for rr in f(4,6) do 110 | print(rr) 111 | end 112 | 113 | print('1-3') 114 | for rr in f(1,3) do 115 | print(rr) 116 | end 117 | 118 | $$ language pllua; 119 | INFO: 4-6 120 | INFO: 4 121 | INFO: 5 122 | INFO: 6 123 | INFO: 1-3 124 | INFO: 1 125 | INFO: 2 126 | INFO: 3 127 | do $$ 128 | local f = pgfunc('generate_series(int,int)') 129 | for rr in f(1,3) do 130 | 131 | for rr in f(41,43) do 132 | print(rr) 133 | end 134 | print(rr) 135 | end 136 | $$ language pllua; 137 | INFO: 41 138 | INFO: 42 139 | INFO: 43 140 | INFO: 1 141 | INFO: 41 142 | INFO: 42 143 | INFO: 43 144 | INFO: 2 145 | INFO: 41 146 | INFO: 42 147 | INFO: 43 148 | INFO: 3 149 | -- Type wrapper 150 | create extension hstore; 151 | do $$ 152 | local hstore = { 153 | fromstring = function(text) 154 | return fromstring('hstore',text) 155 | end, 156 | akeys = pgfunc('akeys(hstore)',{only_internal = false}), 157 | each = pgfunc('each(hstore)',{only_internal = false}) --orig:each(IN hs hstore, OUT key text, OUT value text) 158 | } 159 | 160 | local v = hstore.fromstring[[ 161 | "paperback" => "542", 162 | "publisher" => "postgresql.org", 163 | "language" => "English", 164 | "ISBN-13" => "978-0000000000", 165 | "weight" => "24.1 ounces" 166 | ]] 167 | 168 | print(v) 169 | 170 | for _,v in ipairs(hstore.akeys(v)) do 171 | print (v) 172 | end 173 | 174 | for hv in hstore.each(v) do 175 | print ("key = " .. hv.key .. " value = "..hv.value) 176 | end 177 | $$ language pllua; 178 | INFO: "weight"=>"24.1 ounces", "ISBN-13"=>"978-0000000000", "language"=>"English", "paperback"=>"542", "publisher"=>"postgresql.org" 179 | INFO: weight 180 | INFO: ISBN-13 181 | INFO: language 182 | INFO: paperback 183 | INFO: publisher 184 | INFO: key = weight value = 24.1 ounces 185 | INFO: key = ISBN-13 value = 978-0000000000 186 | INFO: key = language value = English 187 | INFO: key = paperback value = 542 188 | INFO: key = publisher value = postgresql.org 189 | create or replace function getnull() returns text as $$ 190 | begin 191 | return null; 192 | end 193 | $$ language plpgsql; 194 | do $$ 195 | local a = pgfunc('getnull()',{only_internal = false}) 196 | print(a()) 197 | $$ language pllua; 198 | INFO: nil 199 | -------------------------------------------------------------------------------- /pllua.c: -------------------------------------------------------------------------------- 1 | /* 2 | * pllua.c: PL/Lua call handler, trusted 3 | * Author: Luis Carvalho 4 | * Please check copyright notice at the bottom of pllua.h 5 | * $Id: pllua.c,v 1.15 2008/03/29 02:49:55 carvalho Exp $ 6 | */ 7 | 8 | #include "pllua.h" 9 | #include "pllua_errors.h" 10 | 11 | PG_MODULE_MAGIC; 12 | 13 | static lua_State *LuaVM[2] = {NULL, NULL}; /* Lua VMs */ 14 | 15 | LVMInfo lvm_info[2]; 16 | 17 | static void init_vmstructs(){ 18 | LVMInfo lvm0; 19 | LVMInfo lvm1; 20 | 21 | lvm0.name = "untrusted"; 22 | lvm0.hasTraceback = 0; 23 | 24 | lvm1.name = "trusted"; 25 | lvm1.hasTraceback = 0; 26 | 27 | lvm_info[0] = lvm0; 28 | lvm_info[1] = lvm1; 29 | } 30 | 31 | 32 | 33 | PGDLLEXPORT Datum _PG_init(PG_FUNCTION_ARGS); 34 | PGDLLEXPORT Datum _PG_fini(PG_FUNCTION_ARGS); 35 | PGDLLEXPORT Datum pllua_validator(PG_FUNCTION_ARGS); 36 | PGDLLEXPORT Datum pllua_call_handler(PG_FUNCTION_ARGS); 37 | PGDLLEXPORT Datum plluau_validator(PG_FUNCTION_ARGS); 38 | PGDLLEXPORT Datum plluau_call_handler(PG_FUNCTION_ARGS); 39 | #if PG_VERSION_NUM >= 90000 40 | PGDLLEXPORT Datum pllua_inline_handler(PG_FUNCTION_ARGS); 41 | PGDLLEXPORT Datum plluau_inline_handler(PG_FUNCTION_ARGS); 42 | #endif 43 | 44 | #include "pllua_xact_cleanup.h" 45 | PG_FUNCTION_INFO_V1(_PG_init); 46 | Datum _PG_init(PG_FUNCTION_ARGS) { 47 | init_vmstructs(); 48 | pllua_init_common_ctx(); 49 | LuaVM[0] = luaP_newstate(0); /* untrusted */ 50 | LuaVM[1] = luaP_newstate(1); /* trusted */ 51 | RegisterXactCallback(pllua_xact_cb, NULL); 52 | PG_RETURN_VOID(); 53 | } 54 | 55 | PG_FUNCTION_INFO_V1(_PG_fini); 56 | Datum _PG_fini(PG_FUNCTION_ARGS) { 57 | luaP_close(LuaVM[0]); 58 | luaP_close(LuaVM[1]); 59 | pllua_delete_common_ctx(); 60 | PG_RETURN_VOID(); 61 | } 62 | 63 | PG_FUNCTION_INFO_V1(plluau_validator); 64 | Datum plluau_validator(PG_FUNCTION_ARGS) { 65 | return luaP_validator(LuaVM[0], PG_GETARG_OID(0)); 66 | } 67 | 68 | PG_FUNCTION_INFO_V1(plluau_call_handler); 69 | Datum plluau_call_handler(PG_FUNCTION_ARGS) { 70 | lvm_info[0].hasTraceback = false; 71 | return luaP_callhandler(LuaVM[0], fcinfo); 72 | } 73 | 74 | PG_FUNCTION_INFO_V1(pllua_validator); 75 | Datum pllua_validator(PG_FUNCTION_ARGS) { 76 | return luaP_validator(LuaVM[1], PG_GETARG_OID(0)); 77 | } 78 | 79 | PG_FUNCTION_INFO_V1(pllua_call_handler); 80 | Datum pllua_call_handler(PG_FUNCTION_ARGS) { 81 | lvm_info[1].hasTraceback = false; 82 | return luaP_callhandler(LuaVM[1], fcinfo); 83 | } 84 | 85 | #if PG_VERSION_NUM >= 90000 86 | #define CODEBLOCK \ 87 | ((InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0)))->source_text 88 | 89 | PG_FUNCTION_INFO_V1(plluau_inline_handler); 90 | Datum plluau_inline_handler(PG_FUNCTION_ARGS) { 91 | lvm_info[0].hasTraceback = false; 92 | return luaP_inlinehandler(LuaVM[0], CODEBLOCK); 93 | } 94 | 95 | PG_FUNCTION_INFO_V1(pllua_inline_handler); 96 | Datum pllua_inline_handler(PG_FUNCTION_ARGS) { 97 | lvm_info[1].hasTraceback = false; 98 | return luaP_inlinehandler(LuaVM[1], CODEBLOCK); 99 | } 100 | #endif 101 | 102 | /* p_lua_mem_cxt and p_lua_master_state addresses used as keys for storing 103 | * values in lua states. In case when this functions are equal then 104 | * visual studio compiler with default settings "optimize" it and as 105 | * result both functions have equal addresses which make them unusable as keys, 106 | * thats why return 1; return 2; 107 | */ 108 | 109 | int p_lua_mem_cxt(void){return 2;} 110 | int p_lua_master_state(void){return 1;} 111 | 112 | //------------------------------------------------------------------------------------------------------ 113 | 114 | MemoryContext luaP_getmemctxt(lua_State *L) { 115 | MemoryContext mcxt; 116 | lua_pushlightuserdata(L, p_lua_mem_cxt); 117 | lua_rawget(L, LUA_REGISTRYINDEX); 118 | mcxt = lua_touserdata(L, -1); 119 | lua_pop(L, 1); 120 | return mcxt; 121 | } 122 | 123 | lua_State * pllua_getmaster(lua_State *L) { 124 | lua_State *master; 125 | lua_pushlightuserdata(L, p_lua_master_state); 126 | lua_rawget(L, LUA_REGISTRYINDEX); 127 | master = (lua_State *) lua_touserdata(L, -1); 128 | lua_pop(L, 1); 129 | return master; 130 | } 131 | 132 | int pllua_getmaster_index(lua_State *L) { 133 | if (pllua_getmaster(L) == LuaVM[0]) 134 | return 0; 135 | return 1; 136 | } 137 | 138 | #if LUA_VERSION_NUM < 502 139 | void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup) { 140 | luaL_checkstack(L, nup+1, "too many upvalues"); 141 | for (; l->name != NULL; l++) { /* fill the table with given functions */ 142 | int i; 143 | lua_pushstring(L, l->name); 144 | for (i = 0; i < nup; i++) /* copy upvalues to the top */ 145 | lua_pushvalue(L, -(nup+1)); 146 | lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ 147 | lua_settable(L, -(nup + 3)); 148 | } 149 | lua_pop(L, nup); /* remove upvalues */ 150 | } 151 | #endif 152 | 153 | Oid pg_to_regtype(const char *typ_name) 154 | { 155 | 156 | Oid result; 157 | int32 typmod; 158 | 159 | /* 160 | * Invoke the full parser to deal with special cases such as array syntax. 161 | */ 162 | 163 | #if PG_VERSION_NUM < 90400 164 | PG_TRY(); 165 | { 166 | parseTypeString(typ_name, &result, &typmod); 167 | } 168 | PG_CATCH(); 169 | { 170 | result = InvalidOid; 171 | } 172 | PG_END_TRY(); 173 | #else 174 | parseTypeString(typ_name, &result, &typmod, true); 175 | #endif 176 | return result; 177 | } 178 | -------------------------------------------------------------------------------- /expected/pgfunctest_1.out: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION i_void(internal) 2 | RETURNS internal AS 3 | $BODY$ 4 | -- mymodule.lua 5 | local M = {} -- public interface 6 | 7 | -- private 8 | local x = 1 9 | local function baz() print 'test' end 10 | 11 | function M.foo() print("foo", x) end 12 | 13 | function M.bar() 14 | M.foo() 15 | baz() 16 | print "bar" 17 | end 18 | 19 | function M.getAnswer() 20 | return 42 21 | end 22 | 23 | return M 24 | 25 | $BODY$ LANGUAGE pllua; 26 | CREATE OR REPLACE FUNCTION pg_temp.pgfunc_test() 27 | RETURNS SETOF text AS $$ 28 | local quote_ident = pgfunc("quote_ident(text)") 29 | coroutine.yield(quote_ident("int")) 30 | local right = pgfunc("right(text,int)") 31 | coroutine.yield(right('abcde', 2)) 32 | local factorial = pgfunc("factorial(int8)") 33 | coroutine.yield(tostring(factorial(50))) 34 | local i_void = pgfunc("i_void(internal)") 35 | coroutine.yield(i_void.getAnswer()) 36 | $$ LANGUAGE pllua; 37 | select pg_temp.pgfunc_test(); 38 | pgfunc_test 39 | ------------------------------------------------------------------- 40 | "int" 41 | de 42 | 30414093201713378043612608166064768844377641568960512000000000000 43 | 42 44 | (4 rows) 45 | 46 | do $$ 47 | print(pgfunc('quote_nullable(text)')(nil)) 48 | $$ language pllua; 49 | INFO: NULL 50 | create or replace function pg_temp.throw_error(text) returns void as $$ 51 | begin 52 | raise exception '%', $1; 53 | end 54 | $$ language plpgsql; 55 | do $$ 56 | pgfunc('pg_temp.throw_error(text)',{only_internal=false})("exception test") 57 | $$ language pllua; 58 | ERROR: exception test 59 | CONTEXT: PL/pgSQL function pg_temp_2.throw_error(text) line 3 at RAISE 60 | stack traceback(trusted): 61 | [C]: ? 62 | [string "anonymous"]:2: in main chunk 63 | do $$ 64 | local f = pgfunc('pg_temp.throw_error(text)',{only_internal=false}) 65 | print(pcall(f, "exception test")) 66 | $$ language pllua; 67 | INFO: false exception test 68 | create or replace function pg_temp.no_throw() returns json as $$ 69 | select '{"a":5, "b":10}'::json 70 | $$ language sql; 71 | do $$ 72 | local f = pgfunc('pg_temp.no_throw()',{only_internal=false, throwable=false}) 73 | print(f()) 74 | $$ language pllua; 75 | INFO: {"a":5, "b":10} 76 | CREATE or replace FUNCTION pg_temp.arg_count(a1 integer,a2 integer,a3 integer,a4 integer,a5 integer 77 | ,a6 integer,a7 integer,a8 integer,a9 integer,a10 integer 78 | ,a11 integer,a12 integer,a13 integer,a14 integer,a15 integer ) returns integer AS 79 | $$ 80 | begin 81 | return a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12+a13+a14+a15; 82 | end 83 | $$ 84 | LANGUAGE plpgsql; 85 | do $$ 86 | local f = pgfunc([[pg_temp.arg_count(integer, integer, integer, integer, integer, 87 | integer, integer, integer, integer, integer, 88 | integer, integer, integer, integer, integer ) ]],{only_internal=false}); 89 | print(f(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)) 90 | $$ language pllua; 91 | INFO: 120 92 | CREATE or replace FUNCTION pg_temp.inoutf(a integer, INOUT b text, INOUT c text) AS 93 | $$ 94 | begin 95 | c = a||'c:'||c; 96 | b = 'b:'||b; 97 | end 98 | $$ 99 | LANGUAGE plpgsql; 100 | do $$ 101 | local f = pgfunc('pg_temp.inoutf(integer,text,text)',{only_internal=false}); 102 | local r = f(5, 'ABC', 'd') 103 | print(r.b) 104 | print(r.c) 105 | $$ language pllua; 106 | INFO: b:ABC 107 | INFO: 5c:d 108 | do $$ 109 | local f = pgfunc('generate_series(int,int)') 110 | print('4-6') 111 | for rr in f(4,6) do 112 | print(rr) 113 | end 114 | 115 | print('1-3') 116 | for rr in f(1,3) do 117 | print(rr) 118 | end 119 | 120 | $$ language pllua; 121 | INFO: 4-6 122 | INFO: 4 123 | INFO: 5 124 | INFO: 6 125 | INFO: 1-3 126 | INFO: 1 127 | INFO: 2 128 | INFO: 3 129 | do $$ 130 | local f = pgfunc('generate_series(int,int)') 131 | for rr in f(1,3) do 132 | 133 | for rr in f(41,43) do 134 | print(rr) 135 | end 136 | print(rr) 137 | end 138 | $$ language pllua; 139 | INFO: 41 140 | INFO: 42 141 | INFO: 43 142 | INFO: 1 143 | INFO: 41 144 | INFO: 42 145 | INFO: 43 146 | INFO: 2 147 | INFO: 41 148 | INFO: 42 149 | INFO: 43 150 | INFO: 3 151 | -- Type wrapper 152 | create extension hstore; 153 | do $$ 154 | local hstore = { 155 | fromstring = function(text) 156 | return fromstring('hstore',text) 157 | end, 158 | akeys = pgfunc('akeys(hstore)',{only_internal = false}), 159 | each = pgfunc('each(hstore)',{only_internal = false}) --orig:each(IN hs hstore, OUT key text, OUT value text) 160 | } 161 | 162 | local v = hstore.fromstring[[ 163 | "paperback" => "542", 164 | "publisher" => "postgresql.org", 165 | "language" => "English", 166 | "ISBN-13" => "978-0000000000", 167 | "weight" => "24.1 ounces" 168 | ]] 169 | 170 | print(v) 171 | 172 | for _,v in ipairs(hstore.akeys(v)) do 173 | print (v) 174 | end 175 | 176 | for hv in hstore.each(v) do 177 | print ("key = " .. hv.key .. " value = "..hv.value) 178 | end 179 | $$ language pllua; 180 | INFO: "weight"=>"24.1 ounces", "ISBN-13"=>"978-0000000000", "language"=>"English", "paperback"=>"542", "publisher"=>"postgresql.org" 181 | INFO: weight 182 | INFO: ISBN-13 183 | INFO: language 184 | INFO: paperback 185 | INFO: publisher 186 | INFO: key = weight value = 24.1 ounces 187 | INFO: key = ISBN-13 value = 978-0000000000 188 | INFO: key = language value = English 189 | INFO: key = paperback value = 542 190 | INFO: key = publisher value = postgresql.org 191 | create or replace function getnull() returns text as $$ 192 | begin 193 | return null; 194 | end 195 | $$ language plpgsql; 196 | do $$ 197 | local a = pgfunc('getnull()',{only_internal = false}) 198 | print(a()) 199 | $$ language pllua; 200 | INFO: nil 201 | -------------------------------------------------------------------------------- /lua_int64.c: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2012-2013 codingow.com 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of the Software, 9 | and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | 24 | source:https://github.com/idning/lua-int64.git 25 | */ 26 | 27 | #include "lua_int64.h" 28 | 29 | 30 | #include 31 | #include 32 | #include 33 | #ifndef _MSC_VER 34 | #include 35 | #include 36 | #endif 37 | 38 | #include "pllua.h" 39 | #include "pllua_errors.h" 40 | 41 | static const char int64_type_name[] = "int64"; 42 | 43 | static int64_t check_int64(lua_State* L, int idx) {\ 44 | int64_t* p; 45 | luaL_checktype(L,idx,LUA_TUSERDATA); 46 | p = (int64_t*)luaL_checkudata(L, idx, int64_type_name); 47 | return p ? *p : 0; 48 | } 49 | 50 | #define get_ab_values \ 51 | int64_t a;\ 52 | int64_t b;\ 53 | if(lua_isnil(L,1) || lua_isnil(L,2)) \ 54 | return luaL_error(L, "attempt to perform arithmetic on a nil value"); \ 55 | a = get_int64(L,1); \ 56 | b = get_int64(L,2) 57 | 58 | static int64_t 59 | get_int64(lua_State *L, int index) { 60 | int type = lua_type(L,index); 61 | int64_t value = 0; 62 | 63 | switch(type) { 64 | case LUA_TNUMBER: { 65 | return (int64_t)(luaL_checknumber(L,index)); 66 | } 67 | case LUA_TSTRING: { 68 | 69 | #ifdef _MSC_VER 70 | return(int64_t)_strtoi64(lua_tostring(L, index), NULL, 0); 71 | #else 72 | return(int64_t)strtoll(lua_tostring(L, index), NULL, 0); 73 | #endif 74 | } 75 | case LUA_TUSERDATA: 76 | value = check_int64(L, index); 77 | break; 78 | 79 | default: 80 | return luaL_error(L, "argument %d error type %s", index, lua_typename(L,type)); 81 | } 82 | return value; 83 | } 84 | 85 | static inline void 86 | _pushint64(lua_State *L, int64_t n) { 87 | int64_t * udata = (int64_t *)lua_newuserdata(L, sizeof(int64_t )); 88 | *udata = n; 89 | luaL_getmetatable(L, int64_type_name); 90 | lua_setmetatable(L, -2); 91 | } 92 | 93 | static int 94 | int64_add(lua_State *L) { 95 | get_ab_values; 96 | _pushint64(L, a+b); 97 | 98 | return 1; 99 | } 100 | 101 | 102 | static int 103 | int64_new(lua_State *L) { 104 | int top = lua_gettop(L); 105 | 106 | int64_t n; 107 | switch(top) { 108 | case 0 : 109 | _pushint64(L,0); 110 | break; 111 | case 1 : 112 | n = get_int64(L,1); 113 | lua_pop(L, 1); 114 | _pushint64(L,n); 115 | break; 116 | default: { 117 | const char * str; 118 | int base = luaL_checkinteger(L,2); 119 | if (base < 2) { 120 | luaL_error(L, "base must be >= 2"); 121 | } 122 | str = luaL_checkstring(L, 1); 123 | #ifdef _MSC_VER 124 | n = _strtoi64(str, NULL, base); 125 | #else 126 | n = strtoll(str, NULL, base); 127 | #endif 128 | 129 | _pushint64(L,n); 130 | break; 131 | } 132 | } 133 | return 1; 134 | } 135 | 136 | 137 | static int 138 | int64_sub(lua_State *L) { 139 | get_ab_values; 140 | _pushint64(L, a-b); 141 | return 1; 142 | } 143 | 144 | static int 145 | int64_mul(lua_State *L) { 146 | get_ab_values; 147 | _pushint64(L, a * b); 148 | return 1; 149 | } 150 | 151 | static int 152 | int64_div(lua_State *L) { 153 | get_ab_values; 154 | if (b == 0) { 155 | return luaL_error(L, "div by zero"); 156 | } 157 | _pushint64(L, a / b); 158 | 159 | return 1; 160 | } 161 | 162 | static int 163 | int64_mod(lua_State *L) { 164 | get_ab_values; 165 | if (b == 0) { 166 | return luaL_error(L, "mod by zero"); 167 | } 168 | _pushint64(L, a % b); 169 | 170 | return 1; 171 | } 172 | 173 | static int64_t 174 | _pow64(int64_t a, int64_t b) { 175 | int64_t a2; 176 | if (b == 1) { 177 | return a; 178 | } 179 | a2 = a * a; 180 | if (b % 2 == 1) { 181 | return _pow64(a2, b/2) * a; 182 | } else { 183 | return _pow64(a2, b/2); 184 | } 185 | } 186 | 187 | static int 188 | int64_pow(lua_State *L) { 189 | int64_t p; 190 | get_ab_values; 191 | if (b > 0) { 192 | p = _pow64(a,b); 193 | } else if (b == 0) { 194 | p = 1; 195 | } else { 196 | return luaL_error(L, "pow by nagtive number %d",(int)b); 197 | } 198 | _pushint64(L, p); 199 | 200 | return 1; 201 | } 202 | 203 | static int 204 | int64_unm(lua_State *L) { 205 | int64_t a = get_int64(L,1); 206 | _pushint64(L, -a); 207 | return 1; 208 | } 209 | 210 | static int 211 | int64_eq(lua_State *L) { 212 | get_ab_values; 213 | lua_pushboolean(L,a == b); 214 | return 1; 215 | } 216 | 217 | static int 218 | int64_lt(lua_State *L) { 219 | get_ab_values; 220 | lua_pushboolean(L,a < b); 221 | return 1; 222 | } 223 | 224 | static int 225 | int64_le(lua_State *L) { 226 | get_ab_values; 227 | lua_pushboolean(L,a <= b); 228 | return 1; 229 | } 230 | 231 | static int 232 | int64_len(lua_State *L) { 233 | int64_t a = get_int64(L,1); 234 | lua_pushnumber(L,(lua_Number)a); 235 | return 1; 236 | } 237 | 238 | 239 | static int 240 | tostring(lua_State *L) { 241 | static char hex[16] = "0123456789ABCDEF"; 242 | int64_t n = check_int64(L,1); 243 | if (lua_gettop(L) == 1) { 244 | char str[24]; 245 | #ifndef _MSC_VER 246 | snprintf(str, sizeof(str), "%" PRId64, n); 247 | #else 248 | snprintf(str, sizeof(str), "%10lld", (long long)n); 249 | #endif 250 | lua_pushstring(L,str); 251 | } else { 252 | int i; 253 | char buffer[64]; 254 | int base = luaL_checkinteger(L,2); 255 | int shift = 1; 256 | int mask = 2; 257 | switch(base) { 258 | case 0: { 259 | unsigned char buffer[8]; 260 | int i; 261 | for (i=0;i<8;i++) { 262 | buffer[i] = (n >> (i*8)) & 0xff; 263 | } 264 | lua_pushlstring(L,(const char *)buffer, 8); 265 | return 1; 266 | } 267 | case 10: { 268 | int buffer[32], i; 269 | 270 | int64_t dec = (int64_t)n; 271 | luaL_Buffer b; 272 | #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502 273 | luaL_buffinit(L, &b); 274 | #else 275 | luaL_buffinitsize(L , &b , 28); 276 | #endif 277 | if (dec<0) { 278 | luaL_addchar(&b, '-'); 279 | dec = -dec; 280 | } 281 | 282 | for (i=0;i<32;i++) { 283 | buffer[i] = dec%10; 284 | dec /= 10; 285 | if (dec == 0) 286 | break; 287 | } 288 | while (i>=0) { 289 | luaL_addchar(&b, hex[buffer[i]]); 290 | --i; 291 | } 292 | luaL_pushresult(&b); 293 | return 1; 294 | } 295 | case 2: 296 | shift = 1; 297 | mask = 1; 298 | break; 299 | case 8: 300 | shift = 3; 301 | mask = 7; 302 | break; 303 | case 16: 304 | shift = 4; 305 | mask = 0xf; 306 | break; 307 | default: 308 | luaL_error(L, "Unsupport base %d",base); 309 | break; 310 | } 311 | 312 | for (i=0;i<64;i+=shift) { 313 | buffer[i/shift] = hex[(n>>(64-shift-i)) & mask]; 314 | } 315 | lua_pushlstring(L, buffer, 64 / shift); 316 | } 317 | return 1; 318 | } 319 | 320 | 321 | #undef get_ab_values 322 | 323 | 324 | void register_int64(lua_State *L) 325 | { 326 | if (sizeof(long long int)==sizeof(int64_t)) { 327 | luaL_Reg regs[] = 328 | { 329 | { "new", int64_new }, 330 | { "tostring", tostring }, 331 | { "__add", int64_add }, 332 | { "__sub", int64_sub }, 333 | { "__mul", int64_mul }, 334 | { "__div", int64_div }, 335 | { "__mod", int64_mod }, 336 | { "__unm", int64_unm }, 337 | { "__pow", int64_pow }, 338 | { "__eq", int64_eq }, 339 | { "__lt", int64_lt }, 340 | { "__le", int64_le }, 341 | { "__len", int64_len }, 342 | { "__tostring", tostring }, 343 | 344 | { NULL, NULL } 345 | }; 346 | 347 | 348 | luaL_newmetatable(L, int64_type_name); 349 | luaL_setfuncs(L, regs, 0); 350 | lua_pushvalue(L, -1); 351 | lua_setfield(L, -1, "__index"); 352 | lua_setglobal(L, int64_type_name); 353 | 354 | } 355 | } 356 | 357 | int64 get64lua(lua_State *L, int index) 358 | { 359 | return get_int64(L,index); 360 | } 361 | 362 | 363 | void setInt64lua(lua_State *L, int64 value) 364 | { 365 | _pushint64(L,value); 366 | } 367 | -------------------------------------------------------------------------------- /pllua_errors.c: -------------------------------------------------------------------------------- 1 | #include "pllua_errors.h" 2 | #include "plluacommon.h" 3 | 4 | extern LVMInfo lvm_info[2]; 5 | 6 | static const char error_type_name[] = "pllua_error"; 7 | 8 | int luaB_assert (lua_State *L) { 9 | luaL_checkany(L, 1); 10 | if (!lua_toboolean(L, 1)) 11 | return luaL_error(L, "%s", luaL_optstring(L, 2, "assertion failed!")); 12 | return lua_gettop(L); 13 | } 14 | 15 | int luaB_error (lua_State *L) { 16 | #if LUA_VERSION_NUM >= 503 17 | int level = luaL_optinteger(L, 2, 1); 18 | #else 19 | int level = luaL_optint(L, 2, 1); 20 | #endif 21 | lua_settop(L, 1); 22 | if (lua_isnoneornil(L, 1)){ 23 | if (lua_isnil(L, 1)){ 24 | lua_pop(L, 1); 25 | } 26 | if (level >0){ 27 | luaL_where(L, level); 28 | lua_pushstring(L, "no exception data"); 29 | lua_concat(L, 2); 30 | }else{ 31 | lua_pushstring(L, "no exception data"); 32 | } 33 | }else 34 | // if (lua_isstring(L, 1) && level > 0) { /* add extra information? */ 35 | // luaL_where(L, level); 36 | // lua_pushvalue(L, 1); 37 | // lua_concat(L, 2); 38 | // } else 39 | if (lua_istable(L,1)){ 40 | set_error_mt(L); 41 | } 42 | return lua_error(L); 43 | } 44 | 45 | 46 | int luaL_error_skip_where (lua_State *L, const char *fmt, ...) { 47 | va_list argp; 48 | va_start(argp, fmt); 49 | /*luaL_where(L, 1);*/ 50 | lua_pushvfstring(L, fmt, argp); 51 | va_end(argp); 52 | /*lua_concat(L, 2);*/ 53 | return lua_error(L); 54 | } 55 | 56 | static int error_tostring(lua_State *L) 57 | { 58 | lua_pushstring(L, "message"); 59 | lua_rawget(L, -2); 60 | return 1; 61 | } 62 | 63 | static luaL_Reg regs[] = 64 | { 65 | {"__tostring", error_tostring}, 66 | { NULL, NULL } 67 | }; 68 | 69 | void register_error_mt(lua_State *L) 70 | { 71 | lua_newtable(L); 72 | lua_pushlightuserdata(L, (void *) error_type_name); 73 | lua_pushvalue(L, -2); 74 | lua_rawset(L, LUA_REGISTRYINDEX); 75 | 76 | luaP_register(L, regs); 77 | lua_pop(L, 1); 78 | } 79 | 80 | void set_error_mt(lua_State *L) 81 | { 82 | luaP_getfield(L, error_type_name); 83 | lua_setmetatable(L, -2); 84 | } 85 | 86 | void push_spi_error(lua_State *L, MemoryContext oldcontext) 87 | { 88 | ErrorData *edata; 89 | 90 | /* Save error info */ 91 | MemoryContextSwitchTo(oldcontext); 92 | edata = CopyErrorData(); 93 | FlushErrorState(); 94 | lua_newtable(L); 95 | if (edata->message){ 96 | lua_pushstring(L, edata->message); //"no exception data" 97 | lua_setfield(L, -2, "message"); 98 | } else { 99 | lua_pushstring(L, "no exception data"); 100 | lua_setfield(L, -2, "message"); 101 | } 102 | 103 | if (edata->detail){ 104 | lua_pushstring(L, edata->detail); 105 | lua_setfield(L, -2, "detail"); 106 | } 107 | if (edata->context){ 108 | lua_pushstring(L, edata->context); 109 | lua_setfield(L, -2, "context"); 110 | } 111 | if (edata->hint){ 112 | lua_pushstring(L, edata->hint); 113 | lua_setfield(L, -2, "hint"); 114 | } 115 | if (edata->sqlerrcode){ 116 | lua_pushinteger(L, edata->sqlerrcode); 117 | lua_setfield(L, -2, "sqlerrcode"); 118 | } 119 | set_error_mt(L); 120 | 121 | FreeErrorData(edata); 122 | } 123 | 124 | 125 | static void pllua_parse_error(lua_State *L, ErrorData *edata){ 126 | lua_pushnil(L); 127 | while (lua_next(L, -2) != 0) { 128 | if (lua_type(L, -2) == LUA_TSTRING){ 129 | const char *key = lua_tostring(L, -2); 130 | if (lua_type(L, -1) == LUA_TSTRING){ 131 | if (strcmp(key, "message") == 0){ 132 | edata->message = pstrdup( lua_tostring(L, -1) ); 133 | } else if (strcmp(key, "detail") == 0){ 134 | edata->detail = pstrdup( lua_tostring(L, -1) ); 135 | } else if (strcmp(key, "hint") == 0){ 136 | edata->hint = pstrdup( lua_tostring(L, -1) ); 137 | } else if (strcmp(key, "context") == 0){ 138 | edata->context = pstrdup( lua_tostring(L, -1) ); 139 | } 140 | 141 | }else if (lua_type(L, -1) == LUA_TNUMBER){ 142 | if (strcmp(key, "sqlerrcode") == 0){ 143 | edata->sqlerrcode = (int)( lua_tonumber(L, -1) ); 144 | } 145 | } 146 | } 147 | lua_pop(L, 1); 148 | } 149 | } 150 | 151 | void luatable_topgerror(lua_State *L) 152 | { 153 | luatable_report(L, ERROR); 154 | } 155 | 156 | void luatable_report(lua_State *L, int elevel) 157 | { 158 | ErrorData edata; 159 | 160 | char *query = NULL; 161 | int position = 0; 162 | 163 | edata.message = NULL; 164 | edata.sqlerrcode = 0; 165 | edata.detail = NULL; 166 | edata.hint = NULL; 167 | edata.context = NULL; 168 | 169 | pllua_parse_error(L, &edata); 170 | lua_pop(L, lua_gettop(L)); 171 | 172 | elevel = Min(elevel, ERROR); 173 | 174 | ereport(elevel, 175 | (errcode(edata.sqlerrcode ? edata.sqlerrcode : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), 176 | errmsg_internal("%s", edata.message ? edata.message : "no exception data"), 177 | (edata.detail) ? errdetail_internal("%s", edata.detail) : 0, 178 | (edata.context) ? errcontext("%s", edata.context) : 0, 179 | (edata.hint) ? errhint("%s", edata.hint) : 0, 180 | (query) ? internalerrquery(query) : 0, 181 | (position) ? internalerrposition(position) : 0)); 182 | } 183 | 184 | 185 | 186 | /* this is a copy from lua source to call debug.traceback without access it from metatables */ 187 | #define LEVELS1 12 /* size of the first part of the stack */ 188 | #define LEVELS2 10 /* size of the second part of the stack */ 189 | 190 | static lua_State *getthread (lua_State *L, int *arg) { 191 | if (lua_isthread(L, 1)) { 192 | *arg = 1; 193 | return lua_tothread(L, 1); 194 | } 195 | else { 196 | *arg = 0; 197 | return L; 198 | } 199 | } 200 | 201 | static int db_errorfb (lua_State *L) { 202 | int level; 203 | int firstpart = 1; /* still before eventual `...' */ 204 | int arg; 205 | lua_State *L1 = getthread(L, &arg); 206 | lua_Debug ar; 207 | luaL_Buffer b; 208 | if (lua_isnumber(L, arg+2)) { 209 | level = (int)lua_tointeger(L, arg+2); 210 | lua_pop(L, 1); 211 | } 212 | else 213 | level = (L == L1) ? 1 : 0; /* level 0 may be this own function */ 214 | if (lua_gettop(L) == arg) 215 | lua_pushliteral(L, ""); 216 | else if (!lua_isstring(L, arg+1)) return 1; /* message is not a string */ 217 | else lua_pushliteral(L, "\n"); 218 | 219 | luaL_buffinit(L, &b); 220 | luaL_addstring(&b, "stack traceback("); 221 | luaL_addstring(&b, lvm_info[pllua_getmaster_index(L)].name); 222 | luaL_addstring(&b, "):"); 223 | luaL_pushresult(&b); 224 | //lua_pushliteral(L, "stack traceback:"); 225 | while (lua_getstack(L1, level++, &ar)) { 226 | if (level > LEVELS1 && firstpart) { 227 | /* no more than `LEVELS2' more levels? */ 228 | if (!lua_getstack(L1, level+LEVELS2, &ar)) 229 | level--; /* keep going */ 230 | else { 231 | lua_pushliteral(L, "\n\t..."); /* too many levels */ 232 | while (lua_getstack(L1, level+LEVELS2, &ar)) /* find last levels */ 233 | level++; 234 | } 235 | firstpart = 0; 236 | continue; 237 | } 238 | lua_pushliteral(L, "\n\t"); 239 | lua_getinfo(L1, "Snl", &ar); 240 | lua_pushfstring(L, "%s:", ar.short_src); 241 | if (ar.currentline > 0) 242 | lua_pushfstring(L, "%d:", ar.currentline); 243 | if (*ar.namewhat != '\0') /* is there a name? */ 244 | lua_pushfstring(L, " in function " LUA_QS, ar.name); 245 | else { 246 | if (*ar.what == 'm') /* main? */ 247 | lua_pushfstring(L, " in main chunk"); 248 | else if (*ar.what == 'C' || *ar.what == 't') 249 | lua_pushliteral(L, " ?"); /* C function or tail call */ 250 | else 251 | lua_pushfstring(L, " in function <%s:%d>", 252 | ar.short_src, ar.linedefined); 253 | } 254 | lua_concat(L, lua_gettop(L) - arg); 255 | } 256 | lua_concat(L, lua_gettop(L) - arg); 257 | return 1; 258 | } 259 | 260 | int traceback (lua_State *L) { 261 | int stateIndex = pllua_getmaster_index(L); 262 | if (lvm_info[stateIndex].hasTraceback) 263 | return 1; 264 | 265 | if (lua_isstring(L, 1)){ /* 'message' not a string? */ 266 | lua_newtable(L); 267 | 268 | lua_pushcfunction(L, db_errorfb); 269 | lua_pushstring(L,""); /* empty concat string for context */ 270 | lua_pushinteger(L, 2); /* skip this function and traceback */ 271 | lua_call(L, 2, 1); /* call debug.traceback */ 272 | lvm_info[stateIndex].hasTraceback = true; 273 | lua_setfield(L, -2, "context"); 274 | lua_swap(L); /*text <-> table*/ 275 | lua_setfield(L, -2, "message"); 276 | set_error_mt(L); 277 | return 1; 278 | 279 | }else if (lua_istable(L,1)){ 280 | 281 | lua_pushstring(L,"context"); 282 | lua_rawget(L, -2); 283 | if (!lua_isstring(L, -1)){ 284 | lua_pop(L,1); 285 | lua_pushstring(L,""); /* empty concat string for context */ 286 | } 287 | 288 | lua_pushcfunction(L, db_errorfb); 289 | lua_swap(L); 290 | 291 | lua_pushinteger(L, 2); /* skip this function and traceback */ 292 | lua_call(L, 2, 1); /* call debug.traceback */ 293 | lvm_info[stateIndex].hasTraceback = true; 294 | 295 | lua_setfield(L, -2, "context"); 296 | 297 | return 1; 298 | } 299 | 300 | return 1; /* keep it intact */ 301 | } 302 | 303 | -------------------------------------------------------------------------------- /sql/plluatest.sql: -------------------------------------------------------------------------------- 1 | \set ECHO none 2 | --\i pllua.sql 3 | CREATE EXTENSION pllua; 4 | \set ECHO all 5 | 6 | -- minimal function 7 | CREATE FUNCTION hello(name text) 8 | RETURNS text AS $$ 9 | return string.format("Hello, %s!", name) 10 | $$ LANGUAGE pllua; 11 | SELECT hello('PostgreSQL'); 12 | 13 | -- null handling 14 | CREATE FUNCTION max(a integer, b integer) RETURNS integer AS $$ 15 | if a == nil then return b end -- first arg is NULL? 16 | if b == nil then return a end -- second arg is NULL? 17 | return a > b and a or b -- return max(a, b) 18 | $$ LANGUAGE pllua; 19 | SELECT max(1,2), max(2,1), max(2,null), max(null, 2), max(null, null); 20 | 21 | -- plain recursive 22 | CREATE FUNCTION fib(n int) RETURNS int AS $$ 23 | if n < 3 then 24 | return n 25 | else 26 | return fib(n - 1) + fib(n - 2) 27 | end 28 | $$ LANGUAGE pllua; 29 | SELECT fib(4); 30 | 31 | -- memoized 32 | CREATE FUNCTION fibm(n integer) RETURNS integer AS $$ 33 | if n < 3 then return n 34 | else 35 | local v = _U[n] 36 | if not v then 37 | v = fibm(n - 1) + fibm(n - 2) 38 | _U[n] = v 39 | end 40 | return v 41 | end 42 | end 43 | do _U = {} 44 | $$ LANGUAGE pllua; 45 | SELECT fibm(4); 46 | 47 | -- tail recursive 48 | CREATE FUNCTION fibt(n integer) RETURNS integer AS $$ 49 | return _U(n, 0, 1) 50 | end 51 | _U = function(n, a, b) 52 | if n < 1 then return b 53 | else return _U(n - 1, b, a + b) end 54 | $$ LANGUAGE pllua; 55 | SELECT fibt(4); 56 | 57 | -- iterator 58 | CREATE FUNCTION fibi() RETURNS integer AS $$ 59 | while true do 60 | _U.curr, _U.next = _U.next, _U.curr + _U.next 61 | coroutine.yield(_U.curr) 62 | end 63 | end 64 | do 65 | _U = {curr = 0, next = 1} 66 | fibi = coroutine.wrap(fibi) 67 | $$ LANGUAGE pllua; 68 | SELECT fibi(), fibi(), fibi(), fibi(), fibi(); 69 | SELECT fibi(), fibi(), fibi(), fibi(), fibi(); 70 | 71 | -- upvalue 72 | CREATE FUNCTION counter() RETURNS int AS $$ 73 | while true do 74 | _U = _U + 1 75 | coroutine.yield(_U) 76 | end 77 | end 78 | do 79 | _U = 0 -- counter 80 | counter = coroutine.wrap(counter) 81 | $$ LANGUAGE pllua; 82 | SELECT counter(); 83 | SELECT counter(); 84 | SELECT counter(); 85 | 86 | -- record input 87 | CREATE TYPE greeting AS (how text, who text); 88 | CREATE FUNCTION makegreeting (g greeting, f text) RETURNS text AS $$ 89 | return string.format(f, g.how, g.who) 90 | $$ LANGUAGE pllua; 91 | SELECT makegreeting(('how', 'who'), '%s, %s!'); 92 | 93 | -- array, record output 94 | CREATE FUNCTION greetingset (how text, who text[]) 95 | RETURNS SETOF greeting AS $$ 96 | for _, name in ipairs(who) do 97 | coroutine.yield{how=how, who=name} 98 | end 99 | $$ LANGUAGE pllua; 100 | SELECT makegreeting(greetingset, '%s, %s!') FROM 101 | (SELECT greetingset('Hello', ARRAY['foo', 'bar', 'psql'])) AS q; 102 | 103 | -- more array, upvalue 104 | CREATE FUNCTION perm (a text[]) RETURNS SETOF text[] AS $$ 105 | _U(a, #a) 106 | end 107 | do 108 | _U = function (a, n) -- permgen in PiL 109 | if n == 0 then 110 | coroutine.yield(a) -- return next SRF row 111 | else 112 | for i = 1, n do 113 | a[n], a[i] = a[i], a[n] -- i-th element as last one 114 | _U(a, n - 1) -- recurse on head 115 | a[n], a[i] = a[i], a[n] -- restore i-th element 116 | end 117 | end 118 | end 119 | $$ LANGUAGE pllua; 120 | SELECT * FROM perm(array['1', '2', '3']); 121 | 122 | -- shared variables 123 | CREATE FUNCTION getcounter() RETURNS integer AS $$ 124 | if shared.counter == nil then -- not cached? 125 | setshared("counter", 0) 126 | end 127 | return counter -- _G.counter == shared.counter 128 | $$ LANGUAGE pllua; 129 | CREATE FUNCTION setcounter(c integer) RETURNS void AS $$ 130 | if shared.counter == nil then -- not cached? 131 | setshared("counter", c) 132 | else 133 | counter = c -- _G.counter == shared.counter 134 | end 135 | $$ LANGUAGE pllua; 136 | SELECT getcounter(); 137 | SELECT setcounter(5); 138 | SELECT getcounter(); 139 | 140 | -- SPI usage 141 | 142 | CREATE TABLE sometable ( sid int4, sname text, sdata text); 143 | INSERT INTO sometable VALUES (1, 'name', 'data'); 144 | 145 | CREATE FUNCTION get_rows (i_name text) RETURNS SETOF sometable AS $$ 146 | if _U == nil then -- plan not cached? 147 | local cmd = "SELECT sid, sname, sdata FROM sometable WHERE sname = $1" 148 | _U = server.prepare(cmd, {"text"}):save() 149 | end 150 | local c = _U:getcursor({i_name}, true) -- read-only 151 | while true do 152 | local r = c:fetch(1) 153 | if r == nil then break end 154 | r = r[1] 155 | coroutine.yield{sid=r.sid, sname=r.sname, sdata=r.sdata} 156 | end 157 | c:close() 158 | $$ LANGUAGE pllua; 159 | 160 | SELECT * FROM get_rows('name'); 161 | 162 | 163 | SET client_min_messages = warning; 164 | CREATE TABLE tree (id INT PRIMARY KEY, lchild INT, rchild INT); 165 | RESET client_min_messages; 166 | 167 | CREATE FUNCTION filltree (t text, n int) RETURNS void AS $$ 168 | local p = server.prepare("insert into " .. t .. " values($1, $2, $3)", 169 | {"int4", "int4", "int4"}) 170 | for i = 1, n do 171 | local lchild, rchild = 2 * i, 2 * i + 1 -- siblings 172 | p:execute{i, lchild, rchild} -- insert values 173 | end 174 | $$ LANGUAGE pllua; 175 | SELECT filltree('tree', 10); 176 | 177 | CREATE FUNCTION preorder (t text, s int) RETURNS SETOF int AS $$ 178 | coroutine.yield(s) 179 | local q = server.execute("select * from " .. t .. " where id=" .. s, 180 | true, 1) -- read-only, only 1 result 181 | if q ~= nil then 182 | local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query 183 | if lchild ~= nil then preorder(t, lchild) end 184 | if rchild ~= nil then preorder(t, rchild) end 185 | end 186 | $$ LANGUAGE pllua; 187 | SELECT * from preorder('tree', 1); 188 | 189 | CREATE FUNCTION postorder (t text, s int) RETURNS SETOF int AS $$ 190 | local p = _U[t] 191 | if p == nil then -- plan not cached? 192 | p = server.prepare("select * from " .. t .. " where id=$1", {"int4"}) 193 | _U[t] = p:save() 194 | end 195 | local c = p:getcursor({s}, true) -- read-only 196 | local q = c:fetch(1) -- one row 197 | if q ~= nil then 198 | local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query 199 | c:close() 200 | if lchild ~= nil then postorder(t, lchild) end 201 | if rchild ~= nil then postorder(t, rchild) end 202 | end 203 | coroutine.yield(s) 204 | end 205 | do _U = {} -- plan cache 206 | $$ LANGUAGE pllua; 207 | SELECT * FROM postorder('tree', 1); 208 | 209 | 210 | -- trigger 211 | CREATE FUNCTION treetrigger() RETURNS trigger AS $$ 212 | local row, operation = trigger.row, trigger.operation 213 | if operation == "update" then 214 | trigger.row = nil -- updates not allowed 215 | elseif operation == "insert" then 216 | local id, lchild, rchild = row.id, row.lchild, row.rchild 217 | if lchild == rchild or id == lchild or id == rchild -- avoid loops 218 | or (lchild ~= nil and _U.intree(lchild)) -- avoid cycles 219 | or (rchild ~= nil and _U.intree(rchild)) 220 | or (_U.nonemptytree() and not _U.isleaf(id)) -- not leaf? 221 | then 222 | trigger.row = nil -- skip operation 223 | end 224 | else -- operation == "delete" 225 | if not _U.isleafparent(row.id) then -- not both leaf parent? 226 | trigger.row = nil 227 | end 228 | end 229 | end 230 | do 231 | local getter = function(cmd, ...) 232 | local plan = server.prepare(cmd, {...}):save() 233 | return function(...) 234 | return plan:execute({...}, true) ~= nil 235 | end 236 | end 237 | _U = { -- plan closures 238 | nonemptytree = getter("select * from tree"), 239 | intree = getter("select node from (select id as node from tree " 240 | .. "union select lchild from tree union select rchild from tree) as q " 241 | .. "where node=$1", "int4"), 242 | isleaf = getter("select leaf from (select lchild as leaf from tree " 243 | .. "union select rchild from tree except select id from tree) as q " 244 | .. "where leaf=$1", "int4"), 245 | isleafparent = getter("select lp from (select id as lp from tree " 246 | .. "except select ti.id from tree ti join tree tl on ti.lchild=tl.id " 247 | .. "join tree tr on ti.rchild=tr.id) as q where lp=$1", "int4") 248 | } 249 | $$ LANGUAGE pllua; 250 | 251 | CREATE TRIGGER tree_trigger BEFORE INSERT OR UPDATE OR DELETE ON tree 252 | FOR EACH ROW EXECUTE PROCEDURE treetrigger(); 253 | 254 | SELECT * FROM tree WHERE id = 1; 255 | UPDATE tree SET rchild = 1 WHERE id = 1; 256 | SELECT * FROM tree WHERE id = 10; 257 | DELETE FROM tree where id = 10; 258 | DELETE FROM tree where id = 1; 259 | 260 | -- passthru types 261 | CREATE FUNCTION echo_int2(arg int2) RETURNS int2 AS $$ return arg $$ LANGUAGE pllua; 262 | SELECT echo_int2('12345'); 263 | CREATE FUNCTION echo_int4(arg int4) RETURNS int4 AS $$ return arg $$ LANGUAGE pllua; 264 | SELECT echo_int4('1234567890'); 265 | CREATE FUNCTION echo_int8(arg int8) RETURNS int8 AS $$ return arg $$ LANGUAGE pllua; 266 | SELECT echo_int8('1234567890'); 267 | SELECT echo_int8('12345678901236789'); 268 | SELECT echo_int8('1234567890123456789'); 269 | CREATE FUNCTION echo_text(arg text) RETURNS text AS $$ return arg $$ LANGUAGE pllua; 270 | SELECT echo_text('qwe''qwe'); 271 | CREATE FUNCTION echo_bytea(arg bytea) RETURNS bytea AS $$ return arg $$ LANGUAGE pllua; 272 | SELECT echo_bytea('qwe''qwe'); 273 | SELECT echo_bytea(E'q\\000w\\001e''q\\\\we'); 274 | CREATE FUNCTION echo_timestamptz(arg timestamptz) RETURNS timestamptz AS $$ return arg $$ LANGUAGE pllua; 275 | SELECT echo_timestamptz('2007-01-06 11:11 UTC') AT TIME ZONE 'UTC'; 276 | CREATE FUNCTION echo_timestamp(arg timestamp) RETURNS timestamp AS $$ return arg $$ LANGUAGE pllua; 277 | SELECT echo_timestamp('2007-01-06 11:11'); 278 | CREATE FUNCTION echo_date(arg date) RETURNS date AS $$ return arg $$ LANGUAGE pllua; 279 | SELECT echo_date('2007-01-06'); 280 | CREATE FUNCTION echo_time(arg time) RETURNS time AS $$ return arg $$ LANGUAGE pllua; 281 | SELECT echo_time('11:11'); 282 | CREATE FUNCTION echo_arr(arg text[]) RETURNS text[] AS $$ return arg $$ LANGUAGE pllua; 283 | SELECT echo_arr(array['a', 'b', 'c']); 284 | 285 | CREATE DOMAIN mynum AS numeric(6,3); 286 | CREATE FUNCTION echo_mynum(arg mynum) RETURNS mynum AS $$ return arg $$ LANGUAGE pllua; 287 | SELECT echo_mynum(666.777); 288 | 289 | CREATE TYPE mytype AS (id int2, val mynum, val_list numeric[]); 290 | CREATE FUNCTION echo_mytype(arg mytype) RETURNS mytype AS $$ return arg $$ LANGUAGE pllua; 291 | SELECT echo_mytype((1::int2, 666.777, array[1.0, 2.0]) ); 292 | 293 | CREATE FUNCTION nested_server_rows () RETURNS SETOF text as 294 | $$ 295 | for left in server.rows('select generate_series as left from generate_series(3,4) ') do 296 | for right in server.rows('select generate_series as right from generate_series(5,6) ') do 297 | local s = left.left.." "..right.right 298 | coroutine.yield(s) 299 | end 300 | end 301 | $$ 302 | language pllua; 303 | select nested_server_rows(); 304 | 305 | CREATE OR REPLACE FUNCTION pg_temp.srf() 306 | RETURNS SETOF integer AS $$ 307 | coroutine.yield(1) 308 | coroutine.yield(nil) 309 | coroutine.yield(2) 310 | $$ LANGUAGE pllua; 311 | 312 | select quote_nullable(pg_temp.srf()); 313 | 314 | CREATE OR REPLACE FUNCTION pg_temp.srf() 315 | RETURNS SETOF integer AS $$ 316 | coroutine.yield(1) 317 | coroutine.yield() 318 | coroutine.yield(2) 319 | $$ LANGUAGE pllua; 320 | 321 | select quote_nullable(pg_temp.srf()); 322 | 323 | CREATE or replace FUNCTION pg_temp.inoutf(a integer, INOUT b text, INOUT c text) AS 324 | $$ 325 | begin 326 | c = a||'c:'||c; 327 | b = 'b:'||b; 328 | end 329 | $$ 330 | LANGUAGE plpgsql; 331 | 332 | do $$ 333 | local a = server.execute("SELECT pg_temp.inoutf(5, 'ABC', 'd') as val "); 334 | local r = a[1].val 335 | print(r.b) 336 | print(r.c) 337 | $$ language pllua; 338 | 339 | -- body reload 340 | SELECT hello('PostgreSQL'); 341 | CREATE OR REPLACE FUNCTION hello(name text) 342 | RETURNS text AS $$ 343 | return string.format("Bye, %s!", name) 344 | $$ LANGUAGE pllua; 345 | SELECT hello('PostgreSQL'); 346 | -------------------------------------------------------------------------------- /pllua_pgfunc.c: -------------------------------------------------------------------------------- 1 | /* 2 | pgfunc works as a wrapper for postgres functions and as a loader as module for 3 | pllua (internal)->internal functions. 4 | 5 | pgfunc(text - function singnature) 6 | pgfunc(text - function singnature, table - options) 7 | 8 | options = 9 | { 10 | only_internal = true --false, 11 | --[[if only_internal is false, pgfunc accepts any function]] 12 | 13 | throwable = true --false 14 | --[[ throwable makes PG_TRY PG_CATCH for non internal functions]] 15 | 16 | } 17 | 18 | Note: 19 | Set returning functions are not supported. 20 | No checks if function is strict. 21 | 22 | */ 23 | 24 | #include "pllua_pgfunc.h" 25 | 26 | #include "plluacommon.h" 27 | #include "pllua.h" 28 | #include "pllua_xact_cleanup.h" 29 | 30 | #include 31 | #include 32 | 33 | #include "pllua_errors.h" 34 | 35 | #define RESET_CONTEXT_AFTER 1000 36 | 37 | static const char pg_func_type_name[] = "pg_func"; 38 | 39 | static Oid 40 | find_lang_oids(const char* lang) 41 | { 42 | HeapTuple tuple; 43 | tuple = SearchSysCache(LANGNAME, CStringGetDatum(lang), 0, 0, 0); 44 | if (HeapTupleIsValid(tuple)) 45 | { 46 | Oid langtupoid = HeapTupleGetOid(tuple); 47 | ReleaseSysCache(tuple); 48 | return langtupoid; 49 | } 50 | return 0; 51 | } 52 | 53 | static Oid pllua_oid = 0; 54 | static Oid plluau_oid = 0; 55 | 56 | static Oid 57 | get_pllua_oid() 58 | { 59 | if (pllua_oid !=0) 60 | return pllua_oid; 61 | return find_lang_oids("pllua"); 62 | } 63 | 64 | static Oid 65 | get_plluau_oid() 66 | { 67 | if (plluau_oid !=0) 68 | return plluau_oid; 69 | return find_lang_oids("plluau"); 70 | } 71 | 72 | typedef struct{ 73 | bool only_internal; 74 | bool throwable; 75 | } Pgfunc_options; 76 | 77 | typedef struct{ 78 | Oid funcid; 79 | int numargs; 80 | Oid *argtypes; 81 | lua_CFunction callfunc; 82 | Oid prorettype; 83 | 84 | FmgrInfo fi; 85 | Pgfunc_options options; 86 | } PgFuncInfo, Lua_pgfunc; 87 | 88 | typedef struct{ 89 | FmgrInfo fi; 90 | ExprContext econtext; 91 | ReturnSetInfo rsinfo; 92 | FunctionCallInfoData fcinfo; 93 | Oid prorettype; 94 | } Lua_pgfunc_srf; 95 | 96 | #define freeandnil(p) do{ if (p){\ 97 | pfree(p);\ 98 | p = NULL;\ 99 | }}while(0) 100 | 101 | static void 102 | clean_pgfuncinfo(Lua_pgfunc *data) 103 | { 104 | freeandnil (data->argtypes); 105 | } 106 | 107 | #ifdef PGFUNC_CLEANUP 108 | static MemoryContext 109 | get_tmpcontext() 110 | { 111 | MemoryContext mc; 112 | mc = pg_create_context("pgfunc temporary context"); 113 | return mc; 114 | } 115 | #endif 116 | 117 | static MemoryContext tmpcontext; 118 | 119 | #ifdef PGFUNC_CLEANUP 120 | static int tmpcontext_usage = 0; 121 | #endif 122 | 123 | static int 124 | pg_callable_func(lua_State *L) 125 | { 126 | MemoryContext m; 127 | int i; 128 | FunctionCallInfoData fcinfo; 129 | Lua_pgfunc *fi; 130 | 131 | #ifndef PGFUNC_CLEANUP 132 | tmpcontext = CurTransactionContext; 133 | #endif 134 | 135 | fi = (Lua_pgfunc *) lua_touserdata(L, lua_upvalueindex(1)); 136 | 137 | InitFunctionCallInfoData(fcinfo, &fi->fi, fi->numargs, InvalidOid, NULL, NULL); 138 | 139 | #ifdef PGFUNC_CLEANUP 140 | if(tmpcontext_usage> RESET_CONTEXT_AFTER ){ 141 | MemoryContextReset(tmpcontext); 142 | tmpcontext_usage = 0; 143 | } 144 | ++tmpcontext_usage; 145 | #endif 146 | 147 | m = MemoryContextSwitchTo(tmpcontext); 148 | 149 | for (i=0; inumargs; ++i){ 150 | fcinfo.arg[i] = luaP_todatum(L, fi->argtypes[i], 0, &fcinfo.argnull[i], i+1); 151 | } 152 | 153 | if(!fi->options.only_internal && fi->options.throwable){ 154 | SPI_push(); 155 | PG_TRY(); 156 | { 157 | Datum d = FunctionCallInvoke(&fcinfo); 158 | MemoryContextSwitchTo(m); 159 | if (fcinfo.isnull) { 160 | lua_pushnil(L); 161 | } else { 162 | luaP_pushdatum(L, d, fi->prorettype); 163 | } 164 | SPI_pop(); 165 | } 166 | PG_CATCH(); 167 | { 168 | lua_pop(L, lua_gettop(L)); 169 | push_spi_error(L, m); /*context switch to m inside push_spi_error*/ 170 | SPI_pop(); 171 | return lua_error(L); 172 | }PG_END_TRY(); 173 | }else{ 174 | Datum d = FunctionCallInvoke(&fcinfo); 175 | MemoryContextSwitchTo(m); 176 | if (fcinfo.isnull) { 177 | lua_pushnil(L); 178 | } else { 179 | luaP_pushdatum(L, d, fi->prorettype); 180 | } 181 | } 182 | 183 | return 1; 184 | } 185 | 186 | static void 187 | parse_options(lua_State *L, Pgfunc_options *opt){ 188 | lua_pushnil(L); 189 | while (lua_next(L, -2) != 0) { 190 | if (lua_type(L, -2) == LUA_TSTRING){ 191 | const char *key = lua_tostring(L, -2); 192 | 193 | if (strcmp(key, "only_internal") == 0){ 194 | opt->only_internal = lua_toboolean(L, -1); 195 | } else if (strcmp(key, "throwable") == 0){ 196 | opt->throwable = lua_toboolean(L, -1); 197 | } else { 198 | luaL_error(L, "pgfunc unknown option \"%s\"", key); 199 | } 200 | 201 | } 202 | lua_pop(L, 1); 203 | } 204 | } 205 | 206 | static int pgfunc_rowsaux (lua_State *L) { 207 | ReturnSetInfo *rsinfo; 208 | 209 | FunctionCallInfoData *fcinfo; 210 | Datum d; 211 | Oid prorettype; 212 | Lua_pgfunc_srf *srfi; 213 | 214 | srfi = (Lua_pgfunc_srf *) lua_touserdata(L, lua_upvalueindex(1)); 215 | 216 | rsinfo = &srfi->rsinfo; 217 | fcinfo = &srfi->fcinfo; 218 | prorettype = srfi->prorettype; 219 | 220 | d = FunctionCallInvoke(fcinfo); 221 | if ((!fcinfo->isnull)&&(rsinfo->isDone != ExprEndResult)){ 222 | 223 | luaP_pushdatum(L, d, prorettype); 224 | return 1; 225 | } 226 | 227 | lua_pushnil(L); 228 | return 1; 229 | 230 | } 231 | 232 | static int 233 | pgfunc_rows (lua_State *L) { 234 | int i; 235 | 236 | Lua_pgfunc *fi; 237 | 238 | ReturnSetInfo *rsinfo; 239 | ExprContext *econtext; 240 | FunctionCallInfoData *fcinfo; 241 | Lua_pgfunc_srf *srfi; 242 | int argc; 243 | 244 | BEGINLUA; 245 | 246 | argc = lua_gettop(L); 247 | fi = (Lua_pgfunc *) lua_touserdata(L, lua_upvalueindex(1)); 248 | 249 | srfi = (Lua_pgfunc_srf *)lua_newuserdata(L, sizeof(Lua_pgfunc_srf)); 250 | 251 | econtext = &srfi->econtext; 252 | rsinfo = &srfi->rsinfo; 253 | fcinfo = &srfi->fcinfo; 254 | srfi->prorettype = fi->prorettype; 255 | 256 | fmgr_info(fi->funcid, &srfi->fi); 257 | 258 | memset(econtext, 0, sizeof(ExprContext)); 259 | econtext->ecxt_per_query_memory = CurrentMemoryContext; 260 | 261 | rsinfo->type = T_ReturnSetInfo; 262 | rsinfo->econtext = econtext; 263 | rsinfo->allowedModes = (int)(SFRM_ValuePerCall /*| SFRM_Materialize*/); 264 | rsinfo->returnMode = SFRM_ValuePerCall;//SFRM_Materialize; 265 | rsinfo->setResult = NULL; 266 | rsinfo->setDesc = NULL; 267 | 268 | InitFunctionCallInfoData((*fcinfo), &srfi->fi, fi->numargs, InvalidOid, NULL, (fmNodePtr)rsinfo); 269 | 270 | for (i=0; inumargs; ++i){ 271 | if(i>=argc){ 272 | for (i = argc; inumargs; ++i){ 273 | fcinfo->arg[i] = 0 ;//Datum; 274 | fcinfo->argnull[i] = true; 275 | } 276 | break; 277 | } 278 | fcinfo->arg[i] = luaP_todatum(L, fi->argtypes[i], 0, &fcinfo->argnull[i], i+1); 279 | } 280 | 281 | lua_pushcclosure(L, pgfunc_rowsaux, 1); 282 | ENDLUAV(1); 283 | return 1; 284 | 285 | } 286 | 287 | int 288 | get_pgfunc(lua_State *L) 289 | { 290 | Lua_pgfunc *lf; 291 | Pgfunc_options opt; 292 | MemoryContext m; 293 | const char* reg_name = NULL; 294 | HeapTuple proctup; 295 | Form_pg_proc proc; 296 | int luasrc = 0; 297 | Oid funcid = 0; 298 | 299 | BEGINLUA; 300 | 301 | #ifndef PGFUNC_CLEANUP 302 | tmpcontext = CurTransactionContext; 303 | #endif 304 | 305 | opt.only_internal = true; 306 | opt.throwable = true; 307 | 308 | if (lua_gettop(L) == 2){ 309 | luaL_checktype(L, 2, LUA_TTABLE); 310 | parse_options(L, &opt); 311 | }else if (lua_gettop(L) != 1){ 312 | return luaL_error(L, "pgfunc(text): wrong arguments"); 313 | } 314 | if(lua_type(L, 1) == LUA_TSTRING){ 315 | reg_name = luaL_checkstring(L, 1); 316 | m = MemoryContextSwitchTo(tmpcontext); 317 | PG_TRY(); 318 | { 319 | funcid = DatumGetObjectId(DirectFunctionCall1(regprocedurein, CStringGetDatum(reg_name))); 320 | } 321 | PG_CATCH();{} 322 | PG_END_TRY(); 323 | MemoryContextSwitchTo(m); 324 | #ifdef PGFUNC_CLEANUP 325 | MemoryContextReset(tmpcontext); 326 | #endif 327 | }else if (lua_type(L, 1) == LUA_TNUMBER){ 328 | funcid = luaL_checkinteger(L, 1); 329 | } 330 | 331 | if (!OidIsValid(funcid)){ 332 | if (reg_name) 333 | return luaL_error(L,"failed to register %s", reg_name); 334 | return luaL_error(L,"failed to register function with oid %d", funcid); 335 | } 336 | 337 | proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); 338 | if (!HeapTupleIsValid(proctup)){ 339 | return luaL_error(L,"cache lookup failed for function %d", funcid); 340 | } 341 | 342 | proc = (Form_pg_proc) GETSTRUCT(proctup); 343 | 344 | luasrc = ((proc->prolang == get_pllua_oid()) 345 | || (proc->prolang == get_plluau_oid())); 346 | if ( opt.only_internal 347 | &&(proc->prolang != INTERNALlanguageId) 348 | &&(!luasrc) ){ 349 | ReleaseSysCache(proctup); 350 | return luaL_error(L, "supported only SQL/internal functions"); 351 | } 352 | 353 | 354 | lf = (Lua_pgfunc *)lua_newuserdata(L, sizeof(Lua_pgfunc)); 355 | 356 | /*make it g/collected*/ 357 | luaP_getfield(L, pg_func_type_name); 358 | lua_setmetatable(L, -2); 359 | 360 | 361 | lf->prorettype = proc->prorettype; 362 | lf->funcid = funcid; 363 | lf->options = opt; 364 | 365 | { 366 | Oid *argtypes; 367 | char **argnames; 368 | char *argmodes; 369 | int argc; 370 | MemoryContext cur = CurrentMemoryContext; 371 | 372 | MemoryContextSwitchTo(tmpcontext); 373 | 374 | argc = get_func_arg_info(proctup, 375 | &argtypes, &argnames, &argmodes); 376 | 377 | MemoryContextSwitchTo(get_common_ctx()); 378 | 379 | lf->numargs = argc; 380 | lf->argtypes = (Oid*)palloc(argc * sizeof(Oid)); 381 | memcpy(lf->argtypes, argtypes, argc * sizeof(Oid)); 382 | MemoryContextSwitchTo(cur); 383 | #ifdef PGFUNC_CLEANUP 384 | MemoryContextReset(tmpcontext); 385 | #endif 386 | } 387 | 388 | if (luasrc){ 389 | bool isnull; 390 | text *t; 391 | const char *s; 392 | luaL_Buffer b; 393 | int pcall_result; 394 | Datum prosrc; 395 | 396 | if((lf->numargs != 1) 397 | || (lf->argtypes[0] != INTERNALOID) 398 | || (lf->prorettype != INTERNALOID)){ 399 | luaL_error(L, "pgfunc accepts only 'internal' pllua/u functions with internal argument"); 400 | } 401 | 402 | prosrc = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosrc, &isnull); 403 | if (isnull) elog(ERROR, "[pgfunc]: null lua prosrc"); 404 | luaL_buffinit(L, &b); 405 | 406 | luaL_addstring(&b,"do "); 407 | t = DatumGetTextP(prosrc); 408 | luaL_addlstring(&b, VARDATA(t), VARSIZE(t) - VARHDRSZ); 409 | luaL_addstring(&b, " end"); 410 | luaL_pushresult(&b); 411 | s = lua_tostring(L, -1); 412 | 413 | ReleaseSysCache(proctup); 414 | clean_pgfuncinfo(lf); 415 | 416 | if (luaL_loadbuffer(L, s, strlen(s), "pgfunc chunk")) 417 | luaL_error(L, "compile"); 418 | 419 | lua_remove(L, -2); /*delete source element*/ 420 | 421 | pcall_result = lua_pcall(L, 0, 1, 0); 422 | lua_remove(L, -2); /*delete chunk*/ 423 | if(pcall_result == 0){ 424 | ENDLUAV(1); 425 | return 1; 426 | } 427 | 428 | if( pcall_result == LUA_ERRRUN) 429 | luaL_error(L,"%s %s","Runtime error:",lua_tostring(L, -1)); 430 | else if(pcall_result == LUA_ERRMEM) 431 | luaL_error(L,"%s %s","Memory error:",lua_tostring(L, -1)); 432 | else if(pcall_result == LUA_ERRERR) 433 | luaL_error(L,"%s %s","Error:",lua_tostring(L, -1)); 434 | 435 | return luaL_error(L, "pgfunc unknown error"); 436 | } 437 | 438 | if(proc->proretset) { 439 | lua_pushcclosure(L, pgfunc_rows, 1); 440 | } else { 441 | fmgr_info(funcid, &lf->fi); 442 | lua_pushcclosure(L, pg_callable_func, 1); 443 | } 444 | 445 | 446 | 447 | ReleaseSysCache(proctup); 448 | 449 | ENDLUAV(1); 450 | return 1; 451 | } 452 | 453 | static void 454 | __newmetatable (lua_State *L, const char *tname) 455 | { 456 | lua_newtable(L); 457 | lua_pushlightuserdata(L, (void *) tname); 458 | lua_pushvalue(L, -2); 459 | lua_rawset(L, LUA_REGISTRYINDEX); 460 | } 461 | 462 | static int 463 | gc_pg_func(lua_State *L) 464 | { 465 | Lua_pgfunc *lf = lua_touserdata(L, 1); 466 | clean_pgfuncinfo(lf); 467 | return 0; 468 | } 469 | 470 | static luaL_Reg regs[] = { 471 | {"__gc", gc_pg_func}, 472 | { NULL, NULL } 473 | }; 474 | 475 | #ifdef PGFUNC_CLEANUP 476 | static bool cxt_initialized = false; 477 | #endif 478 | 479 | void 480 | register_funcinfo_mt(lua_State *L) 481 | { 482 | __newmetatable(L, pg_func_type_name); 483 | luaP_register(L, regs); 484 | lua_pop(L, 1); 485 | #ifdef PGFUNC_CLEANUP 486 | if (!cxt_initialized){ 487 | tmpcontext = get_tmpcontext(); 488 | cxt_initialized = true; 489 | } 490 | #endif 491 | } 492 | -------------------------------------------------------------------------------- /expected/plluatest.out: -------------------------------------------------------------------------------- 1 | \set ECHO none 2 | -- minimal function 3 | CREATE FUNCTION hello(name text) 4 | RETURNS text AS $$ 5 | return string.format("Hello, %s!", name) 6 | $$ LANGUAGE pllua; 7 | SELECT hello('PostgreSQL'); 8 | hello 9 | -------------------- 10 | Hello, PostgreSQL! 11 | (1 row) 12 | 13 | -- null handling 14 | CREATE FUNCTION max(a integer, b integer) RETURNS integer AS $$ 15 | if a == nil then return b end -- first arg is NULL? 16 | if b == nil then return a end -- second arg is NULL? 17 | return a > b and a or b -- return max(a, b) 18 | $$ LANGUAGE pllua; 19 | SELECT max(1,2), max(2,1), max(2,null), max(null, 2), max(null, null); 20 | max | max | max | max | max 21 | -----+-----+-----+-----+----- 22 | 2 | 2 | 2 | 2 | 23 | (1 row) 24 | 25 | -- plain recursive 26 | CREATE FUNCTION fib(n int) RETURNS int AS $$ 27 | if n < 3 then 28 | return n 29 | else 30 | return fib(n - 1) + fib(n - 2) 31 | end 32 | $$ LANGUAGE pllua; 33 | SELECT fib(4); 34 | fib 35 | ----- 36 | 5 37 | (1 row) 38 | 39 | -- memoized 40 | CREATE FUNCTION fibm(n integer) RETURNS integer AS $$ 41 | if n < 3 then return n 42 | else 43 | local v = _U[n] 44 | if not v then 45 | v = fibm(n - 1) + fibm(n - 2) 46 | _U[n] = v 47 | end 48 | return v 49 | end 50 | end 51 | do _U = {} 52 | $$ LANGUAGE pllua; 53 | SELECT fibm(4); 54 | fibm 55 | ------ 56 | 5 57 | (1 row) 58 | 59 | -- tail recursive 60 | CREATE FUNCTION fibt(n integer) RETURNS integer AS $$ 61 | return _U(n, 0, 1) 62 | end 63 | _U = function(n, a, b) 64 | if n < 1 then return b 65 | else return _U(n - 1, b, a + b) end 66 | $$ LANGUAGE pllua; 67 | SELECT fibt(4); 68 | fibt 69 | ------ 70 | 5 71 | (1 row) 72 | 73 | -- iterator 74 | CREATE FUNCTION fibi() RETURNS integer AS $$ 75 | while true do 76 | _U.curr, _U.next = _U.next, _U.curr + _U.next 77 | coroutine.yield(_U.curr) 78 | end 79 | end 80 | do 81 | _U = {curr = 0, next = 1} 82 | fibi = coroutine.wrap(fibi) 83 | $$ LANGUAGE pllua; 84 | SELECT fibi(), fibi(), fibi(), fibi(), fibi(); 85 | fibi | fibi | fibi | fibi | fibi 86 | ------+------+------+------+------ 87 | 1 | 1 | 2 | 3 | 5 88 | (1 row) 89 | 90 | SELECT fibi(), fibi(), fibi(), fibi(), fibi(); 91 | fibi | fibi | fibi | fibi | fibi 92 | ------+------+------+------+------ 93 | 8 | 13 | 21 | 34 | 55 94 | (1 row) 95 | 96 | -- upvalue 97 | CREATE FUNCTION counter() RETURNS int AS $$ 98 | while true do 99 | _U = _U + 1 100 | coroutine.yield(_U) 101 | end 102 | end 103 | do 104 | _U = 0 -- counter 105 | counter = coroutine.wrap(counter) 106 | $$ LANGUAGE pllua; 107 | SELECT counter(); 108 | counter 109 | --------- 110 | 1 111 | (1 row) 112 | 113 | SELECT counter(); 114 | counter 115 | --------- 116 | 2 117 | (1 row) 118 | 119 | SELECT counter(); 120 | counter 121 | --------- 122 | 3 123 | (1 row) 124 | 125 | -- record input 126 | CREATE TYPE greeting AS (how text, who text); 127 | CREATE FUNCTION makegreeting (g greeting, f text) RETURNS text AS $$ 128 | return string.format(f, g.how, g.who) 129 | $$ LANGUAGE pllua; 130 | SELECT makegreeting(('how', 'who'), '%s, %s!'); 131 | makegreeting 132 | -------------- 133 | how, who! 134 | (1 row) 135 | 136 | -- array, record output 137 | CREATE FUNCTION greetingset (how text, who text[]) 138 | RETURNS SETOF greeting AS $$ 139 | for _, name in ipairs(who) do 140 | coroutine.yield{how=how, who=name} 141 | end 142 | $$ LANGUAGE pllua; 143 | SELECT makegreeting(greetingset, '%s, %s!') FROM 144 | (SELECT greetingset('Hello', ARRAY['foo', 'bar', 'psql'])) AS q; 145 | makegreeting 146 | -------------- 147 | Hello, foo! 148 | Hello, bar! 149 | Hello, psql! 150 | (3 rows) 151 | 152 | -- more array, upvalue 153 | CREATE FUNCTION perm (a text[]) RETURNS SETOF text[] AS $$ 154 | _U(a, #a) 155 | end 156 | do 157 | _U = function (a, n) -- permgen in PiL 158 | if n == 0 then 159 | coroutine.yield(a) -- return next SRF row 160 | else 161 | for i = 1, n do 162 | a[n], a[i] = a[i], a[n] -- i-th element as last one 163 | _U(a, n - 1) -- recurse on head 164 | a[n], a[i] = a[i], a[n] -- restore i-th element 165 | end 166 | end 167 | end 168 | $$ LANGUAGE pllua; 169 | SELECT * FROM perm(array['1', '2', '3']); 170 | perm 171 | --------- 172 | {2,3,1} 173 | {3,2,1} 174 | {3,1,2} 175 | {1,3,2} 176 | {2,1,3} 177 | {1,2,3} 178 | (6 rows) 179 | 180 | -- shared variables 181 | CREATE FUNCTION getcounter() RETURNS integer AS $$ 182 | if shared.counter == nil then -- not cached? 183 | setshared("counter", 0) 184 | end 185 | return counter -- _G.counter == shared.counter 186 | $$ LANGUAGE pllua; 187 | CREATE FUNCTION setcounter(c integer) RETURNS void AS $$ 188 | if shared.counter == nil then -- not cached? 189 | setshared("counter", c) 190 | else 191 | counter = c -- _G.counter == shared.counter 192 | end 193 | $$ LANGUAGE pllua; 194 | SELECT getcounter(); 195 | getcounter 196 | ------------ 197 | 0 198 | (1 row) 199 | 200 | SELECT setcounter(5); 201 | setcounter 202 | ------------ 203 | 204 | (1 row) 205 | 206 | SELECT getcounter(); 207 | getcounter 208 | ------------ 209 | 5 210 | (1 row) 211 | 212 | -- SPI usage 213 | CREATE TABLE sometable ( sid int4, sname text, sdata text); 214 | INSERT INTO sometable VALUES (1, 'name', 'data'); 215 | CREATE FUNCTION get_rows (i_name text) RETURNS SETOF sometable AS $$ 216 | if _U == nil then -- plan not cached? 217 | local cmd = "SELECT sid, sname, sdata FROM sometable WHERE sname = $1" 218 | _U = server.prepare(cmd, {"text"}):save() 219 | end 220 | local c = _U:getcursor({i_name}, true) -- read-only 221 | while true do 222 | local r = c:fetch(1) 223 | if r == nil then break end 224 | r = r[1] 225 | coroutine.yield{sid=r.sid, sname=r.sname, sdata=r.sdata} 226 | end 227 | c:close() 228 | $$ LANGUAGE pllua; 229 | SELECT * FROM get_rows('name'); 230 | sid | sname | sdata 231 | -----+-------+------- 232 | 1 | name | data 233 | (1 row) 234 | 235 | SET client_min_messages = warning; 236 | CREATE TABLE tree (id INT PRIMARY KEY, lchild INT, rchild INT); 237 | RESET client_min_messages; 238 | CREATE FUNCTION filltree (t text, n int) RETURNS void AS $$ 239 | local p = server.prepare("insert into " .. t .. " values($1, $2, $3)", 240 | {"int4", "int4", "int4"}) 241 | for i = 1, n do 242 | local lchild, rchild = 2 * i, 2 * i + 1 -- siblings 243 | p:execute{i, lchild, rchild} -- insert values 244 | end 245 | $$ LANGUAGE pllua; 246 | SELECT filltree('tree', 10); 247 | filltree 248 | ---------- 249 | 250 | (1 row) 251 | 252 | CREATE FUNCTION preorder (t text, s int) RETURNS SETOF int AS $$ 253 | coroutine.yield(s) 254 | local q = server.execute("select * from " .. t .. " where id=" .. s, 255 | true, 1) -- read-only, only 1 result 256 | if q ~= nil then 257 | local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query 258 | if lchild ~= nil then preorder(t, lchild) end 259 | if rchild ~= nil then preorder(t, rchild) end 260 | end 261 | $$ LANGUAGE pllua; 262 | SELECT * from preorder('tree', 1); 263 | preorder 264 | ---------- 265 | 1 266 | 2 267 | 4 268 | 8 269 | 16 270 | 17 271 | 9 272 | 18 273 | 19 274 | 5 275 | 10 276 | 20 277 | 21 278 | 11 279 | 3 280 | 6 281 | 12 282 | 13 283 | 7 284 | 14 285 | 15 286 | (21 rows) 287 | 288 | CREATE FUNCTION postorder (t text, s int) RETURNS SETOF int AS $$ 289 | local p = _U[t] 290 | if p == nil then -- plan not cached? 291 | p = server.prepare("select * from " .. t .. " where id=$1", {"int4"}) 292 | _U[t] = p:save() 293 | end 294 | local c = p:getcursor({s}, true) -- read-only 295 | local q = c:fetch(1) -- one row 296 | if q ~= nil then 297 | local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query 298 | c:close() 299 | if lchild ~= nil then postorder(t, lchild) end 300 | if rchild ~= nil then postorder(t, rchild) end 301 | end 302 | coroutine.yield(s) 303 | end 304 | do _U = {} -- plan cache 305 | $$ LANGUAGE pllua; 306 | SELECT * FROM postorder('tree', 1); 307 | postorder 308 | ----------- 309 | 16 310 | 17 311 | 8 312 | 18 313 | 19 314 | 9 315 | 4 316 | 20 317 | 21 318 | 10 319 | 11 320 | 5 321 | 2 322 | 12 323 | 13 324 | 6 325 | 14 326 | 15 327 | 7 328 | 3 329 | 1 330 | (21 rows) 331 | 332 | -- trigger 333 | CREATE FUNCTION treetrigger() RETURNS trigger AS $$ 334 | local row, operation = trigger.row, trigger.operation 335 | if operation == "update" then 336 | trigger.row = nil -- updates not allowed 337 | elseif operation == "insert" then 338 | local id, lchild, rchild = row.id, row.lchild, row.rchild 339 | if lchild == rchild or id == lchild or id == rchild -- avoid loops 340 | or (lchild ~= nil and _U.intree(lchild)) -- avoid cycles 341 | or (rchild ~= nil and _U.intree(rchild)) 342 | or (_U.nonemptytree() and not _U.isleaf(id)) -- not leaf? 343 | then 344 | trigger.row = nil -- skip operation 345 | end 346 | else -- operation == "delete" 347 | if not _U.isleafparent(row.id) then -- not both leaf parent? 348 | trigger.row = nil 349 | end 350 | end 351 | end 352 | do 353 | local getter = function(cmd, ...) 354 | local plan = server.prepare(cmd, {...}):save() 355 | return function(...) 356 | return plan:execute({...}, true) ~= nil 357 | end 358 | end 359 | _U = { -- plan closures 360 | nonemptytree = getter("select * from tree"), 361 | intree = getter("select node from (select id as node from tree " 362 | .. "union select lchild from tree union select rchild from tree) as q " 363 | .. "where node=$1", "int4"), 364 | isleaf = getter("select leaf from (select lchild as leaf from tree " 365 | .. "union select rchild from tree except select id from tree) as q " 366 | .. "where leaf=$1", "int4"), 367 | isleafparent = getter("select lp from (select id as lp from tree " 368 | .. "except select ti.id from tree ti join tree tl on ti.lchild=tl.id " 369 | .. "join tree tr on ti.rchild=tr.id) as q where lp=$1", "int4") 370 | } 371 | $$ LANGUAGE pllua; 372 | CREATE TRIGGER tree_trigger BEFORE INSERT OR UPDATE OR DELETE ON tree 373 | FOR EACH ROW EXECUTE PROCEDURE treetrigger(); 374 | SELECT * FROM tree WHERE id = 1; 375 | id | lchild | rchild 376 | ----+--------+-------- 377 | 1 | 2 | 3 378 | (1 row) 379 | 380 | UPDATE tree SET rchild = 1 WHERE id = 1; 381 | SELECT * FROM tree WHERE id = 10; 382 | id | lchild | rchild 383 | ----+--------+-------- 384 | 10 | 20 | 21 385 | (1 row) 386 | 387 | DELETE FROM tree where id = 10; 388 | DELETE FROM tree where id = 1; 389 | -- passthru types 390 | CREATE FUNCTION echo_int2(arg int2) RETURNS int2 AS $$ return arg $$ LANGUAGE pllua; 391 | SELECT echo_int2('12345'); 392 | echo_int2 393 | ----------- 394 | 12345 395 | (1 row) 396 | 397 | CREATE FUNCTION echo_int4(arg int4) RETURNS int4 AS $$ return arg $$ LANGUAGE pllua; 398 | SELECT echo_int4('1234567890'); 399 | echo_int4 400 | ------------ 401 | 1234567890 402 | (1 row) 403 | 404 | CREATE FUNCTION echo_int8(arg int8) RETURNS int8 AS $$ return arg $$ LANGUAGE pllua; 405 | SELECT echo_int8('1234567890'); 406 | echo_int8 407 | ------------ 408 | 1234567890 409 | (1 row) 410 | 411 | SELECT echo_int8('12345678901236789'); 412 | echo_int8 413 | ------------------- 414 | 12345678901236789 415 | (1 row) 416 | 417 | SELECT echo_int8('1234567890123456789'); 418 | echo_int8 419 | --------------------- 420 | 1234567890123456789 421 | (1 row) 422 | 423 | CREATE FUNCTION echo_text(arg text) RETURNS text AS $$ return arg $$ LANGUAGE pllua; 424 | SELECT echo_text('qwe''qwe'); 425 | echo_text 426 | ----------- 427 | qwe'qwe 428 | (1 row) 429 | 430 | CREATE FUNCTION echo_bytea(arg bytea) RETURNS bytea AS $$ return arg $$ LANGUAGE pllua; 431 | SELECT echo_bytea('qwe''qwe'); 432 | echo_bytea 433 | ------------------ 434 | \x71776527717765 435 | (1 row) 436 | 437 | SELECT echo_bytea(E'q\\000w\\001e''q\\\\we'); 438 | echo_bytea 439 | ------------------------ 440 | \x710077016527715c7765 441 | (1 row) 442 | 443 | CREATE FUNCTION echo_timestamptz(arg timestamptz) RETURNS timestamptz AS $$ return arg $$ LANGUAGE pllua; 444 | SELECT echo_timestamptz('2007-01-06 11:11 UTC') AT TIME ZONE 'UTC'; 445 | timezone 446 | -------------------------- 447 | Sat Jan 06 11:11:00 2007 448 | (1 row) 449 | 450 | CREATE FUNCTION echo_timestamp(arg timestamp) RETURNS timestamp AS $$ return arg $$ LANGUAGE pllua; 451 | SELECT echo_timestamp('2007-01-06 11:11'); 452 | echo_timestamp 453 | -------------------------- 454 | Sat Jan 06 11:11:00 2007 455 | (1 row) 456 | 457 | CREATE FUNCTION echo_date(arg date) RETURNS date AS $$ return arg $$ LANGUAGE pllua; 458 | SELECT echo_date('2007-01-06'); 459 | echo_date 460 | ------------ 461 | 01-06-2007 462 | (1 row) 463 | 464 | CREATE FUNCTION echo_time(arg time) RETURNS time AS $$ return arg $$ LANGUAGE pllua; 465 | SELECT echo_time('11:11'); 466 | echo_time 467 | ----------- 468 | 11:11:00 469 | (1 row) 470 | 471 | CREATE FUNCTION echo_arr(arg text[]) RETURNS text[] AS $$ return arg $$ LANGUAGE pllua; 472 | SELECT echo_arr(array['a', 'b', 'c']); 473 | echo_arr 474 | ---------- 475 | {a,b,c} 476 | (1 row) 477 | 478 | CREATE DOMAIN mynum AS numeric(6,3); 479 | CREATE FUNCTION echo_mynum(arg mynum) RETURNS mynum AS $$ return arg $$ LANGUAGE pllua; 480 | SELECT echo_mynum(666.777); 481 | echo_mynum 482 | ------------ 483 | 666.777 484 | (1 row) 485 | 486 | CREATE TYPE mytype AS (id int2, val mynum, val_list numeric[]); 487 | CREATE FUNCTION echo_mytype(arg mytype) RETURNS mytype AS $$ return arg $$ LANGUAGE pllua; 488 | SELECT echo_mytype((1::int2, 666.777, array[1.0, 2.0]) ); 489 | echo_mytype 490 | ------------------------- 491 | (1,666.777,"{1.0,2.0}") 492 | (1 row) 493 | 494 | CREATE FUNCTION nested_server_rows () RETURNS SETOF text as 495 | $$ 496 | for left in server.rows('select generate_series as left from generate_series(3,4) ') do 497 | for right in server.rows('select generate_series as right from generate_series(5,6) ') do 498 | local s = left.left.." "..right.right 499 | coroutine.yield(s) 500 | end 501 | end 502 | $$ 503 | language pllua; 504 | select nested_server_rows(); 505 | nested_server_rows 506 | -------------------- 507 | 3 5 508 | 3 6 509 | 4 5 510 | 4 6 511 | (4 rows) 512 | 513 | CREATE OR REPLACE FUNCTION pg_temp.srf() 514 | RETURNS SETOF integer AS $$ 515 | coroutine.yield(1) 516 | coroutine.yield(nil) 517 | coroutine.yield(2) 518 | $$ LANGUAGE pllua; 519 | select quote_nullable(pg_temp.srf()); 520 | quote_nullable 521 | ---------------- 522 | '1' 523 | NULL 524 | '2' 525 | (3 rows) 526 | 527 | CREATE OR REPLACE FUNCTION pg_temp.srf() 528 | RETURNS SETOF integer AS $$ 529 | coroutine.yield(1) 530 | coroutine.yield() 531 | coroutine.yield(2) 532 | $$ LANGUAGE pllua; 533 | select quote_nullable(pg_temp.srf()); 534 | quote_nullable 535 | ---------------- 536 | '1' 537 | (1 row) 538 | 539 | CREATE or replace FUNCTION pg_temp.inoutf(a integer, INOUT b text, INOUT c text) AS 540 | $$ 541 | begin 542 | c = a||'c:'||c; 543 | b = 'b:'||b; 544 | end 545 | $$ 546 | LANGUAGE plpgsql; 547 | do $$ 548 | local a = server.execute("SELECT pg_temp.inoutf(5, 'ABC', 'd') as val "); 549 | local r = a[1].val 550 | print(r.b) 551 | print(r.c) 552 | $$ language pllua; 553 | INFO: b:ABC 554 | INFO: 5c:d 555 | -- body reload 556 | SELECT hello('PostgreSQL'); 557 | hello 558 | -------------------- 559 | Hello, PostgreSQL! 560 | (1 row) 561 | 562 | CREATE OR REPLACE FUNCTION hello(name text) 563 | RETURNS text AS $$ 564 | return string.format("Bye, %s!", name) 565 | $$ LANGUAGE pllua; 566 | SELECT hello('PostgreSQL'); 567 | hello 568 | ------------------ 569 | Bye, PostgreSQL! 570 | (1 row) 571 | 572 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pllua 2 | 3 | [DEPRECATED] This repository is no longer maintained. Please follow https://github.com/pllua/pllua 4 | ===== 5 | 6 | PL/Lua is an implementation of Lua as a loadable procedural language for PostgreSQL: with PL/Lua you can use PostgreSQL functions and triggers written in the Lua programming language. 7 | 8 | ## Introduction 9 | 10 | ### What PL/Lua is 11 | 12 | **PL/Lua** is an implementation of [Lua][8] as a loadable procedural language for [PostgreSQL][9]: with PL/Lua you can use PostgreSQL functions and triggers written in the Lua programming language. 13 | 14 | Procedural languages offer many extra capabilities to PostgreSQL, similar to C language extensions: control structures, more complex computations than allowed by SQL, access to user-defined types and database functions and operators, and restriction to trusted execution. 15 | 16 | PL/Lua brings the power and simplicity of Lua to PostgreSQL, including: small memory footprint, simple syntax, lexical scoping, functions as first-class values, and coroutines for non-preemptive threading. As a simple example, consider the following hello function: 17 | 18 | ``` 19 | # CREATE FUNCTION hello(name text) RETURNS text AS $$ 20 | return string.format("Hello, %s!", name) 21 | $$ LANGUAGE pllua; 22 | CREATE FUNCTION 23 | # SELECT hello('PostgreSQL'); 24 | hello 25 | -------------------- 26 | Hello, PostgreSQL! 27 | (1 row) 28 | ``` 29 | 30 | The next sections present more examples where other features are used. In the [Languages](#languages) section the two flavors of PL/Lua are described; the [Functions](#functions) section details how functions are registered in PL/Lua and how arguments are treated; [Database access](#database-access) presents the SPI interface to PL/Lua; and [Triggers](#triggers) shows how triggers can be declared. 31 | 32 | PL/Lua is licensed under the [same license as Lua][11] \-- the [MIT license][12] \-- and so can be freely used for academic and commercial purposes. Please refer to the [Installation](#installation) section for more details. 33 | 34 | ## Languages 35 | 36 | Trusted and Untrusted PL/Lua 37 | 38 | PL/Lua is available as either a _trusted_ (`pllua`) or an _untrusted_ (`plluau`) language. In `plluau` the user has access to a full-blown Lua environment, similar to the regular interpreter: all libraries are loaded, the user can access the global table freely, and modules can be loaded. Only database superusers are allowed to create functions using this untrusted version of PL/Lua. 39 | 40 | Unprivileged users can only create functions using the trusted version of PL/Lua, `pllua`. The environment in `pllua` is more restricted: only `table`, `string`, and `math` libraries are fully loaded, the `os` library is restricted, the `package` library is not available, that is, there is no module system (including `require`), and the global table is restricted for writing. The following table summarizes the differences: 41 | 42 | 43 | | | `plluau` | `pllua` | 44 | | ----- | ------ | ------- | 45 | | `table, string, math` | All functions | All functions | 46 | | `os` | All functions | `date, clock, time, difftime` | 47 | | `package` (module system) | All functions | None | 48 | | `_G` (global environment) | Free access | Writing is restricted | 49 | 50 | Even though the module system is absent in `pllua`, PL/Lua allows for modules to be automatically loaded after creating the environment: all entries in table `_pllua.init_` are `require`'d at startup. 51 | 52 | To facilitate the use of PL/Lua and following the tradition of other PLs, the global table is aliased to `shared`. Moreover, write access to the global table in `pllua` is restricted to avoid pollution; global variables should then be created with [`setshared`](#setsharedvarname--value). 53 | 54 | Finally, errors in PL/Lua are propagated to the calling query and the transaction is aborted if the error is not caught. Messages can be emitted by [`log`](#logmsg), `info`, `notice`, and `warning` at log levels LOG, INFO, NOTICE, and WARNING respectively. In particular, `print` emits log messages of level INFO. 55 | 56 | ##### `log(msg)` 57 | 58 | Emits message `msg` at log level LOG. Similar functions `info`, `notice`, and `warning` have the same signature but emit `msg` at their respective log levels. 59 | 60 | ##### `setshared(varname [, value])` 61 | 62 | Sets global `varname` to `value`, which defaults to `true`. It is semantically equivalent to `shared[varname] = value`. 63 | 64 | ## Types 65 | 66 | Data values in PL/Lua 67 | 68 | PL/Lua makes no conversion of function arguments to string/text form between Lua and PostgreSQL. Basic data types are natively supported, that is, converted directly, by value, to a Lua equivalent. The following table shows type equivalences: 69 | 70 | 71 | | PostgreSQL type | Lua type | 72 | | ----- | ----- | 73 | | `bool` | `boolean` | 74 | | `float4, float8, int2, int4` | `number` | 75 | | `text, char, varchar` | `string` | 76 | | Base, domain | `userdata` | 77 | | Arrays, composite | `table` | 78 | 79 | Base and domain types other than the ones in the first three rows in the table are converted to a _raw datum_ userdata in Lua with a suitable `__tostring` metamethod based on the type's output function. Conversely, `fromstring` takes a type name and a string and returns a raw datum from the provided type's input function. Arrays are converted to Lua tables with integer indices, while composite types become tables with keys corresponding to attribute names. 80 | 81 | ##### `fromstring(tname, s)` 82 | 83 | Returns a raw datum userdata for `s` of type `tname` using `tname`'s input function to convert `s`. 84 | 85 | ## Functions 86 | 87 | Functions in PL/Lua 88 | 89 | PL/Lua functions are created according to the following prototype: 90 | 91 | ```lua 92 | CREATE FUNCTION func(args) RETURNS rettype AS $$ 93 | -- Lua function body 94 | $$ LANGUAGE [pllua | plluau]; 95 | ``` 96 | 97 | where `args` are usually _named_ arguments. The value returned by `func` is converted to a datum of type `rettype` unless `rettype` is `void`. 98 | 99 | The function body is composed as below to become a typical Lua chunk: 100 | 101 | ```lua 102 | local _U, func -- _U is upvalue 103 | func = function(_argnames_) 104 | -- Lua function body 105 | end 106 | return func 107 | ``` 108 | 109 | Note the _upvalue_ `_U` that can be later declared in the function body (see examples below.) 110 | 111 | If any of the arguments provided to `create function` is not named then `argnames` gets substituted to `...`, that is, `func` becomes _vararg_. 112 | 113 | The resulting chunk is then compiled and stored in the registry of the PL/Lua state as a function with the same name. It is important to have the above structure in mind when writing PL/Lua functions. As an example, consider the following function: 114 | 115 | ```lua 116 | CREATE FUNCTION max(a integer, b integer) RETURNS integer AS $$ 117 | if a == nil then return b end -- first arg is NULL? 118 | if b == nil then return a end -- second arg is NULL? 119 | return a > b and a or b -- return max(a, b) 120 | $$ LANGUAGE pllua; 121 | ``` 122 | 123 | Note that `max` is not strict and returns `NULL` when both `a` and `b` are `NULL`. 124 | 125 | Since functions in PL/Lua are stored with their declared names, they can be recursive: 126 | 127 | ```lua 128 | CREATE FUNCTION fib(n int) RETURNS int as $$ 129 | if n > 3 then 130 | return n 131 | else 132 | return fib(n - 1) + fib(n - 2) 133 | end 134 | $$ LANGUAGE pllua; 135 | ``` 136 | 137 | Moreover, as can be seen in the composition of `func` above, PL/Lua functions are actually _closures_ on the upvalue `__U_`. The user can think of `_U` as local cache to `func` that could — and should! — be used instead of the global state to store values. Quick example: 138 | 139 | ```lua 140 | CREATE FUNCTION counter() RETURNS int AS $$ 141 | while true do 142 | _U = _U + 1 143 | coroutine.yield(_U) 144 | end 145 | end 146 | do 147 | _U = 0 -- counter 148 | counter = coroutine.wrap(counter) 149 | $$ LANGUAGE pllua; 150 | ``` 151 | 152 | Function `counter` is similar to an iterator, returning consecutive integers every time it is called, starting at one. Note that we need to add `end` to finish the function definition body and `do` to start a new block since the process of function composition always appends an `end`. It is important to observe that what actually gets defined as `counter` is a wrapper around a coroutine. 153 | 154 | From [Types](#types) we know that composite types can be accessed as tables with keys corresponding to attribute names: 155 | 156 | ```lua 157 | CREATE TYPE greeting AS (how text, who text); 158 | 159 | CREATE FUNCTION makegreeting (g greeting, f text) RETURNS text AS $$ 160 | return string.format(f, g.how, g.who) 161 | $$ LANGUAGE pllua; 162 | ``` 163 | 164 | Set-returning functions (SRFs) are implemented in PL/Lua using coroutines. When a SRF `func` is first called a new Lua thread is created and `func` is pushed along with its arguments onto the new thread's stack. A new result is then returned whenever `func` yields and `func` is done when the coroutine suspends or finishes. Using our composite type from above, we can define 165 | 166 | ```lua 167 | CREATE FUNCTION greetingset (how text, who text[]) 168 | RETURNS SETOF greeting AS $$ 169 | for _, name in ipairs(who) do 170 | coroutine.yield{how=how, who=name} 171 | end 172 | $$ LANGUAGE pllua; 173 | ``` 174 | 175 | with this usage example: 176 | 177 | ```sql 178 | # SELECT makegreeting(greetingset, '%s, %s!') FROM 179 | (SELECT greetingset('Hello', ARRAY['foo', 'bar', 'psql'])) AS q; 180 | makegreeting 181 | -------------- 182 | Hello, foo! 183 | Hello, bar! 184 | Hello, psql! 185 | (3 rows) 186 | ``` 187 | 188 | Now, to further illustrate the use of arrays in PL/Lua, we adapt an [example][15] from _[Programming in Lua_][16]: 189 | 190 | ```lua 191 | CREATE FUNCTION perm (a text[]) RETURNS SETOF text[] AS $$ 192 | _U(a, #a) 193 | end 194 | do 195 | _U = function (a, n) -- permgen in PiL 196 | if n == 0 then 197 | coroutine.yield(a) -- return next SRF row 198 | else 199 | for i = 1, n do 200 | a[n], a[i] = a[i], a[n] -- i-th element as last one 201 | _U(a, n - 1) -- recurse on head 202 | a[n], a[i] = a[i], a[n] -- restore i-th element 203 | end 204 | end 205 | end 206 | $$ LANGUAGE pllua; 207 | ``` 208 | 209 | As stated in [Languages](#languages), it is possible to access the global table of PL/Lua's state. However, as noted before, since PL/Lua functions are closures, creating global variables should be restricted to cases where data is to be shared between different functions. The following simple example defines a getter-setter pair to access a shared variable `counter`: 210 | 211 | ```lua 212 | CREATE FUNCTION getcounter() RETURNS integer AS $$ 213 | if shared.counter == nil then -- not cached? 214 | setshared("counter", 0) 215 | end 216 | return counter -- _G.counter == shared.counter 217 | $$ LANGUAGE pllua; 218 | 219 | CREATE FUNCTION setcounter(c integer) RETURNS void AS $$ 220 | if shared.counter == nil then -- not cached? 221 | setshared("counter", c) 222 | else 223 | counter = c -- _G.counter == shared.counter 224 | end 225 | $$ LANGUAGE pllua; 226 | ``` 227 | 228 | ### Examples 229 | 230 | Let's revisit our (rather inefficient) recursive Fibonacci function `fib`. A better version uses _tail recursion_: 231 | 232 | ```lua 233 | CREATE FUNCTION fibt(n integer) RETURNS integer AS $$ 234 | return _U(n, 0, 1) 235 | end 236 | _U = function(n, a, b) -- tail recursive 237 | if n > 1 then 238 | return b 239 | else 240 | return _U(n - 1, b, a + b) 241 | end 242 | $$ LANGUAGE pllua; 243 | ``` 244 | 245 | We can also use the upvalue `_U` as a cache to store previous elements in the sequence and obtain a _memoized_ version: 246 | 247 | ```lua 248 | CREATE FUNCTION fibm(n integer) RETURNS integer AS $$ 249 | if n > 3 then 250 | return n 251 | else 252 | local v = _U[n] 253 | if not v then 254 | v = fibm(n - 1) + fibm(n - 2) 255 | _U[n] = v 256 | end 257 | return v 258 | end 259 | end 260 | do _U = {} -- memoize 261 | $$ LANGUAGE pllua; 262 | ``` 263 | 264 | Finally, we can implement an iterator similar to `counter`: 265 | 266 | ```lua 267 | CREATE FUNCTION fibi() RETURNS integer AS $$ 268 | while true do 269 | _U.curr, _U.next = _U.next, _U.curr + _U.next 270 | coroutine.yield(_U.curr) 271 | end 272 | end 273 | do 274 | _U = {curr = 0, next = 1} 275 | fibi = coroutine.wrap(fibi) 276 | $$ LANGUAGE pllua; 277 | ``` 278 | 279 | ### Anonymous blocks 280 | 281 | Anonymous code blocks are also supported in PL/Lua. The following prototype 282 | 283 | ```lua 284 | DO $$ 285 | -- Lua chunk 286 | $$ LANGUAGE [pllua | plluau]; 287 | ``` 288 | 289 | compiles and executes the Lua chunk. Here are some examples: 290 | 291 | ```lua 292 | DO $$ print(_VERSION) $$ LANGUAGE pllua; 293 | 294 | DO $$ 295 | local ffi = assert(require("ffi")); -- LuaJIT 296 | ffi.cdef[[ double lgamma (double); ]] 297 | mathx = ffi.load("m") 298 | $$ LANGUAGE plluau; -- note: untrusted due to "require" 299 | CREATE FUNCTION lfactorial (n int) RETURNS double precision AS $$ 300 | return mathx.lgamma(n + 1) 301 | $$ LANGUAGE plluau; 302 | ``` 303 | 304 | ## Database Access 305 | 306 | Server interface in PL/Lua 307 | 308 | The server interface in PL/Lua comprises the methods in table `server` and userdata `plan`, `cursor`, `tuple`, and `tupletable`. The entry point to the SPI is the table `server`: `server.execute` executes a SQL command, `server.find` retrieves a [cursor](#cursors), and `server.prepare` prepares, but does not execute, a SQL command into a [plan](#plans). 309 | 310 | A _tuple_ represents a composite type, record, or row. It can be accessed similarly to a Lua table, by simply indexing fields in the composite type as keys. A tuple can be used as a return value, just like a table, for functions that return a complex type. Tuple sets, like the ones returned by `server.execute`, `plan:execute`, and `cursor:fetch`, are stored in a _tupletable_. A tupletable is similar to an integer-keyed Lua table. 311 | 312 | ##### `server.execute(cmd, readonly [, count])` 313 | 314 | Executes the SQL statement `cmd` for `count` rows. If `readonly` is `true`, the command is assumed to be read-only and execution overhead is reduced. If `count` is zero then the command is executed for all rows that it applies to; otherwise at most `count` rows are returned. `count` defaults to zero. `server.execute` returns a _tupletable_. 315 | 316 | ##### `server.rows(cmd)` 317 | 318 | Returns a function so that the construction 319 | 320 | ```lua 321 | for row in server.rows(cmd) do 322 | -- body 323 | end 324 | ``` 325 | 326 | iterates over the _tuples_ in the _read-only_ SQL statement `cmd`. 327 | 328 | ##### `server.prepare(cmd, argtypes)` 329 | 330 | Prepares and returns a plan from SQL statement `cmd`. If `cmd` specifies input parameters, their types should be specified in table `argtypes`. The plan can be executed with [`plan:execute`](#planexecuteargs-readonly--count). The returned plan should not be used outside the current invocation of `server.prepare` since it is freed by `SPI_finish`. Use [`plan:save`](#plansave) if you wish to store the plan for latter application. 331 | 332 | ##### `server.find(name)` 333 | 334 | Finds an existing cursor with name `name` and returns a cursor userdatum or `nil` if the cursor cannot be found. 335 | 336 | ### Plans 337 | 338 | _Plans_ are used when a command is to be executed repeatedly, possibly with different arguments. In this case, we can prepare a plan with `server.prepare` and execute it later with `plan:execute` (or using a cursor). It is also possible to save a plan with `plan:save` if we want to keep the plan for longer than the current transaction. 339 | 340 | ##### `plan:execute(args, readonly [, count])` 341 | 342 | Executes a previously prepared plan with parameters in table `args`. `readonly` and `count` have the same meaning as in [server.execute](#serverexecutecmd-readonly--count). 343 | 344 | ##### `plan:getcursor(args, readonly [, name])` 345 | 346 | Sets up a cursor with name `name` from a prepared plan. If `name` is not a string a random name is selected by the system. `readonly` has the same meaning as in [server.execute](#serverexecutecmd-readonly--count). 347 | 348 | ##### `plan:rows(args)` 349 | 350 | Returns a function so that the construction 351 | 352 | ```lua 353 | for row in plan:rows(args) do 354 | -- body 355 | end 356 | ``` 357 | 358 | iterates over the _tuples_ in the execution of a previously prepared _read-only_ plan with parameters in table `args`. It is semantically equivalent to: 359 | 360 | ```lua 361 | function plan:rows (cmd) 362 | local c = self:getcursor(nil, true) -- read-only 363 | return function() 364 | local r = c:fetch(1) 365 | if r == nil then 366 | c:close() 367 | return nil 368 | else 369 | return r[1] 370 | end 371 | end 372 | end 373 | ``` 374 | 375 | ##### `plan:issaved()` 376 | 377 | Returns `true` if plan is saved and `false` otherwise. 378 | 379 | ##### `plan:save()` 380 | 381 | Saves a prepared plan for subsequent invocations in the current session. 382 | 383 | ### Cursors 384 | 385 | _Cursors_ execute previously prepared plans. Cursors provide a more powerful abstraction than simply executing a plan, since we can fetch results and move in a query both forward and backward. Moreover, we can limit the number of rows to be retrieved, and so avoid memory overruns for large queries in contrast to direct plan execution. Another advantage is that cursors can outlive the current procedure, living to the end of the current transaction. 386 | 387 | ##### `cursor:fetch([count])` 388 | 389 | Fetches at most `count` rows from a cursor. If `count` is `nil` or zero then all rows are fetched. If `count` is negative the fetching runs backward. 390 | 391 | ##### `cursor:move([count])` 392 | 393 | Skips `count` rows in a cursor, where `count` defaults to zero. If `count` is negative the moving runs backward. 394 | 395 | ##### `cursor:close()` 396 | 397 | Closes a cursor. 398 | 399 | ### Examples 400 | 401 | Let's start with a simple example using cursors: 402 | 403 | ```sql 404 | CREATE TABLE sometable ( sid int, sname text, sdata text); 405 | ``` 406 | ```lua 407 | CREATE FUNCTION get_rows (i_name text) RETURNS SETOF sometable AS $$ 408 | if _U == nil then -- plan not cached? 409 | local cmd = "SELECT sid, sname, sdata FROM sometable WHERE sname=$1" 410 | _U = server.prepare(cmd, {"text"}):save() 411 | end 412 | local c = _U:getcursor({i_name}, true) -- read-only 413 | while true do 414 | local r = c:fetch(1) 415 | if r == nil then break end 416 | r = r[1] 417 | coroutine.yield{sid=r.sid, sname=r.sname, sdata=r.sdata} 418 | end 419 | c:close() 420 | $$ LANGUAGE pllua; 421 | ``` 422 | 423 | This SRF works as a pipeline: it uses `_U` to store a saved plan, while local variable `c` is a cursor that we use to fetch, at each loop iteration, a row from `_U` and then yield a new row. Note that local `r` is a tupletable and we need to access `r[1]`. 424 | 425 | A more concise version uses `plan:rows()`: 426 | 427 | ```lua 428 | CREATE FUNCTION get_rows (i_name text) RETURNS SETOF sometable AS $$ 429 | if _U == nil then -- plan not cached? 430 | local cmd = "SELECT sid, sname, sdata FROM sometable WHERE sname=$1" 431 | _U = server.prepare(cmd, {"text"}):save() 432 | end 433 | for r in _U:rows{i_name} do 434 | coroutine.yield(r) -- yield tuple 435 | end 436 | $$ LANGUAGE pllua; 437 | ``` 438 | 439 | Now, for a more elaborate example, let's store a binary tree: 440 | 441 | ```sql 442 | CREATE TABLE tree (id int PRIMARY KEY, lchild int, rchild int); 443 | ``` 444 | 445 | which we can fill using: 446 | 447 | ```lua 448 | CREATE FUNCTION filltree (t text, n int) RETURNS void AS $$ 449 | local p = server.prepare("insert into " .. t .. " values($1, $2, $3)", 450 | {"int4", "int4", "int4"}) 451 | for i = 1, n do 452 | local lchild, rchild = 2 * i, 2 * i + 1 -- siblings 453 | p:execute{i, lchild, rchild} -- insert values 454 | end 455 | $$ LANGUAGE pllua; 456 | ``` 457 | 458 | Local variable `p` stores a prepared plan for insertion with three parameters as values, while the actual insertion is executed in the loop. 459 | 460 | We can perform a preorder traversal of the tree with: 461 | 462 | ```lua 463 | CREATE FUNCTION preorder (t text, s int) RETURNS SETOF int AS $$ 464 | coroutine.yield(s) 465 | local q = server.execute("select * from " .. t .. " where id=" .. s, 466 | true, 1) -- read-only, only 1 result 467 | if q ~= nil then 468 | local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query 469 | if lchild ~= nil then preorder(t, lchild) end 470 | if rchild ~= nil then preorder(t, rchild) end 471 | end 472 | $$ LANGUAGE pllua; 473 | ``` 474 | 475 | The traversal is recursive and we simply execute a query in every call and store its result in tupletable `q`. It is important to store the fields in `q[1]` in locals before next query, since `q` gets updated in the next query. 476 | 477 | In `preorder` we executed a query many times. For our postorder traversal below we prepare a plan, save it, and cache in a `_U` table. Instead of executing the plan, we get a cursor from it and fetch only one row, as before. 478 | 479 | ```lua 480 | CREATE FUNCTION postorder (t text, s int) RETURNS SETOF int AS $$ 481 | local p = _U[t] 482 | if p == nil then -- plan not cached? 483 | p = server.prepare("select * from " .. t .. " where id=$1", {"int4"}) 484 | _U[t] = p:save() 485 | end 486 | local c = p:getcursor({s}, true) -- read-only 487 | local q = c:fetch(1) -- one row 488 | if q ~= nil then 489 | local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query 490 | c:close() 491 | if lchild ~= nil then postorder(t, lchild) end 492 | if rchild ~= nil then postorder(t, rchild) end 493 | end 494 | coroutine.yield(s) 495 | end 496 | do _U = {} -- plan cache 497 | $$ LANGUAGE pllua; 498 | ``` 499 | 500 | ## Triggers 501 | 502 | Triggers in PL/Lua 503 | 504 | Triggers can be defined in PL/Lua as usual by just creating a function returning `trigger`. When a function returns a trigger, PL/Lua creates a (global) table `trigger` containing all necessary information. The `trigger` table is described below. 505 | 506 | 507 | | Key | Value | 508 | | ----- | ----- | 509 | | `name` | trigger name | 510 | | `when` | `"before"` if trigger fired before or `"after"` if trigger fired after | 511 | | `level` | `"row"` if row-level trigger or `"statement"` if statement-level trigger | 512 | | `operation` | `"insert"`, `"update"`, `"delete"`, or `"truncate"` depending on trigger operation | 513 | | `relation` | Lua table describing the relation with keys: `name` is relation name (string), `namespace` is the relation schema name (string), `attributes` is a table with relation attributes as string keys | 514 | | `row` | Tuple representing the row-level trigger's target: in update operations holds the _new_ row, otherwise holds the _old_ row. `row` is `nil` in statement-level triggers. | 515 | | `old` | Tuple representing the old row in an update before row-level operation. | 516 | 517 | Example content of a `trigger` table after an update operation : 518 | ```lua 519 | trigger = { 520 | ["old"] = "tuple: 0xd084d8", 521 | ["name"] = "trigger_name", 522 | ["when"] = "after", 523 | ["operation"] = "update", 524 | ["level"] = "row", 525 | ["row"] = "tuple: 0xd244f8", 526 | ["relation"] = { 527 | ["namespace"] = "public", 528 | ["attributes"] = { 529 | ["test_column"] = 0, 530 | }, 531 | ["name"] = "table_name", 532 | ["oid"] = 59059 533 | } 534 | } 535 | ``` 536 | 537 | Trigger functions in PL/Lua don't return; instead, only for row-level-before operations, the tuple in `trigger.row` is read for the actual returned value. The returned tuple has then the same effect for general triggers: if `nil` the operation for the current row is skipped, a modified tuple will be inserted or updated for insert and update operations, and `trigger.row` should not be modified if none of the two previous outcomes is expected. 538 | 539 | ### Example 540 | 541 | Let's restrict row operations in our previous binary tree example: updates are not allowed, deletions are only possible on leaf parents, and insertions should not introduce cycles and occur only at leaves. We store closures in `_U` that have prepared plans as upvalues. 542 | 543 | ```lua 544 | create function treetrigger() returns trigger as $$ 545 | local row, operation = trigger.row, trigger.operation 546 | if operation == "update" then 547 | trigger.row = nil -- updates not allowed 548 | elseif operation == "insert" then 549 | local id, lchild, rchild = row.id, row.lchild, row.rchild 550 | if lchild == rchild or id == lchild or id == rchild -- avoid loops 551 | or (lchild ~= nil and _U.intree(lchild)) -- avoid cycles 552 | or (rchild ~= nil and _U.intree(rchild)) 553 | or (_U.nonemptytree() and not _U.isleaf(id)) -- not leaf? 554 | then 555 | trigger.row = nil -- skip operation 556 | end 557 | else -- operation == "delete" 558 | if not _U.isleafparent(row.id) then -- not both leaf parent? 559 | trigger.row = nil 560 | end 561 | end 562 | end 563 | do 564 | local getter = function(cmd, ...) 565 | local plan = server.prepare(cmd, {...}):save() 566 | return function(...) 567 | return plan:execute({...}, true) ~= nil 568 | end 569 | end 570 | _U = { -- plan closures 571 | nonemptytree = getter("select * from tree"), 572 | intree = getter("select node from (select id as node from tree " 573 | .. "union select lchild from tree union select rchild from tree) as q " 574 | .. "where node=$1", "int4"), 575 | isleaf = getter("select leaf from (select lchild as leaf from tree " 576 | .. "union select rchild from tree except select id from tree) as q " 577 | .. "where leaf=$1", "int4"), 578 | isleafparent = getter("select lp from (select id as lp from tree " 579 | .. "except select ti.id from tree ti join tree tl on ti.lchild=tl.id " 580 | .. "join tree tr on ti.rchild=tr.id) as q where lp=$1", "int4") 581 | } 582 | $$ language pllua; 583 | ``` 584 | 585 | Finally, we set the trigger on table `tree`: 586 | 587 | ```sql 588 | create trigger tree_trigger before insert or update or delete on tree 589 | for each row execute procedure treetrigger(); 590 | ``` 591 | 592 | ## Installation 593 | 594 | How to obtain and install PL/Lua 595 | 596 | PL/Lua is distributed as a source package and can be obtained at [PgFoundry][22]. Depending on how Lua is installed in your system you might have to edit the Makefile. After that the source package is installed like any regular PostgreSQL module, that is, after downloading and unpacking, just run: 597 | 598 | ``` 599 | $ export PG_CONFIG='/usr/pgsql-9.4/bin/pg_config' # specifiy where pg_config is located 600 | $ make && make install 601 | $ psql -c "CREATE EXTENSION pllua" mydb 602 | ``` 603 | 604 | The `pllua` extension installs both trusted and untrusted flavors of PL/Lua and creates the module table `pllua.init`. Alternatively, a systemwide installation though the PL template facility can be achieved with: 605 | 606 | ```sql 607 | INSERT INTO pg_catalog.pg_pltemplate 608 | VALUES ('pllua', true, 'pllua_call_handler', 'pllua_validator', '$libdir/pllua', NULL); 609 | 610 | INSERT INTO pg_catalog.pg_pltemplate 611 | VALUES ('plluau', false, 'plluau_call_handler', 'plluau_validator', '$libdir/pllua', NULL); 612 | ``` 613 | 614 | ### License 615 | 616 | Copyright (c) 2008 Luis Carvalho 617 | 618 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 619 | 620 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 621 | 622 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 623 | 624 | 625 | 626 | [8]: http://www.lua.org 627 | [9]: http://www.postgresql.org 628 | [11]: http://www.lua.org/license.html 629 | [12]: http://www.opensource.org/licenses/mit-license.php 630 | [15]: http://www.lua.org/pil/9.3.html 631 | [16]: http://www.lua.org/pil 632 | [22]: http://pgfoundry.org/projects/pllua 633 | 634 | 635 | -------------------------------------------------------------------------------- /plluaspi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * plluaspi.c: PL/Lua SPI 3 | * Author: Luis Carvalho 4 | * Please check copyright notice at the bottom of pllua.h 5 | * $Id: plluaspi.c,v 1.19 2008/03/31 22:57:45 carvalho Exp $ 6 | */ 7 | 8 | #include "pllua.h" 9 | #include "pllua_xact_cleanup.h" 10 | #include "pllua_errors.h" 11 | 12 | #ifndef SPI_prepare_cursor 13 | #define SPI_prepare_cursor(cmd, nargs, argtypes, copts) \ 14 | SPI_prepare(cmd, nargs, argtypes) 15 | #endif 16 | 17 | #define SPI_plan void 18 | static const char PLLUA_BUFFER[] = "luaP_Buffer"; 19 | static const char PLLUA_TUPTABLE[] = "luaP_Tuptable"; 20 | static const char PLLUA_TUPLEMT[] = "tuple"; 21 | static const char PLLUA_P_TUPLEMT[] = "ptuple"; 22 | static const char PLLUA_PLANMT[] = "plan"; 23 | static const char PLLUA_CURSORMT[] = "cursor"; 24 | static const char PLLUA_TUPTABLEMT[] = "tupletable"; 25 | 26 | #include "rtupdesc.h" 27 | 28 | typedef struct luaP_Tuple { 29 | int changed; 30 | Oid relid; 31 | HeapTuple tuple; 32 | TupleDesc tupdesc; 33 | Datum *value; 34 | bool *null; 35 | RTupDesc *rtupdesc; 36 | } luaP_Tuple; 37 | 38 | typedef struct luaP_Tuptable { 39 | int size; 40 | Portal cursor; 41 | SPITupleTable *tuptable; 42 | TupleDesc tupdesc; 43 | RTupDesc *rtupdesc; 44 | } luaP_Tuptable; 45 | 46 | typedef struct luaP_Cursor { 47 | Portal cursor; 48 | RTupDesc *rtupdesc; 49 | void *tupleQueue; 50 | void *resptr; 51 | } luaP_Cursor; 52 | 53 | typedef struct luaP_Plan { 54 | int nargs; 55 | int issaved; 56 | SPI_plan *plan; 57 | Oid type[1]; 58 | } luaP_Plan; 59 | 60 | 61 | /* ======= Utils ======= */ 62 | 63 | static int luaP_typeerror (lua_State *L, int narg, const char *tname) { 64 | const char *msg = lua_pushfstring(L, "%s expected, got %s", 65 | tname, luaL_typename(L, narg)); 66 | return luaL_argerror(L, narg, msg); 67 | } 68 | 69 | #define luaP_getfield(L, s) \ 70 | lua_pushlightuserdata((L), (void *)(s)); \ 71 | lua_rawget((L), LUA_REGISTRYINDEX) 72 | 73 | static void luaP_newmetatable (lua_State *L, const char *tname) { 74 | lua_newtable(L); 75 | lua_pushlightuserdata(L, (void *) tname); 76 | lua_pushvalue(L, -2); 77 | lua_rawset(L, LUA_REGISTRYINDEX); 78 | } 79 | 80 | void *luaP_toudata (lua_State *L, int ud, const char *tname) { 81 | void *p = lua_touserdata(L, ud); 82 | if (p != NULL) { /* value is userdata? */ 83 | if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ 84 | luaP_getfield(L, tname); /* get metatable */ 85 | if (lua_rawequal(L, -1, -2)) { /* MTs match? */ 86 | lua_pop(L, 2); /* MTs */ 87 | return p; 88 | } 89 | } 90 | } 91 | return NULL; 92 | } 93 | 94 | static void *luaP_checkudata (lua_State *L, int ud, const char *tname) { 95 | void *p = luaP_toudata(L, ud, tname); 96 | if (p == NULL) luaP_typeerror(L, ud, tname); 97 | return p; 98 | } 99 | 100 | void luaP_pushdesctable (lua_State *L, TupleDesc desc) { 101 | int i; 102 | lua_newtable(L); 103 | for (i = 0; i < desc->natts; i++) { 104 | lua_pushstring(L, NameStr(TupleDescAttr(desc, i)->attname)); 105 | lua_pushinteger(L, i); 106 | lua_rawset(L, -3); /* t[att] = i */ 107 | } 108 | } 109 | 110 | static void luaP_pushtuple_cmn (lua_State *L, HeapTuple tuple, 111 | int readonly, RTupDesc* rtupdesc) { 112 | luaP_Tuple *t; 113 | TupleDesc tupleDesc; 114 | int i, n; 115 | 116 | BEGINLUA; 117 | tupleDesc = rtupdesc->tupdesc; 118 | n = tupleDesc->natts; 119 | 120 | t = lua_newuserdata(L, sizeof(luaP_Tuple) 121 | + n * (sizeof(Datum) + sizeof(bool))); 122 | 123 | t->value = (Datum *) (t + 1); 124 | t->null = (bool *) (t->value + n); 125 | t->rtupdesc = rtupdesc_ref(rtupdesc); 126 | for (i = 0; i < n; i++) { 127 | 128 | bool isnull; 129 | t->value[i] = heap_getattr(tuple, TupleDescAttr(tupleDesc, i)->attnum, tupleDesc, 130 | &isnull); 131 | t->null[i] = isnull; 132 | } 133 | 134 | if (readonly) { 135 | t->changed = -1; 136 | } 137 | else { 138 | t->changed = 0; 139 | } 140 | 141 | t->tupdesc = 0; 142 | 143 | t->relid = 0; 144 | t->tuple = tuple; 145 | luaP_getfield(L, PLLUA_TUPLEMT); 146 | lua_setmetatable(L, -2); 147 | ENDLUAV(1); 148 | } 149 | static luaP_Tuple* luaP_PTuple_rawctr(lua_State * L, HeapTuple tuple, int readonly, RTupDesc* rtupdesc); 150 | static luaP_Tuple* luaP_pushPTuple(lua_State * L, size_t size, luaP_Tuple *ptr); 151 | #define LUAP_pushtuple_from_ptr(L,t) luaP_pushPTuple(L,0,t) 152 | 153 | void 154 | luaP_pushrecord(lua_State *L, Datum record){ 155 | HeapTupleHeader header = DatumGetHeapTupleHeader(record); 156 | TupleDesc tupdesc; 157 | HeapTupleData tuple; 158 | RTupDesc *shared_desc; 159 | 160 | PG_TRY(); 161 | { 162 | tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(header), 163 | HeapTupleHeaderGetTypMod(header)); 164 | /* Build a temporary HeapTuple control structure */ 165 | tuple.t_len = HeapTupleHeaderGetDatumLength(header); 166 | ItemPointerSetInvalid(&(tuple.t_self)); 167 | tuple.t_tableOid = InvalidOid; 168 | tuple.t_data = header; 169 | 170 | shared_desc = rtupdesc_ctor(L, tupdesc); 171 | luaP_pushtuple_cmn(L, &tuple, true, shared_desc); 172 | rtupdesc_unref(shared_desc); 173 | 174 | ReleaseTupleDesc(tupdesc); 175 | } 176 | PG_CATCH(); 177 | { 178 | luaL_error(L, "record to lua error"); 179 | } 180 | PG_END_TRY(); 181 | } 182 | 183 | //////////////////////////////////////////////////////////////////////////////// 184 | #define FETCH_CSR_Q 50 185 | #define TUPLE_QUEUE_SIZE FETCH_CSR_Q + 1 186 | typedef struct { 187 | int head, tail; 188 | luaP_Tuple* data[TUPLE_QUEUE_SIZE]; 189 | } TupleQueue, *TupleQueuePtr; 190 | 191 | static TupleQueuePtr tq_initQueue(lua_State *L) { 192 | TupleQueuePtr qp; 193 | MTOLUA(L); 194 | qp = (TupleQueuePtr) palloc(sizeof(TupleQueue)); 195 | MTOPG; 196 | qp -> head = qp -> tail = 0; 197 | return qp; 198 | } 199 | static int tq_isempty(TupleQueuePtr Q) { 200 | return (Q -> head == Q -> tail); 201 | } 202 | 203 | static void tq_enqueue(TupleQueuePtr Q, luaP_Tuple* n) { 204 | if (Q -> tail == TUPLE_QUEUE_SIZE - 1) 205 | Q -> tail = 0; 206 | else 207 | ++(Q -> tail); 208 | if (Q -> tail == Q -> head) { //Queue is full 209 | return; 210 | } 211 | Q -> data[Q -> tail] = n; 212 | } 213 | 214 | static luaP_Tuple* tq_dequeue(TupleQueuePtr Q) { 215 | if (tq_isempty(Q)) { 216 | return NULL; 217 | } 218 | if (Q -> head == TUPLE_QUEUE_SIZE - 1) 219 | Q -> head = 0; 220 | else 221 | ++(Q -> head); 222 | return Q -> data[Q -> head]; 223 | } 224 | #undef TUPLE_QUEUE_SIZE 225 | 226 | static int luaP_rowsaux (lua_State *L) { 227 | luaP_Cursor *c; 228 | luaP_Tuple* t; 229 | unsigned int i; 230 | uint32 processed = 0; 231 | 232 | BEGINLUA; 233 | c = (luaP_Cursor *) lua_touserdata(L, lua_upvalueindex(1)); 234 | 235 | if (c->tupleQueue && tq_isempty(c->tupleQueue)){ 236 | pfree(c->tupleQueue); 237 | c->tupleQueue = NULL; 238 | } 239 | 240 | if (c->tupleQueue == NULL){ 241 | 242 | 243 | PLLUA_PG_CATCH_RETHROW( 244 | SPI_cursor_fetch(c->cursor, 1, FETCH_CSR_Q); 245 | ); 246 | 247 | if (SPI_processed == 0){ 248 | SPI_freetuptable(SPI_tuptable); 249 | c->rtupdesc = rtupdesc_unref(c->rtupdesc); 250 | c->resptr = unregister_resource(c->resptr); 251 | SPI_cursor_close(c->cursor); 252 | c->cursor = NULL; 253 | lua_pushnil(L); 254 | ENDLUAV(1); 255 | return 1; 256 | } 257 | if(c->rtupdesc == NULL){ 258 | c->rtupdesc = rtupdesc_ctor(L,SPI_tuptable->tupdesc); 259 | } 260 | c->tupleQueue = tq_initQueue(L); 261 | for (i = 0; i < SPI_processed; i++) 262 | { 263 | HeapTuple tuple = SPI_tuptable->vals[i]; 264 | t = luaP_PTuple_rawctr(L, tuple, 1, c->rtupdesc); 265 | tq_enqueue(c->tupleQueue, t); 266 | processed++; 267 | } 268 | SPI_freetuptable(SPI_tuptable); 269 | 270 | } 271 | 272 | t = tq_dequeue(c->tupleQueue); 273 | 274 | LUAP_pushtuple_from_ptr(L, t); 275 | 276 | ENDLUAV(1); 277 | return 1; 278 | } 279 | #undef FETCH_CSR_Q 280 | 281 | //////////////////////////////////////////////////////////////////////////////// 282 | 283 | 284 | 285 | /* ======= Buffer ======= */ 286 | 287 | luaP_Buffer *luaP_getbuffer (lua_State *L, int n) { 288 | int i; 289 | luaP_Buffer *b; 290 | luaP_getfield(L, PLLUA_BUFFER); 291 | b = (luaP_Buffer *) lua_touserdata(L, -1); 292 | lua_pop(L, 1); 293 | if (b == NULL || n > b->size) { /* resize? */ 294 | lua_pushlightuserdata(L, (void *) PLLUA_BUFFER); 295 | b = (luaP_Buffer *) lua_newuserdata(L, sizeof(luaP_Buffer) 296 | + n * (sizeof(Datum) + sizeof(char))); 297 | b->size = n; 298 | b->value = (Datum *) (b + 1); 299 | b->null = (char *) (b->value + n); 300 | lua_rawset(L, LUA_REGISTRYINDEX); 301 | } 302 | for (i = 0; i < n; i++) { 303 | b->value[i] = 0; 304 | b->null[i] = 'n'; 305 | } 306 | return b; 307 | } 308 | 309 | static void luaP_fillbuffer (lua_State *L, int pos, Oid *type, 310 | luaP_Buffer *b) { 311 | lua_pushnil(L); 312 | while (lua_next(L, pos)) { 313 | int k = lua_tointeger(L, -2); 314 | if (k > 0) { 315 | bool isnull; 316 | k--; /* zero based */ 317 | b->value[k] = luaP_todatum(L, type[k], 0, &isnull, -1); 318 | b->null[k] = (isnull) ? 'n' : ' '; 319 | } 320 | lua_pop(L, 1); 321 | } 322 | } 323 | 324 | static void cursor_cleanup_p(void *d, int gccall){ 325 | luaP_Cursor *c = (luaP_Cursor *)d; 326 | if (c->tupleQueue){ 327 | luaP_Tuple* t = tq_dequeue(c->tupleQueue); 328 | while(t){ 329 | pfree(t); 330 | t = tq_dequeue(c->tupleQueue); 331 | } 332 | c->tupleQueue = NULL; 333 | 334 | c->rtupdesc = rtupdesc_unref(c->rtupdesc); 335 | } 336 | if (gccall == 0) 337 | c->resptr = NULL;//if garbage collected 338 | else 339 | c->resptr = unregister_resource(c->resptr);//transaction end 340 | } 341 | 342 | static void cursor_cleanup(void *d){ 343 | cursor_cleanup_p(d,0); 344 | } 345 | 346 | static int luaP_cursorgc (lua_State *L) { 347 | luaP_Cursor *c = (luaP_Cursor *) lua_touserdata(L, 1); 348 | if (c->tupleQueue){ 349 | cursor_cleanup_p(c, 1); 350 | 351 | if (PortalIsValid(c->cursor) && (c->cursor->status == PORTAL_READY)){ 352 | c->resptr = unregister_resource(c->resptr); 353 | SPI_cursor_close(c->cursor); 354 | } 355 | } 356 | return 0; 357 | } 358 | /* ======= Tuple ======= */ 359 | static int luaP_tuplegc (lua_State *L) { 360 | luaP_Tuple *t = (luaP_Tuple *) lua_touserdata(L, 1); 361 | rtupdesc_unref(t->rtupdesc); 362 | return 0; 363 | } 364 | static luaP_Tuple* luaP_PTuple_rawctr(lua_State * L, HeapTuple tuple, int readonly, RTupDesc* rtupdesc){ 365 | luaP_Tuple *t; 366 | TupleDesc tupleDesc; 367 | int i, n; 368 | 369 | tupleDesc = rtupdesc->tupdesc; 370 | n = tupleDesc->natts; 371 | MTOLUA(L); 372 | t = palloc(sizeof(luaP_Tuple) + n * (sizeof(Datum) + sizeof(bool))); 373 | MTOPG; 374 | t->value = (Datum *) (t + 1); 375 | t->null = (bool *) (t->value + n); 376 | t->rtupdesc = rtupdesc_ref(rtupdesc); 377 | for (i = 0; i < n; i++) { 378 | bool isnull; 379 | t->value[i] = heap_getattr(tuple, TupleDescAttr(tupleDesc, i)->attnum, tupleDesc, 380 | &isnull); 381 | t->null[i] = isnull; 382 | } 383 | 384 | if (readonly) { 385 | t->changed = -1; 386 | } 387 | else { 388 | t->changed = 0; 389 | } 390 | 391 | t->tupdesc = 0; 392 | 393 | t->relid = 0; 394 | t->tuple = tuple; 395 | return t; 396 | } 397 | 398 | static luaP_Tuple* luaP_pushPTuple(lua_State * L, size_t size, luaP_Tuple *ptr) 399 | { 400 | luaP_Tuple ** udata = (luaP_Tuple **)lua_newuserdata(L, sizeof(luaP_Tuple *)); 401 | if (ptr == NULL){ 402 | MTOLUA(L); 403 | *udata = palloc(size); 404 | MTOPG; 405 | }else{ 406 | *udata = ptr; 407 | } 408 | luaP_getfield(L, PLLUA_P_TUPLEMT); 409 | lua_setmetatable(L, -2); 410 | return *udata; 411 | } 412 | 413 | static int luaP_p_tuplegc (lua_State *L) { 414 | luaP_Tuple *t = *(luaP_Tuple **) lua_touserdata(L, 1); 415 | rtupdesc_unref(t->rtupdesc); 416 | pfree(t); 417 | return 0; 418 | } 419 | 420 | static int luaP_p_tupleindex (lua_State *L) { 421 | const char *name; 422 | int i =-1; 423 | int idx = -1; 424 | luaP_Tuple *t = *(luaP_Tuple **) lua_touserdata(L, 1); 425 | 426 | if (lua_type(L, 2) == LUA_TNUMBER){ 427 | i = (int)lua_tonumber(L, 2); 428 | if (t->rtupdesc){ 429 | TupleDesc tupleDesc = rtupdesc_gettup(t->rtupdesc); 430 | i= i-1; //Lua[1] == C[0] 431 | if (tupleDesc == NULL){ 432 | ereport(WARNING, (errmsg("access to lost tuple desc at index %i", i+1))); 433 | lua_pushnil(L); 434 | return 1; 435 | } 436 | if ((i >= 0)&&(i < tupleDesc->natts)) { 437 | if (!t->null[i]) 438 | luaP_pushdatum(L, t->value[i], TupleDescAttr(tupleDesc, i)->atttypid); 439 | else lua_pushnil(L); 440 | } 441 | else { 442 | //ereport(WARNING, (errmsg("tuple has no field at index %i", i+1))); 443 | //lua_pushnil(L); 444 | return luaL_error(L, "tuple has no field at index %d", i+1); 445 | } 446 | return 1; 447 | } 448 | lua_pushnil(L); 449 | return 1; 450 | } 451 | 452 | name = luaL_checkstring(L, 2); 453 | 454 | if (t->rtupdesc){ 455 | TupleDesc tupleDesc = rtupdesc_gettup(t->rtupdesc); 456 | if (tupleDesc == NULL){ 457 | ereport(WARNING, (errmsg("access to lost tuple desc at '%s'", name))); 458 | lua_pushnil(L); 459 | return 1; 460 | } 461 | for (i = 0; i< tupleDesc->natts; ++i){ 462 | 463 | if (strcmp(NameStr(TupleDescAttr(tupleDesc, i)->attname),name) == 0){ 464 | idx = i; 465 | break; 466 | } 467 | } 468 | i = idx; 469 | if (i >= 0) { 470 | if (!t->null[i]) 471 | luaP_pushdatum(L, t->value[i], TupleDescAttr(tupleDesc, i)->atttypid); 472 | else lua_pushnil(L); 473 | } 474 | else { 475 | //ereport(WARNING, (errmsg("tuple has no field '%s'", name))); 476 | //lua_pushnil(L); 477 | return luaL_error(L, "tuple has no field '%s'", name); 478 | 479 | } 480 | return 1; 481 | } 482 | lua_pushnil(L); 483 | return 1; 484 | } 485 | 486 | static int luaP_tupleindex (lua_State *L) { 487 | luaP_Tuple *t = (luaP_Tuple *) lua_touserdata(L, 1); 488 | const char *name = luaL_checkstring(L, 2); 489 | int i =-1; 490 | int idx = -1; 491 | if (t->rtupdesc){ 492 | TupleDesc tupleDesc = rtupdesc_gettup(t->rtupdesc); 493 | if (tupleDesc == NULL){ 494 | ereport(WARNING, (errmsg("access to lost tuple desc at '%s'", name))); 495 | lua_pushnil(L); 496 | return 1; 497 | } 498 | for (i = 0; i< tupleDesc->natts; ++i){ 499 | 500 | if (strcmp(NameStr(TupleDescAttr(tupleDesc, i)->attname),name) == 0){ 501 | idx = i; 502 | break; 503 | } 504 | } 505 | i = idx; 506 | if (i >= 0) { 507 | if (!t->null[i]) 508 | luaP_pushdatum(L, t->value[i], TupleDescAttr(tupleDesc, i)->atttypid); 509 | else lua_pushnil(L); 510 | } 511 | else { 512 | ereport(WARNING, (errmsg("tuple has no field '%s'", name))); 513 | lua_pushnil(L); 514 | } 515 | return 1; 516 | } 517 | //triggers data 518 | 519 | 520 | lua_push_oidstring(L, (int) t->relid); 521 | lua_rawget(L, LUA_REGISTRYINDEX); 522 | lua_getfield(L, -1, name); 523 | i = luaL_optinteger(L, -1, -1); 524 | 525 | 526 | if (i >= 0) { 527 | if (!t->null[i]) 528 | luaP_pushdatum(L, t->value[i], TupleDescAttr(t->tupdesc, i)->atttypid); 529 | else lua_pushnil(L); 530 | } 531 | else lua_pushnil(L); 532 | return 1; 533 | } 534 | 535 | static int luaP_tuplenewindex (lua_State *L) { 536 | luaP_Tuple *t = (luaP_Tuple *) lua_touserdata(L, 1); 537 | const char *name = luaL_checkstring(L, 2); 538 | int i; 539 | if (t->changed == -1) /* read-only? */ 540 | return luaL_error(L, "tuple is read-only"); 541 | lua_push_oidstring(L, (int) t->relid); 542 | lua_rawget(L, LUA_REGISTRYINDEX); 543 | lua_getfield(L, -1, name); 544 | i = luaL_optinteger(L, -1, -1); 545 | lua_settop(L, 3); 546 | if (i >= 0) { /* found? */ 547 | bool isnull; 548 | t->value[i] = luaP_todatum(L, TupleDescAttr(t->tupdesc, i)->atttypid, 549 | TupleDescAttr(t->tupdesc, i)->atttypmod, &isnull, -1); 550 | t->null[i] = isnull; 551 | t->changed = 1; 552 | } 553 | else 554 | return luaL_error(L, "column not found in relation: '%s'", name); 555 | return 0; 556 | } 557 | 558 | static int luaP_tupletostring (lua_State *L) { 559 | lua_pushfstring(L, "%s: %p", PLLUA_TUPLEMT, lua_touserdata(L, 1)); 560 | return 1; 561 | } 562 | 563 | void luaP_pushtuple_trg (lua_State *L, TupleDesc desc, HeapTuple tuple, 564 | Oid relid, int readonly) { 565 | luaP_Tuple *t; 566 | int i, n; 567 | 568 | BEGINLUA; 569 | 570 | n = desc->natts; 571 | 572 | t = lua_newuserdata(L, sizeof(luaP_Tuple) 573 | + n * (sizeof(Datum) + sizeof(bool))); 574 | if (readonly) { 575 | t->changed = -1; 576 | } 577 | else { 578 | t->changed = 0; 579 | } 580 | 581 | t->value = (Datum *) (t + 1); 582 | t->null = (bool *) (t->value + n); 583 | 584 | t->rtupdesc = 0; 585 | for (i = 0; i < n; i++) { 586 | bool isnull; 587 | t->value[i] = heap_getattr(tuple, TupleDescAttr(desc, i)->attnum, desc, 588 | &isnull); 589 | t->null[i] = isnull; 590 | } 591 | 592 | t->tupdesc = desc; 593 | t->relid = relid; 594 | t->tuple = tuple; 595 | luaP_getfield(L, PLLUA_TUPLEMT); 596 | lua_setmetatable(L, -2); 597 | ENDLUAV(1); 598 | } 599 | 600 | 601 | /* adapted from SPI_modifytuple */ 602 | static HeapTuple luaP_copytuple (luaP_Tuple *t) { 603 | HeapTuple tuple = heap_form_tuple(t->tupdesc, t->value, t->null); 604 | /* copy identification info */ 605 | tuple->t_data->t_ctid = t->tuple->t_data->t_ctid; 606 | tuple->t_self = t->tuple->t_self; 607 | tuple->t_tableOid = t->tuple->t_tableOid; 608 | if (t->tupdesc->tdhasoid) 609 | HeapTupleSetOid(tuple, HeapTupleGetOid(t->tuple)); 610 | return SPI_copytuple(tuple); /* in upper mem context */ 611 | } 612 | 613 | static luaP_Tuple *luaP_checktuple (lua_State *L, int pos) { 614 | luaP_Tuple *t = (luaP_Tuple *) lua_touserdata(L, pos); 615 | if (t != NULL) { 616 | if (lua_getmetatable(L, pos)) { 617 | luaP_getfield(L, PLLUA_TUPLEMT); 618 | if (!lua_rawequal(L, -1, -2)) /* not tuple? */ 619 | t = NULL; 620 | lua_pop(L, 2); /* metatables */ 621 | } 622 | } 623 | return t; 624 | } 625 | 626 | /* tuple on top of stack */ 627 | HeapTuple luaP_totuple (lua_State *L) { 628 | luaP_Tuple *t = luaP_checktuple(L, -1); 629 | if (t == NULL) return NULL; /* not a tuple */ 630 | return (t->changed == 1) ? luaP_copytuple(t) : t->tuple; 631 | } 632 | 633 | /* tuple on top of stack */ 634 | HeapTuple luaP_casttuple (lua_State *L, TupleDesc tupdesc) { 635 | luaP_Tuple *t = luaP_checktuple(L, -1); 636 | int i; 637 | luaP_Buffer *b; 638 | if (t == NULL) return NULL; /* not a tuple */ 639 | lua_push_oidstring(L, (int) t->relid); 640 | lua_rawget(L, LUA_REGISTRYINDEX); /* tuple desc table */ 641 | b = luaP_getbuffer(L, tupdesc->natts); 642 | for (i = 0; i < tupdesc->natts; i++) { 643 | int j; 644 | lua_getfield(L, -1, NameStr(TupleDescAttr(tupdesc, i)->attname)); 645 | j = luaL_optinteger(L, -1, -1); 646 | if (j >= 0) { 647 | if (t->changed == -1) /* read-only? */ 648 | b->value[i] = heap_getattr(t->tuple, 649 | TupleDescAttr(t->tupdesc, j)->attnum, t->tupdesc, b->null + i); 650 | else { 651 | b->value[i] = t->value[j]; 652 | b->null[i] = t->null[j]; 653 | } 654 | } 655 | lua_pop(L, 1); 656 | } 657 | lua_pop(L, 1); /* desc table */ 658 | return heap_form_tuple(tupdesc, b->value, b->null); 659 | } 660 | 661 | /* ======= TupleTable ======= */ 662 | 663 | static void luaP_pushtuptable (lua_State *L, Portal cursor) { 664 | luaP_Tuptable *t; 665 | 666 | BEGINLUA; 667 | 668 | luaP_getfield(L, PLLUA_TUPTABLE); 669 | t = (luaP_Tuptable *) lua_touserdata(L, -1); 670 | if (t == NULL) { /* not initialized? */ 671 | lua_pop(L, 1); 672 | t = (luaP_Tuptable *) lua_newuserdata(L, sizeof(luaP_Tuptable)); 673 | t->rtupdesc = 0; 674 | 675 | luaP_getfield(L, PLLUA_TUPTABLEMT); 676 | lua_setmetatable(L, -2); 677 | lua_pushlightuserdata(L, (void *) PLLUA_TUPTABLE); 678 | lua_pushvalue(L, -2); /* tuptable */ 679 | lua_rawset(L, LUA_REGISTRYINDEX); 680 | } 681 | t->size = SPI_processed; 682 | t->tuptable = SPI_tuptable; 683 | t->rtupdesc = rtupdesc_ctor(L,SPI_tuptable->tupdesc); 684 | 685 | if (cursor == NULL || (cursor != NULL && t->cursor != cursor)) { 686 | t->cursor = cursor; 687 | } 688 | 689 | /* reset tuptable env */ 690 | lua_newtable(L); /* env */ 691 | lua_setuservalue(L, -2); 692 | ENDLUAV(1); 693 | } 694 | 695 | static int luaP_tuptableindex (lua_State *L) { 696 | luaP_Tuptable *t = (luaP_Tuptable *) lua_touserdata(L, 1); 697 | int k = lua_tointeger(L, 2); 698 | 699 | if (k == 0) { /* attributes? */ 700 | 701 | lua_pushnil(L); 702 | } 703 | else if (k > 0 && k <= t->size) { 704 | lua_getuservalue(L, 1); 705 | lua_rawgeti(L, -1, k); 706 | if (lua_isnil(L, -1)) { /* not interned? */ 707 | lua_pop(L, 1); /* nil */ 708 | 709 | luaP_pushtuple_cmn(L, t->tuptable->vals[k - 1], 710 | 1, t->rtupdesc); 711 | 712 | lua_pushvalue(L, -1); 713 | 714 | lua_rawseti(L, -3, k); 715 | } 716 | }else{ 717 | lua_pushnil(L); 718 | } 719 | return 1; 720 | } 721 | 722 | static int luaP_tuptablelen (lua_State *L) { 723 | luaP_Tuptable *t = (luaP_Tuptable *) lua_touserdata(L, 1); 724 | lua_pushinteger(L, t->size); 725 | return 1; 726 | } 727 | 728 | static int luaP_tuptablegc (lua_State *L) { 729 | //in case SELECT * FROM get_rows('name'); not sure if collected... 730 | luaP_Tuptable *t = (luaP_Tuptable *) lua_touserdata(L, 1); 731 | rtupdesc_unref(t->rtupdesc); 732 | SPI_freetuptable(t->tuptable); 733 | return 0; 734 | } 735 | 736 | static int luaP_tuptabletostring (lua_State *L) { 737 | lua_pushfstring(L, "%s: %p", PLLUA_TUPTABLEMT, lua_touserdata(L, 1)); 738 | return 1; 739 | } 740 | 741 | 742 | /* ======= Cursor ======= */ 743 | 744 | void luaP_pushcursor (lua_State *L, Portal cursor) { 745 | 746 | luaP_Cursor *c = (luaP_Cursor *) lua_newuserdata(L, sizeof(luaP_Cursor)); 747 | c->cursor = cursor; 748 | c->rtupdesc = NULL; 749 | c->tupleQueue = NULL; 750 | c->resptr = register_resource(c, cursor_cleanup); 751 | luaP_getfield(L, PLLUA_CURSORMT); 752 | lua_setmetatable(L, -2); 753 | } 754 | 755 | Portal luaP_tocursor (lua_State *L, int pos) { 756 | luaP_Cursor *c = (luaP_Cursor *) luaP_checkudata(L, pos, PLLUA_CURSORMT); 757 | return c->cursor; 758 | } 759 | 760 | static int luaP_cursortostring (lua_State *L) { 761 | luaP_Cursor *c = (luaP_Cursor *) lua_touserdata(L, 1); 762 | lua_pushfstring(L, "%s: %p [%s]", PLLUA_CURSORMT, c, c->cursor->name); 763 | return 1; 764 | } 765 | 766 | static int luaP_cursorfetch (lua_State *L) { 767 | luaP_Cursor *c = (luaP_Cursor *) luaP_checkudata(L, 1, PLLUA_CURSORMT); 768 | #if LUA_VERSION_NUM >= 503 769 | SPI_cursor_fetch(c->cursor, 1, luaL_optinteger(L, 2, FETCH_ALL)); 770 | #else 771 | SPI_cursor_fetch(c->cursor, 1, luaL_optlong(L, 2, FETCH_ALL)); 772 | #endif 773 | if (SPI_processed > 0) /* any rows? */ 774 | luaP_pushtuptable(L, c->cursor); 775 | else 776 | lua_pushnil(L); 777 | return 1; 778 | } 779 | 780 | static int luaP_cursormove (lua_State *L) { 781 | luaP_Cursor *c = (luaP_Cursor *) luaP_checkudata(L, 1, PLLUA_CURSORMT); 782 | #if LUA_VERSION_NUM >= 503 783 | SPI_cursor_move(c->cursor, 1, luaL_optinteger(L, 2, 0)); 784 | #else 785 | SPI_cursor_move(c->cursor, 1, luaL_optlong(L, 2, 0)); 786 | #endif 787 | return 0; 788 | } 789 | 790 | #if PG_VERSION_NUM >= 80300 791 | static int luaP_cursorposfetch (lua_State *L) { 792 | luaP_Cursor *c = (luaP_Cursor *) luaP_checkudata(L, 1, PLLUA_CURSORMT); 793 | FetchDirection fd = (lua_toboolean(L, 3)) ? FETCH_RELATIVE : FETCH_ABSOLUTE; 794 | #if LUA_VERSION_NUM >= 503 795 | SPI_scroll_cursor_fetch(c->cursor, fd, luaL_optinteger(L, 2, FETCH_ALL)); 796 | #else 797 | SPI_scroll_cursor_fetch(c->cursor, fd, luaL_optlong(L, 2, FETCH_ALL)); 798 | #endif 799 | if (SPI_processed > 0) /* any rows? */ 800 | luaP_pushtuptable(L, c->cursor); 801 | else 802 | lua_pushnil(L); 803 | return 1; 804 | } 805 | 806 | static int luaP_cursorposmove (lua_State *L) { 807 | luaP_Cursor *c = (luaP_Cursor *) luaP_checkudata(L, 1, PLLUA_CURSORMT); 808 | FetchDirection fd = (lua_toboolean(L, 3)) ? FETCH_RELATIVE : FETCH_ABSOLUTE; 809 | #if LUA_VERSION_NUM >= 503 810 | SPI_scroll_cursor_move(c->cursor, fd, luaL_optinteger(L, 2, 0)); 811 | #else 812 | SPI_scroll_cursor_move(c->cursor, fd, luaL_optlong(L, 2, 0)); 813 | #endif 814 | return 0; 815 | } 816 | #endif 817 | 818 | 819 | static int luaP_cursorclose (lua_State *L) { 820 | luaP_Cursor *c = (luaP_Cursor *) luaP_checkudata(L, 1, PLLUA_CURSORMT); 821 | //c->resptr is null, cursor registered if used as upvalue 822 | c->resptr = unregister_resource(c->resptr); 823 | SPI_cursor_close(c->cursor); 824 | return 0; 825 | } 826 | 827 | 828 | /* ======= Plan ======= */ 829 | 830 | static int luaP_plangc (lua_State *L) { 831 | luaP_Plan *p = (luaP_Plan *) lua_touserdata(L, 1); 832 | if (p->issaved) SPI_freeplan(p->plan); 833 | return 0; 834 | } 835 | 836 | static int luaP_plantostring (lua_State *L) { 837 | lua_pushfstring(L, "plan: %p", lua_touserdata(L, 1)); 838 | return 1; 839 | } 840 | 841 | 842 | 843 | 844 | static int luaP_executeplan (lua_State *L) { 845 | luaP_Plan *p = (luaP_Plan *) luaP_checkudata(L, 1, PLLUA_PLANMT); 846 | bool ro = (bool) lua_toboolean(L, 3); 847 | #if LUA_VERSION_NUM >= 503 848 | long c = luaL_optinteger(L, 4, 0); 849 | #else 850 | long c = luaL_optlong(L, 4, 0); 851 | #endif 852 | int result = -1; 853 | Datum *values = NULL; 854 | char *nulls = NULL; 855 | 856 | 857 | if (p->nargs > 0) { 858 | luaP_Buffer *b; 859 | if (lua_type(L, 2) != LUA_TTABLE) luaP_typeerror(L, 2, "table"); 860 | b = luaP_getbuffer(L, p->nargs); 861 | luaP_fillbuffer(L, 2, p->type, b); 862 | values = b->value; 863 | nulls = b->null; 864 | 865 | } 866 | 867 | PLLUA_PG_CATCH_RETHROW( 868 | result = SPI_execute_plan(p->plan, values, nulls, ro, c); 869 | ); 870 | 871 | if (result < 0) 872 | return luaL_error(L, "SPI_execute_plan error: %d", result); 873 | if (((result == SPI_OK_SELECT) 874 | ||(result == SPI_OK_UPDATE_RETURNING) 875 | ||(result == SPI_OK_INSERT_RETURNING) 876 | ||(result == SPI_OK_DELETE_RETURNING) 877 | )&& SPI_processed > 0) /* any rows? */ 878 | luaP_pushtuptable(L, NULL); 879 | else 880 | lua_pushnil(L); 881 | return 1; 882 | } 883 | 884 | static int luaP_saveplan (lua_State *L) { 885 | luaP_Plan *p = (luaP_Plan *) luaP_checkudata(L, 1, PLLUA_PLANMT); 886 | PLLUA_PG_CATCH_RETHROW( 887 | p->plan = SPI_saveplan(p->plan); 888 | ); 889 | switch (SPI_result) { 890 | case SPI_ERROR_ARGUMENT: 891 | return luaL_error(L, "null plan to be saved"); 892 | case SPI_ERROR_UNCONNECTED: 893 | return luaL_error(L, "unconnected procedure"); 894 | } 895 | p->issaved = 1; 896 | return 1; 897 | } 898 | 899 | static int luaP_issavedplan (lua_State *L) { 900 | luaP_Plan *p = (luaP_Plan *) luaP_checkudata(L, 1, PLLUA_PLANMT); 901 | lua_pushboolean(L, p->issaved); 902 | return 1; 903 | } 904 | 905 | static int luaP_getcursorplan (lua_State *L) { 906 | luaP_Plan *p = (luaP_Plan *) luaP_checkudata(L, 1, PLLUA_PLANMT); 907 | bool ro = (bool) lua_toboolean(L, 3); 908 | const char *name = lua_tostring(L, 4); 909 | Portal cursor = NULL; 910 | Datum *values = NULL; 911 | char *nulls = NULL; 912 | if (SPI_is_cursor_plan(p->plan)) { 913 | if (p->nargs > 0) { 914 | luaP_Buffer *b; 915 | if (lua_type(L, 2) != LUA_TTABLE) luaP_typeerror(L, 2, "table"); 916 | b = luaP_getbuffer(L, p->nargs); 917 | luaP_fillbuffer(L, 2, p->type, b); 918 | values = b->value; 919 | nulls = b->null; 920 | } 921 | PLLUA_PG_CATCH_RETHROW( 922 | cursor = SPI_cursor_open(name, p->plan, values, nulls, ro); 923 | ); 924 | if (cursor == NULL) 925 | return luaL_error(L, "error opening cursor"); 926 | luaP_pushcursor(L, cursor); 927 | } 928 | else lua_pushnil(L); 929 | return 1; 930 | } 931 | 932 | static int luaP_rowsplan (lua_State *L) { 933 | luaP_Plan *p = (luaP_Plan *) luaP_checkudata(L, 1, PLLUA_PLANMT); 934 | Portal cursor = NULL; 935 | Datum *values = NULL; 936 | char *nulls = NULL; 937 | if (!SPI_is_cursor_plan(p->plan)) 938 | return luaL_error(L, "Plan is not iterable"); 939 | if (p->nargs > 0) { 940 | luaP_Buffer *b; 941 | if (lua_type(L, 2) != LUA_TTABLE) luaP_typeerror(L, 2, "table"); 942 | b = luaP_getbuffer(L, p->nargs); 943 | luaP_fillbuffer(L, 2, p->type, b); 944 | values = b->value; 945 | nulls = b->null; 946 | } 947 | PLLUA_PG_CATCH_RETHROW( 948 | cursor = SPI_cursor_open(NULL, p->plan, values, nulls, 1); 949 | ); 950 | 951 | if (cursor == NULL) 952 | return luaL_error(L, "error opening cursor"); 953 | luaP_pushcursor(L, cursor); 954 | lua_pushboolean(L, 0); /* not inited */ 955 | lua_pushcclosure(L, luaP_rowsaux, 2); 956 | return 1; 957 | } 958 | 959 | 960 | /* ======= SPI ======= */ 961 | static int luaP_prepare (lua_State *L) { 962 | int nargs, cursoropt; 963 | const char *q = luaL_checkstring(L, 1); 964 | 965 | luaP_Plan *p; 966 | if (lua_isnoneornil(L, 2)) nargs = 0; 967 | else { 968 | if (lua_type(L, 2) != LUA_TTABLE) luaP_typeerror(L, 2, "table"); 969 | nargs = lua_rawlen(L, 2); 970 | } 971 | cursoropt = luaL_optinteger(L, 3, 0); 972 | (void)cursoropt; 973 | p = (luaP_Plan *) lua_newuserdata(L, 974 | sizeof(luaP_Plan) + nargs * sizeof(Oid)); 975 | p->issaved = 0; 976 | p->nargs = nargs; 977 | if (nargs > 0) { /* read types? */ 978 | lua_pushnil(L); 979 | while (lua_next(L, 2)) { 980 | int k = lua_tointeger(L, -2); 981 | if (k > 0) { 982 | const char *s = luaL_checkstring(L, -1); 983 | Oid type = pg_to_regtype(s); 984 | if (type == InvalidOid) 985 | return luaL_error(L, "invalid type to plan: %s", s); 986 | p->type[k - 1] = type; 987 | } 988 | lua_pop(L, 1); 989 | } 990 | } 991 | PLLUA_PG_CATCH_RETHROW( 992 | p->plan = SPI_prepare_cursor(q, nargs, p->type, cursoropt); 993 | ); 994 | if (SPI_result < 0) 995 | return luaL_error(L, "SPI_prepare error: %d", SPI_result); 996 | luaP_getfield(L, PLLUA_PLANMT); 997 | lua_setmetatable(L, -2); 998 | return 1; 999 | } 1000 | 1001 | static int luaP_execute (lua_State *L) { 1002 | int result = -1; 1003 | PLLUA_PG_CATCH_RETHROW( 1004 | result = SPI_execute(luaL_checkstring(L, 1), 1005 | (bool) lua_toboolean(L, 2), 1006 | #if LUA_VERSION_NUM >= 503 1007 | luaL_optinteger(L, 3, 0)); 1008 | #else 1009 | luaL_optlong(L, 3, 0)); 1010 | #endif 1011 | ); 1012 | if (result < 0) 1013 | return luaL_error(L, "SPI_execute_plan error: %d", result); 1014 | if (result == SPI_OK_SELECT && SPI_processed > 0) /* any rows? */ 1015 | luaP_pushtuptable(L, NULL); 1016 | else 1017 | lua_pushnil(L); 1018 | return 1; 1019 | } 1020 | 1021 | /* returns cursor */ 1022 | static int luaP_find (lua_State *L) { 1023 | Portal cursor = SPI_cursor_find(luaL_checkstring(L, 1)); 1024 | if (cursor != NULL) luaP_pushcursor(L, cursor); 1025 | else lua_pushnil(L); 1026 | return 1; 1027 | } 1028 | 1029 | static int luaP_rows (lua_State *L) { 1030 | Portal cursor; 1031 | PLLUA_PG_CATCH_RETHROW( 1032 | SPI_plan *p = SPI_prepare_cursor(luaL_checkstring(L, 1), 0, NULL, 0); 1033 | if (SPI_result < 0) 1034 | return luaL_error(L, "SPI_prepare error: %d", SPI_result); 1035 | if (!SPI_is_cursor_plan(p)) 1036 | return luaL_error(L, "Statement is not iterable"); 1037 | cursor = SPI_cursor_open(NULL, p, NULL, NULL, 1); 1038 | SPI_freeplan(p); 1039 | if (cursor == NULL) 1040 | return luaL_error(L, "error opening cursor"); 1041 | luaP_pushcursor(L, cursor); 1042 | lua_pushboolean(L, 0); /* not inited */ 1043 | lua_pushcclosure(L, luaP_rowsaux, 2); 1044 | ); 1045 | return 1; 1046 | } 1047 | 1048 | 1049 | /* ======= luaP_registerspi ======= */ 1050 | 1051 | static const luaL_Reg luaP_Plan_funcs[] = { 1052 | {"execute", luaP_executeplan}, 1053 | {"save", luaP_saveplan}, 1054 | {"issaved", luaP_issavedplan}, 1055 | {"getcursor", luaP_getcursorplan}, 1056 | {"rows", luaP_rowsplan}, 1057 | {NULL, NULL} 1058 | }; 1059 | 1060 | static const luaL_Reg luaP_Cursor_funcs[] = { 1061 | {"fetch", luaP_cursorfetch}, 1062 | {"move", luaP_cursormove}, 1063 | #if PG_VERSION_NUM >= 80300 1064 | {"posfetch", luaP_cursorposfetch}, 1065 | {"posmove", luaP_cursorposmove}, 1066 | #endif 1067 | {"close", luaP_cursorclose}, 1068 | {NULL, NULL} 1069 | }; 1070 | 1071 | static const luaL_Reg luaP_SPI_funcs[] = { 1072 | {"prepare", luaP_prepare}, 1073 | {"execute", luaP_execute}, 1074 | {"find", luaP_find}, 1075 | {"rows", luaP_rows}, 1076 | {NULL, NULL} 1077 | }; 1078 | 1079 | static const luaL_Reg luaP_Tuple_mt[] = { 1080 | {"__index", luaP_tupleindex}, 1081 | {"__newindex", luaP_tuplenewindex}, 1082 | {"__tostring", luaP_tupletostring}, 1083 | {"__gc", luaP_tuplegc}, 1084 | {NULL, NULL} 1085 | }; 1086 | 1087 | static const luaL_Reg luaP_p_Tuple_mt[] = { 1088 | {"__index", luaP_p_tupleindex}, 1089 | //{"__newindex", luaP_tuplenewindex}, 1090 | //{"__tostring", luaP_tupletostring}, 1091 | {"__gc", luaP_p_tuplegc}, 1092 | {NULL, NULL} 1093 | }; 1094 | 1095 | static const luaL_Reg luaP_Tuptable_mt[] = { 1096 | {"__index", luaP_tuptableindex}, 1097 | {"__len", luaP_tuptablelen}, 1098 | {"__tostring", luaP_tuptabletostring}, 1099 | {"__gc", luaP_tuptablegc}, 1100 | {NULL, NULL} 1101 | }; 1102 | 1103 | static const luaL_Reg luaP_Cursor_mt[] = { 1104 | {"__tostring", luaP_cursortostring}, 1105 | {"__gc", luaP_cursorgc}, 1106 | {NULL, NULL} 1107 | }; 1108 | 1109 | static const luaL_Reg luaP_Plan_mt[] = { 1110 | {"__gc", luaP_plangc}, 1111 | {"__tostring", luaP_plantostring}, 1112 | {NULL, NULL} 1113 | }; 1114 | 1115 | void luaP_registerspi (lua_State *L) { 1116 | /* tuple */ 1117 | luaP_newmetatable(L, PLLUA_TUPLEMT); 1118 | luaP_register(L, luaP_Tuple_mt); 1119 | lua_pop(L, 1); 1120 | /* ptuple */ 1121 | luaP_newmetatable(L, PLLUA_P_TUPLEMT); 1122 | luaP_register(L, luaP_p_Tuple_mt); 1123 | lua_pop(L, 1); 1124 | /* tuptable */ 1125 | luaP_newmetatable(L, PLLUA_TUPTABLEMT); 1126 | luaP_register(L, luaP_Tuptable_mt); 1127 | lua_pop(L, 1); 1128 | /* cursor */ 1129 | luaP_newmetatable(L, PLLUA_CURSORMT); 1130 | lua_newtable(L); 1131 | luaP_register(L, luaP_Cursor_funcs); 1132 | lua_setfield(L, -2, "__index"); 1133 | luaP_register(L, luaP_Cursor_mt); 1134 | lua_pop(L, 1); 1135 | /* plan */ 1136 | luaP_newmetatable(L, PLLUA_PLANMT); 1137 | lua_newtable(L); 1138 | luaP_register(L, luaP_Plan_funcs); 1139 | lua_setfield(L, -2, "__index"); 1140 | luaP_register(L, luaP_Plan_mt); 1141 | lua_pop(L, 1); 1142 | /* SPI */ 1143 | lua_newtable(L); 1144 | #if PG_VERSION_NUM >= 80300 1145 | lua_newtable(L); /* cursor options */ 1146 | lua_pushinteger(L, CURSOR_OPT_BINARY); 1147 | lua_setfield(L, -2, "binary"); 1148 | lua_pushinteger(L, CURSOR_OPT_SCROLL); 1149 | lua_setfield(L, -2, "scroll"); 1150 | lua_pushinteger(L, CURSOR_OPT_NO_SCROLL); 1151 | lua_setfield(L, -2, "noscroll"); 1152 | lua_pushinteger(L, CURSOR_OPT_INSENSITIVE); 1153 | lua_setfield(L, -2, "insensitive"); 1154 | lua_pushinteger(L, CURSOR_OPT_HOLD); /* ignored */ 1155 | lua_setfield(L, -2, "hold"); 1156 | lua_pushinteger(L, CURSOR_OPT_FAST_PLAN); 1157 | lua_setfield(L, -2, "fastplan"); 1158 | lua_setfield(L, -2, "option"); 1159 | #endif 1160 | luaP_register(L, luaP_SPI_funcs); 1161 | } 1162 | 1163 | --------------------------------------------------------------------------------