pax_global_header00006660000000000000000000000064130476426150014522gustar00rootroot0000000000000052 comment=c47c456d84ac600d197aec2b303d7859d5455536 pllua-1.1.0/000077500000000000000000000000001304764261500126365ustar00rootroot00000000000000pllua-1.1.0/.travis.yml000066400000000000000000000050571304764261500147560ustar00rootroot00000000000000before_install: - psql --version - sudo /etc/init.d/postgresql stop - sudo apt-get -y --purge remove postgresql libpq-dev libpq5 postgresql-client-common postgresql-common - sudo rm -rf /var/lib/postgresql - wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - - sudo sh -c "echo deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main $PGVERSION >> /etc/apt/sources.list.d/postgresql.list" - sudo apt-get update -qq - sudo apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::="--force-confnew" install postgresql-$PGVERSION postgresql-server-dev-$PGVERSION - sudo chmod 777 /etc/postgresql/$PGVERSION/main/pg_hba.conf - sudo echo "local all postgres trust" > /etc/postgresql/$PGVERSION/main/pg_hba.conf - sudo echo "local all all trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf - sudo echo "host all all 127.0.0.1/32 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf - sudo echo "host all all ::1/128 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf - sudo /etc/init.d/postgresql restart - | if [[ "$LUA" == "lua5.3" ]]; then wget http://www.lua.org/ftp/lua-5.3.2.tar.gz -O lua.tar.gz tar -xvzf lua.tar.gz cd lua-* (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 sudo make INSTALL_TOP=/usr/ INSTALL_INC=${LUA_INCDIR} TO_LIB=liblua5.3.so linux install || exit 1 cd .. else sudo apt-get install $LUA sudo apt-get install $LUA_DEV fi before_script: - createuser -U postgres -s travis env: matrix: - PGVERSION=9.4 LUA=lua5.1 LUA_DEV=liblua5.1-dev LUA_INCDIR=/usr/include/lua5.1 LUALIB=-llua5.1 - PGVERSION=9.4 LUA=lua5.3 LUA_DEV=liblua5.3-dev LUA_INCDIR=/usr/include/lua5.3 LUALIB=-llua5.3 - PGVERSION=9.4 LUA=luajit LUA_DEV=libluajit-5.1-dev LUA_INCDIR=/usr/include/luajit-2.0 LUALIB=-lluajit-5.1 - PGVERSION=9.5 LUA=lua5.1 LUA_DEV=liblua5.1-dev LUA_INCDIR=/usr/include/lua5.1 LUALIB=-llua5.1 - PGVERSION=9.5 LUA=lua5.3 LUA_DEV=liblua5.3-dev LUA_INCDIR=/usr/include/lua5.3 LUALIB=-llua5.3 - PGVERSION=9.5 LUA=luajit LUA_DEV=libluajit-5.1-dev LUA_INCDIR=/usr/include/luajit-2.0 LUALIB=-lluajit-5.1 language: c compiler: - gcc script: - make && sudo make install && make installcheck after_script: - cat regression.diffs || true pllua-1.1.0/Makefile000066400000000000000000000020171304764261500142760ustar00rootroot00000000000000# Makefile for PL/Lua PG_CONFIG ?= pg_config PKG_LIBDIR := $(shell $(PG_CONFIG) --pkglibdir) # Lua specific # General LUA_INCDIR ?= /usr/include/lua5.1 LUALIB ?= -L/usr/local/lib -llua5.1 # LuaJIT #LUA_INCDIR = /usr/local/include/luajit-2.0 #LUALIB = -L/usr/local/lib -lluajit-5.1 # Debian/Ubuntu #LUA_INCDIR = /usr/include/lua5.1 #LUALIB = -llua5.1 # Fink #LUA_INCDIR = /sw/include -I/sw/include/postgresql #LUALIB = -L/sw/lib -llua # Lua for Windows #LUA_INCDIR = C:/PROGRA~1/Lua/5.1/include #LUALIB = -LC:/PROGRA~1/Lua/5.1/lib -llua5.1 # no need to edit below here MODULE_big = pllua EXTENSION = pllua DATA = pllua--1.0.sql #DATA_built = pllua.sql REGRESS = \ plluatest \ biginttest \ pgfunctest \ subtransaction \ error_info OBJS = \ pllua.o \ pllua_debug.o \ pllua_xact_cleanup.o \ plluaapi.o \ plluaspi.o \ lua_int64.o \ rtupdesc.o \ rtupdescstk.o \ pllua_pgfunc.o \ pllua_subxact.o \ pllua_errors.o PG_CPPFLAGS = -I$(LUA_INCDIR) #-DPLLUA_DEBUG SHLIB_LINK = $(LUALIB) PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) pllua-1.1.0/README.md000066400000000000000000000647071304764261500141330ustar00rootroot00000000000000pllua [![Build Status](https://travis-ci.org/pllua/pllua.svg)](https://travis-ci.org/pllua/pllua) ===== 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. ## Introduction ### What PL/Lua is **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. 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. 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: ``` # CREATE FUNCTION hello(name text) RETURNS text AS $$ return string.format("Hello, %s!", name) $$ LANGUAGE pllua; CREATE FUNCTION # SELECT hello('PostgreSQL'); hello -------------------- Hello, PostgreSQL! (1 row) ``` 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. 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. ## Languages Trusted and Untrusted PL/Lua 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. 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: | | `plluau` | `pllua` | | ----- | ------ | ------- | | `table, string, math` | All functions | All functions | | `os` | All functions | `date, clock, time, difftime` | | `package` (module system) | All functions | None | | `_G` (global environment) | Free access | Writing is restricted | 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. 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). 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. ##### `log(msg)` 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. ##### `setshared(varname [, value])` Sets global `varname` to `value`, which defaults to `true`. It is semantically equivalent to `shared[varname] = value`. ## Types Data values in PL/Lua 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: | PostgreSQL type | Lua type | | ----- | ----- | | `bool` | `boolean` | | `float4, float8, int2, int4` | `number` | | `text, char, varchar` | `string` | | Base, domain | `userdata` | | Arrays, composite | `table` | 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. ##### `fromstring(tname, s)` Returns a raw datum userdata for `s` of type `tname` using `tname`'s input function to convert `s`. ## Functions Functions in PL/Lua PL/Lua functions are created according to the following prototype: ```lua CREATE FUNCTION func(args) RETURNS rettype AS $$ -- Lua function body $$ LANGUAGE [pllua | plluau]; ``` where `args` are usually _named_ arguments. The value returned by `func` is converted to a datum of type `rettype` unless `rettype` is `void`. The function body is composed as below to become a typical Lua chunk: ```lua local _U, func -- _U is upvalue func = function(_argnames_) -- Lua function body end return func ``` Note the _upvalue_ `_U` that can be later declared in the function body (see examples below.) If any of the arguments provided to `create function` is not named then `argnames` gets substituted to `...`, that is, `func` becomes _vararg_. 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: ```lua CREATE FUNCTION max(a integer, b integer) RETURNS integer AS $$ if a == nil then return b end -- first arg is NULL? if b == nil then return a end -- second arg is NULL? return a > b and a or b -- return max(a, b) $$ LANGUAGE pllua; ``` Note that `max` is not strict and returns `NULL` when both `a` and `b` are `NULL`. Since functions in PL/Lua are stored with their declared names, they can be recursive: ```lua CREATE FUNCTION fib(n int) RETURNS int as $$ if n > 3 then return n else return fib(n - 1) + fib(n - 2) end $$ LANGUAGE pllua; ``` 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: ```lua CREATE FUNCTION counter() RETURNS int AS $$ while true do _U = _U + 1 coroutine.yield(_U) end end do _U = 0 -- counter counter = coroutine.wrap(counter) $$ LANGUAGE pllua; ``` 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. From [Types](#types) we know that composite types can be accessed as tables with keys corresponding to attribute names: ```lua CREATE TYPE greeting AS (how text, who text); CREATE FUNCTION makegreeting (g greeting, f text) RETURNS text AS $$ return string.format(f, g.how, g.who) $$ LANGUAGE pllua; ``` 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 ```lua CREATE FUNCTION greetingset (how text, who text[]) RETURNS SETOF greeting AS $$ for _, name in ipairs(who) do coroutine.yield{how=how, who=name} end $$ LANGUAGE pllua; ``` with this usage example: ```sql # SELECT makegreeting(greetingset, '%s, %s!') FROM (SELECT greetingset('Hello', ARRAY['foo', 'bar', 'psql'])) AS q; makegreeting -------------- Hello, foo! Hello, bar! Hello, psql! (3 rows) ``` Now, to further illustrate the use of arrays in PL/Lua, we adapt an [example][15] from _[Programming in Lua_][16]: ```lua CREATE FUNCTION perm (a text[]) RETURNS SETOF text[] AS $$ _U(a, #a) end do _U = function (a, n) -- permgen in PiL if n == 0 then coroutine.yield(a) -- return next SRF row else for i = 1, n do a[n], a[i] = a[i], a[n] -- i-th element as last one _U(a, n - 1) -- recurse on head a[n], a[i] = a[i], a[n] -- restore i-th element end end end $$ LANGUAGE pllua; ``` 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`: ```lua CREATE FUNCTION getcounter() RETURNS integer AS $$ if shared.counter == nil then -- not cached? setshared("counter", 0) end return counter -- _G.counter == shared.counter $$ LANGUAGE pllua; CREATE FUNCTION setcounter(c integer) RETURNS void AS $$ if shared.counter == nil then -- not cached? setshared("counter", c) else counter = c -- _G.counter == shared.counter end $$ LANGUAGE pllua; ``` ### Examples Let's revisit our (rather inefficient) recursive Fibonacci function `fib`. A better version uses _tail recursion_: ```lua CREATE FUNCTION fibt(n integer) RETURNS integer AS $$ return _U(n, 0, 1) end _U = function(n, a, b) -- tail recursive if n > 1 then return b else return _U(n - 1, b, a + b) end $$ LANGUAGE pllua; ``` We can also use the upvalue `_U` as a cache to store previous elements in the sequence and obtain a _memoized_ version: ```lua CREATE FUNCTION fibm(n integer) RETURNS integer AS $$ if n > 3 then return n else local v = _U[n] if not v then v = fibm(n - 1) + fibm(n - 2) _U[n] = v end return v end end do _U = {} -- memoize $$ LANGUAGE pllua; ``` Finally, we can implement an iterator similar to `counter`: ```lua CREATE FUNCTION fibi() RETURNS integer AS $$ while true do _U.curr, _U.next = _U.next, _U.curr + _U.next coroutine.yield(_U.curr) end end do _U = {curr = 0, next = 1} fibi = coroutine.wrap(fibi) $$ LANGUAGE pllua; ``` ### Anonymous blocks Anonymous code blocks are also supported in PL/Lua. The following prototype ```lua DO $$ -- Lua chunk $$ LANGUAGE [pllua | plluau]; ``` compiles and executes the Lua chunk. Here are some examples: ```lua DO $$ print(_VERSION) $$ LANGUAGE pllua; DO $$ local ffi = assert(require("ffi")); -- LuaJIT ffi.cdef[[ double lgamma (double); ]] mathx = ffi.load("m") $$ LANGUAGE plluau; -- note: untrusted due to "require" CREATE FUNCTION lfactorial (n int) RETURNS double precision AS $$ return mathx.lgamma(n + 1) $$ LANGUAGE plluau; ``` ## Database Access Server interface in PL/Lua 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). 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. ##### `server.execute(cmd, readonly [, count])` 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_. ##### `server.rows(cmd)` Returns a function so that the construction ```lua for row in server.rows(cmd) do -- body end ``` iterates over the _tuples_ in the _read-only_ SQL statement `cmd`. ##### `server.prepare(cmd, argtypes)` 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. ##### `server.find(name)` Finds an existing cursor with name `name` and returns a cursor userdatum or `nil` if the cursor cannot be found. ### Plans _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. ##### `plan:execute(args, readonly [, count])` Executes a previously prepared plan with parameters in table `args`. `readonly` and `count` have the same meaning as in [server.execute](#serverexecutecmd-readonly--count). ##### `plan:getcursor(args, readonly [, name])` 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). ##### `plan:rows(args)` Returns a function so that the construction ```lua for row in plan:rows(args) do -- body end ``` iterates over the _tuples_ in the execution of a previously prepared _read-only_ plan with parameters in table `args`. It is semantically equivalent to: ```lua function plan:rows (cmd) local c = self:getcursor(nil, true) -- read-only return function() local r = c:fetch(1) if r == nil then c:close() return nil else return r[1] end end end ``` ##### `plan:issaved()` Returns `true` if plan is saved and `false` otherwise. ##### `plan:save()` Saves a prepared plan for subsequent invocations in the current session. ### Cursors _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. ##### `cursor:fetch([count])` 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. ##### `cursor:move([count])` Skips `count` rows in a cursor, where `count` defaults to zero. If `count` is negative the moving runs backward. ##### `cursor:close()` Closes a cursor. ### Examples Let's start with a simple example using cursors: ```sql CREATE TABLE sometable ( sid int, sname text, sdata text); ``` ```lua CREATE FUNCTION get_rows (i_name text) RETURNS SETOF sometable AS $$ if _U == nil then -- plan not cached? local cmd = "SELECT sid, sname, sdata FROM sometable WHERE sname=$1" _U = server.prepare(cmd, {"text"}):save() end local c = _U:getcursor({i_name}, true) -- read-only while true do local r = c:fetch(1) if r == nil then break end r = r[1] coroutine.yield{sid=r.sid, sname=r.sname, sdata=r.sdata} end c:close() $$ LANGUAGE pllua; ``` 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]`. A more concise version uses `plan:rows()`: ```lua CREATE FUNCTION get_rows (i_name text) RETURNS SETOF sometable AS $$ if _U == nil then -- plan not cached? local cmd = "SELECT sid, sname, sdata FROM sometable WHERE sname=$1" _U = server.prepare(cmd, {"text"}):save() end for r in _U:rows{i_name} do coroutine.yield(r) -- yield tuple end $$ LANGUAGE pllua; ``` Now, for a more elaborate example, let's store a binary tree: ```sql CREATE TABLE tree (id int PRIMARY KEY, lchild int, rchild int); ``` which we can fill using: ```lua CREATE FUNCTION filltree (t text, n int) RETURNS void AS $$ local p = server.prepare("insert into " .. t .. " values($1, $2, $3)", {"int4", "int4", "int4"}) for i = 1, n do local lchild, rchild = 2 * i, 2 * i + 1 -- siblings p:execute{i, lchild, rchild} -- insert values end $$ LANGUAGE pllua; ``` Local variable `p` stores a prepared plan for insertion with three parameters as values, while the actual insertion is executed in the loop. We can perform a preorder traversal of the tree with: ```lua CREATE FUNCTION preorder (t text, s int) RETURNS SETOF int AS $$ coroutine.yield(s) local q = server.execute("select * from " .. t .. " where id=" .. s, true, 1) -- read-only, only 1 result if q ~= nil then local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query if lchild ~= nil then preorder(t, lchild) end if rchild ~= nil then preorder(t, rchild) end end $$ LANGUAGE pllua; ``` 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. 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. ```lua CREATE FUNCTION postorder (t text, s int) RETURNS SETOF int AS $$ local p = _U[t] if p == nil then -- plan not cached? p = server.prepare("select * from " .. t .. " where id=$1", {"int4"}) _U[t] = p:save() end local c = p:getcursor({s}, true) -- read-only local q = c:fetch(1) -- one row if q ~= nil then local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query c:close() if lchild ~= nil then postorder(t, lchild) end if rchild ~= nil then postorder(t, rchild) end end coroutine.yield(s) end do _U = {} -- plan cache $$ LANGUAGE pllua; ``` ## Triggers Triggers in PL/Lua 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. | Key | Value | | ----- | ----- | | `name` | trigger name | | `when` | `"before"` if trigger fired before or `"after"` if trigger fired after | | `level` | `"row"` if row-level trigger or `"statement"` if statement-level trigger | | `operation` | `"insert"`, `"update"`, `"delete"`, or `"truncate"` depending on trigger operation | | `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 | | `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. | | `old` | Tuple representing the old row in an update before row-level operation. | Example content of a `trigger` table after an update operation : ```lua trigger = { ["old"] = "tuple: 0xd084d8", ["name"] = "trigger_name", ["when"] = "after", ["operation"] = "update", ["level"] = "row", ["row"] = "tuple: 0xd244f8", ["relation"] = { ["namespace"] = "public", ["attributes"] = { ["test_column"] = 0, }, ["name"] = "table_name", ["oid"] = 59059 } } ``` 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. ### Example 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. ```lua create function treetrigger() returns trigger as $$ local row, operation = trigger.row, trigger.operation if operation == "update" then trigger.row = nil -- updates not allowed elseif operation == "insert" then local id, lchild, rchild = row.id, row.lchild, row.rchild if lchild == rchild or id == lchild or id == rchild -- avoid loops or (lchild ~= nil and _U.intree(lchild)) -- avoid cycles or (rchild ~= nil and _U.intree(rchild)) or (_U.nonemptytree() and not _U.isleaf(id)) -- not leaf? then trigger.row = nil -- skip operation end else -- operation == "delete" if not _U.isleafparent(row.id) then -- not both leaf parent? trigger.row = nil end end end do local getter = function(cmd, ...) local plan = server.prepare(cmd, {...}):save() return function(...) return plan:execute({...}, true) ~= nil end end _U = { -- plan closures nonemptytree = getter("select * from tree"), intree = getter("select node from (select id as node from tree " .. "union select lchild from tree union select rchild from tree) as q " .. "where node=$1", "int4"), isleaf = getter("select leaf from (select lchild as leaf from tree " .. "union select rchild from tree except select id from tree) as q " .. "where leaf=$1", "int4"), isleafparent = getter("select lp from (select id as lp from tree " .. "except select ti.id from tree ti join tree tl on ti.lchild=tl.id " .. "join tree tr on ti.rchild=tr.id) as q where lp=$1", "int4") } $$ language pllua; ``` Finally, we set the trigger on table `tree`: ```sql create trigger tree_trigger before insert or update or delete on tree for each row execute procedure treetrigger(); ``` ## Installation How to obtain and install PL/Lua 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: ``` $ export PG_CONFIG='/usr/pgsql-9.4/bin/pg_config' # specifiy where pg_config is located $ make && make install $ psql -c "CREATE EXTENSION pllua" mydb ``` 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: ```sql INSERT INTO pg_catalog.pg_pltemplate VALUES ('pllua', true, 'pllua_call_handler', 'pllua_validator', '$libdir/pllua', NULL); INSERT INTO pg_catalog.pg_pltemplate VALUES ('plluau', false, 'plluau_call_handler', 'plluau_validator', '$libdir/pllua', NULL); ``` ### License Copyright (c) 2008 Luis Carvalho 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. [8]: http://www.lua.org [9]: http://www.postgresql.org [11]: http://www.lua.org/license.html [12]: http://www.opensource.org/licenses/mit-license.php [15]: http://www.lua.org/pil/9.3.html [16]: http://www.lua.org/pil [22]: http://pgfoundry.org/projects/pllua pllua-1.1.0/expected/000077500000000000000000000000001304764261500144375ustar00rootroot00000000000000pllua-1.1.0/expected/biginttest.out000066400000000000000000000003411304764261500173420ustar00rootroot00000000000000CREATE FUNCTION int64_minus_one(value bigint) RETURNS bigint AS $$ return value - 1; $$ LANGUAGE pllua; select int64_minus_one(9223372036854775807); int64_minus_one --------------------- 9223372036854775806 (1 row) pllua-1.1.0/expected/error_info.out000066400000000000000000000061571304764261500173450ustar00rootroot00000000000000do $$ local testfunc = function () error("my error") end local f = function() local status, err = pcall(testfunc) if (err) then error(err) end end f() $$language pllua; ERROR: my error CONTEXT: stack traceback(trusted): [C]: in function 'error' [string "anonymous"]:6: in function 'f' [string "anonymous"]:9: in main chunk create or replace function pg_temp.function_with_error() returns integer as $$ local testfunc = function () error("my error") end local f = function() local status, err = pcall(testfunc) if (err) then error(err) end end f() $$language plluau; create or replace function pg_temp.second_function() returns void as $$ local k = server.execute('select pg_temp.function_with_error()') [0] $$language plluau; do $$ server.execute('select pg_temp.second_function()') $$language pllua; ERROR: my error CONTEXT: stack traceback(untrusted): [C]: in function 'error' [string "function_with_error"]:6: in function 'f' [string "function_with_error"]:9: in function <[string "function_with_error"]:1> [C]: in function 'execute' [string "second_function"]:2: in function <[string "second_function"]:1> SQL statement "select pg_temp.function_with_error()" SQL statement "select pg_temp.second_function()" SQL statement "select pg_temp.second_function()" stack traceback(trusted): [C]: in function 'execute' [string "anonymous"]:2: in main chunk do $$ local status, err = subtransaction(function() assert(1==2) end) if (err) then error(err) end $$language pllua; ERROR: assertion failed! CONTEXT: stack traceback(trusted): [C]: in function 'error' [string "anonymous"]:4: in main chunk do $$ info({message="info message", hint="info hint", detail="info detail"}) $$language pllua; INFO: info message DETAIL: info detail HINT: info hint do $$ info("info message") $$language pllua; INFO: info message do $$ warning({message="warning message", hint="warning hint", detail="warning detail"}) $$language pllua; WARNING: warning message DETAIL: warning detail HINT: warning hint do $$ warning("warning message") $$language pllua; WARNING: warning message do $$ error({message="error message", hint="error hint", detail="error detail"}) $$language pllua; ERROR: error message DETAIL: error detail HINT: error hint CONTEXT: stack traceback(trusted): [C]: in function 'error' [string "anonymous"]:2: in main chunk do $$ error("error message") $$language pllua; ERROR: error message CONTEXT: stack traceback(trusted): [C]: in function 'error' [string "anonymous"]:2: in main chunk do $$ info() $$language pllua; ERROR: [string "anonymous"]:2: bad argument #1 to 'info' (string expected, got no value) CONTEXT: stack traceback(trusted): [C]: in function 'info' [string "anonymous"]:2: in main chunk do $$ warning() $$language pllua; ERROR: [string "anonymous"]:2: bad argument #1 to 'warning' (string expected, got no value) CONTEXT: stack traceback(trusted): [C]: in function 'warning' [string "anonymous"]:2: in main chunk do $$ error() $$language pllua; ERROR: [string "anonymous"]:2: no exception data CONTEXT: stack traceback(trusted): [C]: in function 'error' [string "anonymous"]:2: in main chunk pllua-1.1.0/expected/pgfunctest.out000066400000000000000000000107751304764261500173640ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION i_void(internal) RETURNS internal AS $BODY$ -- mymodule.lua local M = {} -- public interface -- private local x = 1 local function baz() print 'test' end function M.foo() print("foo", x) end function M.bar() M.foo() baz() print "bar" end function M.getAnswer() return 42 end return M $BODY$ LANGUAGE pllua; CREATE OR REPLACE FUNCTION pg_temp.pgfunc_test() RETURNS SETOF text AS $$ local quote_ident = pgfunc("quote_ident(text)") coroutine.yield(quote_ident("int")) local right = pgfunc("right(text,int)") coroutine.yield(right('abcde', 2)) local factorial = pgfunc("factorial(int8)") coroutine.yield(tostring(factorial(50))) local i_void = pgfunc("i_void(internal)") coroutine.yield(i_void.getAnswer()) $$ LANGUAGE pllua; select pg_temp.pgfunc_test(); pgfunc_test ------------------------------------------------------------------- "int" de 30414093201713378043612608166064768844377641568960512000000000000 42 (4 rows) do $$ print(pgfunc('quote_nullable(text)')(nil)) $$ language pllua; INFO: NULL create or replace function pg_temp.throw_error(text) returns void as $$ begin raise exception '%', $1; end $$ language plpgsql; do $$ pgfunc('pg_temp.throw_error(text)',{only_internal=false})("exception test") $$ language pllua; ERROR: exception test CONTEXT: stack traceback(trusted): [C]: ? [string "anonymous"]:2: in main chunk do $$ local f = pgfunc('pg_temp.throw_error(text)',{only_internal=false}) print(pcall(f, "exception test")) $$ language pllua; INFO: false exception test create or replace function pg_temp.no_throw() returns jsonb as $$ select '{"a":5, "b":10}'::jsonb $$ language sql; do $$ local f = pgfunc('pg_temp.no_throw()',{only_internal=false, throwable=false}) print(f()) $$ language pllua; INFO: {"a": 5, "b": 10} CREATE or replace FUNCTION pg_temp.arg_count(a1 integer,a2 integer,a3 integer,a4 integer,a5 integer ,a6 integer,a7 integer,a8 integer,a9 integer,a10 integer ,a11 integer,a12 integer,a13 integer,a14 integer,a15 integer ) returns integer AS $$ begin return a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12+a13+a14+a15; end $$ LANGUAGE plpgsql; do $$ local f = pgfunc([[pg_temp.arg_count(integer, integer, integer, integer, integer, integer, integer, integer, integer, integer, integer, integer, integer, integer, integer ) ]],{only_internal=false}); print(f(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)) $$ language pllua; INFO: 120 CREATE or replace FUNCTION pg_temp.inoutf(a integer, INOUT b text, INOUT c text) AS $$ begin c = a||'c:'||c; b = 'b:'||b; end $$ LANGUAGE plpgsql; do $$ local f = pgfunc('pg_temp.inoutf(integer,text,text)',{only_internal=false}); local r = f(5, 'ABC', 'd') print(r.b) print(r.c) $$ language pllua; INFO: b:ABC INFO: 5c:d do $$ local f = pgfunc('generate_series(int,int)') print('4-6') for rr in f(4,6) do print(rr) end print('1-3') for rr in f(1,3) do print(rr) end $$ language pllua; INFO: 4-6 INFO: 4 INFO: 5 INFO: 6 INFO: 1-3 INFO: 1 INFO: 2 INFO: 3 do $$ local f = pgfunc('generate_series(int,int)') for rr in f(1,3) do for rr in f(41,43) do print(rr) end print(rr) end $$ language pllua; INFO: 41 INFO: 42 INFO: 43 INFO: 1 INFO: 41 INFO: 42 INFO: 43 INFO: 2 INFO: 41 INFO: 42 INFO: 43 INFO: 3 -- Type wrapper create extension hstore; do $$ local hstore = { fromstring = function(text) return fromstring('hstore',text) end, akeys = pgfunc('akeys(hstore)',{only_internal = false}), each = pgfunc('each(hstore)',{only_internal = false}) --orig:each(IN hs hstore, OUT key text, OUT value text) } local v = hstore.fromstring[[ "paperback" => "542", "publisher" => "postgresql.org", "language" => "English", "ISBN-13" => "978-0000000000", "weight" => "24.1 ounces" ]] print(v) for _,v in ipairs(hstore.akeys(v)) do print (v) end for hv in hstore.each(v) do print ("key = " .. hv.key .. " value = "..hv.value) end $$ language pllua; INFO: "weight"=>"24.1 ounces", "ISBN-13"=>"978-0000000000", "language"=>"English", "paperback"=>"542", "publisher"=>"postgresql.org" INFO: weight INFO: ISBN-13 INFO: language INFO: paperback INFO: publisher INFO: key = weight value = 24.1 ounces INFO: key = ISBN-13 value = 978-0000000000 INFO: key = language value = English INFO: key = paperback value = 542 INFO: key = publisher value = postgresql.org create or replace function getnull() returns text as $$ begin return null; end $$ language plpgsql; do $$ local a = pgfunc('getnull()',{only_internal = false}) print(a()) $$ language pllua; INFO: nil pllua-1.1.0/expected/plluatest.out000066400000000000000000000316621304764261500172150ustar00rootroot00000000000000\set ECHO none -- minimal function CREATE FUNCTION hello(name text) RETURNS text AS $$ return string.format("Hello, %s!", name) $$ LANGUAGE pllua; SELECT hello('PostgreSQL'); hello -------------------- Hello, PostgreSQL! (1 row) -- null handling CREATE FUNCTION max(a integer, b integer) RETURNS integer AS $$ if a == nil then return b end -- first arg is NULL? if b == nil then return a end -- second arg is NULL? return a > b and a or b -- return max(a, b) $$ LANGUAGE pllua; SELECT max(1,2), max(2,1), max(2,null), max(null, 2), max(null, null); max | max | max | max | max -----+-----+-----+-----+----- 2 | 2 | 2 | 2 | (1 row) -- plain recursive CREATE FUNCTION fib(n int) RETURNS int AS $$ if n < 3 then return n else return fib(n - 1) + fib(n - 2) end $$ LANGUAGE pllua; SELECT fib(4); fib ----- 5 (1 row) -- memoized CREATE FUNCTION fibm(n integer) RETURNS integer AS $$ if n < 3 then return n else local v = _U[n] if not v then v = fibm(n - 1) + fibm(n - 2) _U[n] = v end return v end end do _U = {} $$ LANGUAGE pllua; SELECT fibm(4); fibm ------ 5 (1 row) -- tail recursive CREATE FUNCTION fibt(n integer) RETURNS integer AS $$ return _U(n, 0, 1) end _U = function(n, a, b) if n < 1 then return b else return _U(n - 1, b, a + b) end $$ LANGUAGE pllua; SELECT fibt(4); fibt ------ 5 (1 row) -- iterator CREATE FUNCTION fibi() RETURNS integer AS $$ while true do _U.curr, _U.next = _U.next, _U.curr + _U.next coroutine.yield(_U.curr) end end do _U = {curr = 0, next = 1} fibi = coroutine.wrap(fibi) $$ LANGUAGE pllua; SELECT fibi(), fibi(), fibi(), fibi(), fibi(); fibi | fibi | fibi | fibi | fibi ------+------+------+------+------ 1 | 1 | 2 | 3 | 5 (1 row) SELECT fibi(), fibi(), fibi(), fibi(), fibi(); fibi | fibi | fibi | fibi | fibi ------+------+------+------+------ 8 | 13 | 21 | 34 | 55 (1 row) -- upvalue CREATE FUNCTION counter() RETURNS int AS $$ while true do _U = _U + 1 coroutine.yield(_U) end end do _U = 0 -- counter counter = coroutine.wrap(counter) $$ LANGUAGE pllua; SELECT counter(); counter --------- 1 (1 row) SELECT counter(); counter --------- 2 (1 row) SELECT counter(); counter --------- 3 (1 row) -- record input CREATE TYPE greeting AS (how text, who text); CREATE FUNCTION makegreeting (g greeting, f text) RETURNS text AS $$ return string.format(f, g.how, g.who) $$ LANGUAGE pllua; SELECT makegreeting(('how', 'who'), '%s, %s!'); makegreeting -------------- how, who! (1 row) -- array, record output CREATE FUNCTION greetingset (how text, who text[]) RETURNS SETOF greeting AS $$ for _, name in ipairs(who) do coroutine.yield{how=how, who=name} end $$ LANGUAGE pllua; SELECT makegreeting(greetingset, '%s, %s!') FROM (SELECT greetingset('Hello', ARRAY['foo', 'bar', 'psql'])) AS q; makegreeting -------------- Hello, foo! Hello, bar! Hello, psql! (3 rows) -- more array, upvalue CREATE FUNCTION perm (a text[]) RETURNS SETOF text[] AS $$ _U(a, #a) end do _U = function (a, n) -- permgen in PiL if n == 0 then coroutine.yield(a) -- return next SRF row else for i = 1, n do a[n], a[i] = a[i], a[n] -- i-th element as last one _U(a, n - 1) -- recurse on head a[n], a[i] = a[i], a[n] -- restore i-th element end end end $$ LANGUAGE pllua; SELECT * FROM perm(array['1', '2', '3']); perm --------- {2,3,1} {3,2,1} {3,1,2} {1,3,2} {2,1,3} {1,2,3} (6 rows) -- shared variables CREATE FUNCTION getcounter() RETURNS integer AS $$ if shared.counter == nil then -- not cached? setshared("counter", 0) end return counter -- _G.counter == shared.counter $$ LANGUAGE pllua; CREATE FUNCTION setcounter(c integer) RETURNS void AS $$ if shared.counter == nil then -- not cached? setshared("counter", c) else counter = c -- _G.counter == shared.counter end $$ LANGUAGE pllua; SELECT getcounter(); getcounter ------------ 0 (1 row) SELECT setcounter(5); setcounter ------------ (1 row) SELECT getcounter(); getcounter ------------ 5 (1 row) -- SPI usage CREATE TABLE sometable ( sid int4, sname text, sdata text); INSERT INTO sometable VALUES (1, 'name', 'data'); CREATE FUNCTION get_rows (i_name text) RETURNS SETOF sometable AS $$ if _U == nil then -- plan not cached? local cmd = "SELECT sid, sname, sdata FROM sometable WHERE sname = $1" _U = server.prepare(cmd, {"text"}):save() end local c = _U:getcursor({i_name}, true) -- read-only while true do local r = c:fetch(1) if r == nil then break end r = r[1] coroutine.yield{sid=r.sid, sname=r.sname, sdata=r.sdata} end c:close() $$ LANGUAGE pllua; SELECT * FROM get_rows('name'); sid | sname | sdata -----+-------+------- 1 | name | data (1 row) CREATE TABLE tree (id INT PRIMARY KEY, lchild INT, rchild INT); CREATE FUNCTION filltree (t text, n int) RETURNS void AS $$ local p = server.prepare("insert into " .. t .. " values($1, $2, $3)", {"int4", "int4", "int4"}) for i = 1, n do local lchild, rchild = 2 * i, 2 * i + 1 -- siblings p:execute{i, lchild, rchild} -- insert values end $$ LANGUAGE pllua; SELECT filltree('tree', 10); filltree ---------- (1 row) CREATE FUNCTION preorder (t text, s int) RETURNS SETOF int AS $$ coroutine.yield(s) local q = server.execute("select * from " .. t .. " where id=" .. s, true, 1) -- read-only, only 1 result if q ~= nil then local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query if lchild ~= nil then preorder(t, lchild) end if rchild ~= nil then preorder(t, rchild) end end $$ LANGUAGE pllua; SELECT * from preorder('tree', 1); preorder ---------- 1 2 4 8 16 17 9 18 19 5 10 20 21 11 3 6 12 13 7 14 15 (21 rows) CREATE FUNCTION postorder (t text, s int) RETURNS SETOF int AS $$ local p = _U[t] if p == nil then -- plan not cached? p = server.prepare("select * from " .. t .. " where id=$1", {"int4"}) _U[t] = p:save() end local c = p:getcursor({s}, true) -- read-only local q = c:fetch(1) -- one row if q ~= nil then local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query c:close() if lchild ~= nil then postorder(t, lchild) end if rchild ~= nil then postorder(t, rchild) end end coroutine.yield(s) end do _U = {} -- plan cache $$ LANGUAGE pllua; SELECT * FROM postorder('tree', 1); postorder ----------- 16 17 8 18 19 9 4 20 21 10 11 5 2 12 13 6 14 15 7 3 1 (21 rows) -- trigger CREATE FUNCTION treetrigger() RETURNS trigger AS $$ local row, operation = trigger.row, trigger.operation if operation == "update" then trigger.row = nil -- updates not allowed elseif operation == "insert" then local id, lchild, rchild = row.id, row.lchild, row.rchild if lchild == rchild or id == lchild or id == rchild -- avoid loops or (lchild ~= nil and _U.intree(lchild)) -- avoid cycles or (rchild ~= nil and _U.intree(rchild)) or (_U.nonemptytree() and not _U.isleaf(id)) -- not leaf? then trigger.row = nil -- skip operation end else -- operation == "delete" if not _U.isleafparent(row.id) then -- not both leaf parent? trigger.row = nil end end end do local getter = function(cmd, ...) local plan = server.prepare(cmd, {...}):save() return function(...) return plan:execute({...}, true) ~= nil end end _U = { -- plan closures nonemptytree = getter("select * from tree"), intree = getter("select node from (select id as node from tree " .. "union select lchild from tree union select rchild from tree) as q " .. "where node=$1", "int4"), isleaf = getter("select leaf from (select lchild as leaf from tree " .. "union select rchild from tree except select id from tree) as q " .. "where leaf=$1", "int4"), isleafparent = getter("select lp from (select id as lp from tree " .. "except select ti.id from tree ti join tree tl on ti.lchild=tl.id " .. "join tree tr on ti.rchild=tr.id) as q where lp=$1", "int4") } $$ LANGUAGE pllua; CREATE TRIGGER tree_trigger BEFORE INSERT OR UPDATE OR DELETE ON tree FOR EACH ROW EXECUTE PROCEDURE treetrigger(); SELECT * FROM tree WHERE id = 1; id | lchild | rchild ----+--------+-------- 1 | 2 | 3 (1 row) UPDATE tree SET rchild = 1 WHERE id = 1; SELECT * FROM tree WHERE id = 10; id | lchild | rchild ----+--------+-------- 10 | 20 | 21 (1 row) DELETE FROM tree where id = 10; DELETE FROM tree where id = 1; -- passthru types CREATE FUNCTION echo_int2(arg int2) RETURNS int2 AS $$ return arg $$ LANGUAGE pllua; SELECT echo_int2('12345'); echo_int2 ----------- 12345 (1 row) CREATE FUNCTION echo_int4(arg int4) RETURNS int4 AS $$ return arg $$ LANGUAGE pllua; SELECT echo_int4('1234567890'); echo_int4 ------------ 1234567890 (1 row) CREATE FUNCTION echo_int8(arg int8) RETURNS int8 AS $$ return arg $$ LANGUAGE pllua; SELECT echo_int8('1234567890'); echo_int8 ------------ 1234567890 (1 row) SELECT echo_int8('12345678901236789'); echo_int8 ------------------- 12345678901236789 (1 row) SELECT echo_int8('1234567890123456789'); echo_int8 --------------------- 1234567890123456789 (1 row) CREATE FUNCTION echo_text(arg text) RETURNS text AS $$ return arg $$ LANGUAGE pllua; SELECT echo_text('qwe''qwe'); echo_text ----------- qwe'qwe (1 row) CREATE FUNCTION echo_bytea(arg bytea) RETURNS bytea AS $$ return arg $$ LANGUAGE pllua; SELECT echo_bytea('qwe''qwe'); echo_bytea ------------------ \x71776527717765 (1 row) SELECT echo_bytea(E'q\\000w\\001e''q\\\\we'); echo_bytea ------------------------ \x710077016527715c7765 (1 row) CREATE FUNCTION echo_timestamptz(arg timestamptz) RETURNS timestamptz AS $$ return arg $$ LANGUAGE pllua; SELECT echo_timestamptz('2007-01-06 11:11 UTC') AT TIME ZONE 'UTC'; timezone -------------------------- Sat Jan 06 11:11:00 2007 (1 row) CREATE FUNCTION echo_timestamp(arg timestamp) RETURNS timestamp AS $$ return arg $$ LANGUAGE pllua; SELECT echo_timestamp('2007-01-06 11:11'); echo_timestamp -------------------------- Sat Jan 06 11:11:00 2007 (1 row) CREATE FUNCTION echo_date(arg date) RETURNS date AS $$ return arg $$ LANGUAGE pllua; SELECT echo_date('2007-01-06'); echo_date ------------ 01-06-2007 (1 row) CREATE FUNCTION echo_time(arg time) RETURNS time AS $$ return arg $$ LANGUAGE pllua; SELECT echo_time('11:11'); echo_time ----------- 11:11:00 (1 row) CREATE FUNCTION echo_arr(arg text[]) RETURNS text[] AS $$ return arg $$ LANGUAGE pllua; SELECT echo_arr(array['a', 'b', 'c']); echo_arr ---------- {a,b,c} (1 row) CREATE DOMAIN mynum AS numeric(6,3); CREATE FUNCTION echo_mynum(arg mynum) RETURNS mynum AS $$ return arg $$ LANGUAGE pllua; SELECT echo_mynum(666.777); echo_mynum ------------ 666.777 (1 row) CREATE TYPE mytype AS (id int2, val mynum, val_list numeric[]); CREATE FUNCTION echo_mytype(arg mytype) RETURNS mytype AS $$ return arg $$ LANGUAGE pllua; SELECT echo_mytype((1::int2, 666.777, array[1.0, 2.0]) ); echo_mytype ------------------------- (1,666.777,"{1.0,2.0}") (1 row) CREATE FUNCTION nested_server_rows () RETURNS SETOF text as $$ for left in server.rows('select generate_series as left from generate_series(3,4) ') do for right in server.rows('select generate_series as right from generate_series(5,6) ') do local s = left.left.." "..right.right coroutine.yield(s) end end $$ language pllua; select nested_server_rows(); nested_server_rows -------------------- 3 5 3 6 4 5 4 6 (4 rows) CREATE OR REPLACE FUNCTION pg_temp.srf() RETURNS SETOF integer AS $$ coroutine.yield(1) coroutine.yield(nil) coroutine.yield(2) $$ LANGUAGE pllua; select quote_nullable(pg_temp.srf()); quote_nullable ---------------- '1' NULL '2' (3 rows) CREATE OR REPLACE FUNCTION pg_temp.srf() RETURNS SETOF integer AS $$ coroutine.yield(1) coroutine.yield() coroutine.yield(2) $$ LANGUAGE pllua; select quote_nullable(pg_temp.srf()); quote_nullable ---------------- '1' (1 row) CREATE or replace FUNCTION pg_temp.inoutf(a integer, INOUT b text, INOUT c text) AS $$ begin c = a||'c:'||c; b = 'b:'||b; end $$ LANGUAGE plpgsql; do $$ local a = server.execute("SELECT pg_temp.inoutf(5, 'ABC', 'd') as val "); local r = a[1].val print(r.b) print(r.c) $$ language pllua; INFO: b:ABC INFO: 5c:d -- body reload SELECT hello('PostgreSQL'); hello -------------------- Hello, PostgreSQL! (1 row) CREATE OR REPLACE FUNCTION hello(name text) RETURNS text AS $$ return string.format("Bye, %s!", name) $$ LANGUAGE pllua; SELECT hello('PostgreSQL'); hello ------------------ Bye, PostgreSQL! (1 row) pllua-1.1.0/expected/subtransaction.out000066400000000000000000000026431304764261500202340ustar00rootroot00000000000000CREATE TABLE accounts ( id bigserial NOT NULL, account_name text, balance integer, CONSTRAINT accounts_pkey PRIMARY KEY (id), CONSTRAINT no_minus CHECK (balance >= 0) ); insert into accounts(account_name, balance) values('joe', 200); insert into accounts(account_name, balance) values('mary', 50); CREATE OR REPLACE FUNCTION pg_temp.sub_test() RETURNS SETOF text AS $$ local f = function() local p = server.prepare("UPDATE accounts SET balance = balance + $2 WHERE account_name = $1", {"text","int4"}) p:execute{'joe', 100} p:execute{'mary',-100} return true end local status, err = subtransaction(f) coroutine.yield(tostring(status)) f = function() local p = server.prepare("UPDATE accounts SET balance = balance + $2 WHERE account_name = $1", {"text","int4"}) p:execute{'joe', -100} p:execute{'mary', 100} return true end status, err = subtransaction(f) coroutine.yield(tostring(status)) $$ LANGUAGE pllua; select pg_temp.sub_test(); sub_test ---------- false true (2 rows) do $$ local status, result = subtransaction(function() server.execute('select 1,'); -- < special SQL syntax error end); print (status, result) status, result = pcall(function() server.execute('select 1,'); -- < special SQL syntax error end); print (status, result) print ('done') $$ language pllua; INFO: false syntax error at end of input INFO: false syntax error at end of input INFO: done pllua-1.1.0/lua_int64.c000066400000000000000000000174701304764261500146200ustar00rootroot00000000000000/** Copyright (c) 2012-2013 codingow.com 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. source:https://github.com/idning/lua-int64.git */ #include "lua_int64.h" #include #include #include #ifndef _MSC_VER #include #include #endif #include "pllua.h" #include "pllua_errors.h" static const char int64_type_name[] = "int64"; static int64_t check_int64(lua_State* L, int idx) {\ int64_t* p; luaL_checktype(L,idx,LUA_TUSERDATA); p = (int64_t*)luaL_checkudata(L, idx, int64_type_name); return p ? *p : 0; } #define get_ab_values \ int64_t a;\ int64_t b;\ if(lua_isnil(L,1) || lua_isnil(L,2)) \ return luaL_error(L, "attempt to perform arithmetic on a nil value"); \ a = get_int64(L,1); \ b = get_int64(L,2) static int64_t get_int64(lua_State *L, int index) { int type = lua_type(L,index); int64_t value = 0; switch(type) { case LUA_TNUMBER: { return (int64_t)(luaL_checknumber(L,index)); } case LUA_TSTRING: { #ifdef _MSC_VER return(int64_t)_strtoi64(lua_tostring(L, index), NULL, 0); #else return(int64_t)strtoll(lua_tostring(L, index), NULL, 0); #endif } case LUA_TUSERDATA: value = check_int64(L, index); break; default: return luaL_error(L, "argument %d error type %s", index, lua_typename(L,type)); } return value; } static inline void _pushint64(lua_State *L, int64_t n) { int64_t * udata = (int64_t *)lua_newuserdata(L, sizeof(int64_t )); *udata = n; luaL_getmetatable(L, int64_type_name); lua_setmetatable(L, -2); } static int int64_add(lua_State *L) { get_ab_values; _pushint64(L, a+b); return 1; } static int int64_new(lua_State *L) { int top = lua_gettop(L); int64_t n; switch(top) { case 0 : _pushint64(L,0); break; case 1 : n = get_int64(L,1); lua_pop(L, 1); _pushint64(L,n); break; default: { const char * str; int base = luaL_checkinteger(L,2); if (base < 2) { luaL_error(L, "base must be >= 2"); } str = luaL_checkstring(L, 1); #ifdef _MSC_VER n = _strtoi64(str, NULL, base); #else n = strtoll(str, NULL, base); #endif _pushint64(L,n); break; } } return 1; } static int int64_sub(lua_State *L) { get_ab_values; _pushint64(L, a-b); return 1; } static int int64_mul(lua_State *L) { get_ab_values; _pushint64(L, a * b); return 1; } static int int64_div(lua_State *L) { get_ab_values; if (b == 0) { return luaL_error(L, "div by zero"); } _pushint64(L, a / b); return 1; } static int int64_mod(lua_State *L) { get_ab_values; if (b == 0) { return luaL_error(L, "mod by zero"); } _pushint64(L, a % b); return 1; } static int64_t _pow64(int64_t a, int64_t b) { int64_t a2; if (b == 1) { return a; } a2 = a * a; if (b % 2 == 1) { return _pow64(a2, b/2) * a; } else { return _pow64(a2, b/2); } } static int int64_pow(lua_State *L) { int64_t p; get_ab_values; if (b > 0) { p = _pow64(a,b); } else if (b == 0) { p = 1; } else { return luaL_error(L, "pow by nagtive number %d",(int)b); } _pushint64(L, p); return 1; } static int int64_unm(lua_State *L) { int64_t a = get_int64(L,1); _pushint64(L, -a); return 1; } static int int64_eq(lua_State *L) { get_ab_values; lua_pushboolean(L,a == b); return 1; } static int int64_lt(lua_State *L) { get_ab_values; lua_pushboolean(L,a < b); return 1; } static int int64_le(lua_State *L) { get_ab_values; lua_pushboolean(L,a <= b); return 1; } static int int64_len(lua_State *L) { int64_t a = get_int64(L,1); lua_pushnumber(L,(lua_Number)a); return 1; } static int tostring(lua_State *L) { static char hex[16] = "0123456789ABCDEF"; int64_t n = check_int64(L,1); if (lua_gettop(L) == 1) { char str[24]; #ifndef _MSC_VER snprintf(str, sizeof(str), "%" PRId64, n); #else snprintf(str, sizeof(str), "%10lld", (long long)n); #endif lua_pushstring(L,str); } else { int i; char buffer[64]; int base = luaL_checkinteger(L,2); int shift = 1; int mask = 2; switch(base) { case 0: { unsigned char buffer[8]; int i; for (i=0;i<8;i++) { buffer[i] = (n >> (i*8)) & 0xff; } lua_pushlstring(L,(const char *)buffer, 8); return 1; } case 10: { int buffer[32], i; int64_t dec = (int64_t)n; luaL_Buffer b; #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502 luaL_buffinit(L, &b); #else luaL_buffinitsize(L , &b , 28); #endif if (dec<0) { luaL_addchar(&b, '-'); dec = -dec; } for (i=0;i<32;i++) { buffer[i] = dec%10; dec /= 10; if (dec == 0) break; } while (i>=0) { luaL_addchar(&b, hex[buffer[i]]); --i; } luaL_pushresult(&b); return 1; } case 2: shift = 1; mask = 1; break; case 8: shift = 3; mask = 7; break; case 16: shift = 4; mask = 0xf; break; default: luaL_error(L, "Unsupport base %d",base); break; } for (i=0;i<64;i+=shift) { buffer[i/shift] = hex[(n>>(64-shift-i)) & mask]; } lua_pushlstring(L, buffer, 64 / shift); } return 1; } #undef get_ab_values void register_int64(lua_State *L) { if (sizeof(long long int)==sizeof(int64_t)) { luaL_Reg regs[] = { { "new", int64_new }, { "tostring", tostring }, { "__add", int64_add }, { "__sub", int64_sub }, { "__mul", int64_mul }, { "__div", int64_div }, { "__mod", int64_mod }, { "__unm", int64_unm }, { "__pow", int64_pow }, { "__eq", int64_eq }, { "__lt", int64_lt }, { "__le", int64_le }, { "__len", int64_len }, { "__tostring", tostring }, { NULL, NULL } }; luaL_newmetatable(L, int64_type_name); luaL_setfuncs(L, regs, 0); lua_pushvalue(L, -1); lua_setfield(L, -1, "__index"); lua_setglobal(L, int64_type_name); } } int64 get64lua(lua_State *L, int index) { return get_int64(L,index); } void setInt64lua(lua_State *L, int64 value) { _pushint64(L,value); } pllua-1.1.0/lua_int64.h000066400000000000000000000004201304764261500146100ustar00rootroot00000000000000#ifndef LUA_INT64_H #define LUA_INT64_H #include #include #include #include void register_int64(lua_State * L); int64 get64lua(lua_State * L,int index); void setInt64lua(lua_State * L,int64 value); #endif // LUA_INT64_H pllua-1.1.0/pllua--1.0.sql000066400000000000000000000032201304764261500150420ustar00rootroot00000000000000\echo Use "CREATE EXTENSION pllua" to load this file. \quit CREATE FUNCTION pllua_call_handler() RETURNS language_handler AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT; -- comment out if VERSION < 9.0 CREATE FUNCTION pllua_inline_handler(internal) RETURNS VOID AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION pllua_validator(oid) RETURNS VOID AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT; CREATE TRUSTED LANGUAGE pllua HANDLER pllua_call_handler INLINE pllua_inline_handler -- comment out if VERSION < 9.0 VALIDATOR pllua_validator; CREATE FUNCTION plluau_call_handler() RETURNS language_handler AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT; -- comment out if VERSION < 9.0 CREATE FUNCTION plluau_inline_handler(internal) RETURNS VOID AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION plluau_validator(oid) RETURNS VOID AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT; CREATE LANGUAGE plluau HANDLER plluau_call_handler INLINE plluau_inline_handler -- comment out if VERSION < 9.0 VALIDATOR plluau_validator; --within 'pllua' schema CREATE TABLE init (module text); -- PL template installation: INSERT INTO pg_catalog.pg_pltemplate SELECT 'pllua', true, true, 'pllua_call_handler', 'pllua_inline_handler', 'pllua_validator', 'MODULE_PATHNAME', NULL WHERE 'pllua' NOT IN (SELECT tmplname FROM pg_catalog.pg_pltemplate); INSERT INTO pg_catalog.pg_pltemplate SELECT 'plluau', false, true, 'plluau_call_handler', 'plluau_inline_handler', 'plluau_validator', 'MODULE_PATHNAME', NULL WHERE 'plluau' NOT IN (SELECT tmplname FROM pg_catalog.pg_pltemplate); -- vim: set syn=sql: pllua-1.1.0/pllua.c000066400000000000000000000111101304764261500141110ustar00rootroot00000000000000/* * pllua.c: PL/Lua call handler, trusted * Author: Luis Carvalho * Please check copyright notice at the bottom of pllua.h * $Id: pllua.c,v 1.15 2008/03/29 02:49:55 carvalho Exp $ */ #include "pllua.h" #include "pllua_errors.h" PG_MODULE_MAGIC; static lua_State *LuaVM[2] = {NULL, NULL}; /* Lua VMs */ LVMInfo lvm_info[2]; static void init_vmstructs(){ LVMInfo lvm0; LVMInfo lvm1; lvm0.name = "untrusted"; lvm0.hasTraceback = 0; lvm1.name = "trusted"; lvm1.hasTraceback = 0; lvm_info[0] = lvm0; lvm_info[1] = lvm1; } PGDLLEXPORT Datum _PG_init(PG_FUNCTION_ARGS); PGDLLEXPORT Datum _PG_fini(PG_FUNCTION_ARGS); PGDLLEXPORT Datum pllua_validator(PG_FUNCTION_ARGS); PGDLLEXPORT Datum pllua_call_handler(PG_FUNCTION_ARGS); PGDLLEXPORT Datum plluau_validator(PG_FUNCTION_ARGS); PGDLLEXPORT Datum plluau_call_handler(PG_FUNCTION_ARGS); #if PG_VERSION_NUM >= 90000 PGDLLEXPORT Datum pllua_inline_handler(PG_FUNCTION_ARGS); PGDLLEXPORT Datum plluau_inline_handler(PG_FUNCTION_ARGS); #endif #include "pllua_xact_cleanup.h" PG_FUNCTION_INFO_V1(_PG_init); Datum _PG_init(PG_FUNCTION_ARGS) { init_vmstructs(); pllua_init_common_ctx(); LuaVM[0] = luaP_newstate(0); /* untrusted */ LuaVM[1] = luaP_newstate(1); /* trusted */ RegisterXactCallback(pllua_xact_cb, NULL); PG_RETURN_VOID(); } PG_FUNCTION_INFO_V1(_PG_fini); Datum _PG_fini(PG_FUNCTION_ARGS) { luaP_close(LuaVM[0]); luaP_close(LuaVM[1]); pllua_delete_common_ctx(); PG_RETURN_VOID(); } PG_FUNCTION_INFO_V1(plluau_validator); Datum plluau_validator(PG_FUNCTION_ARGS) { return luaP_validator(LuaVM[0], PG_GETARG_OID(0)); } PG_FUNCTION_INFO_V1(plluau_call_handler); Datum plluau_call_handler(PG_FUNCTION_ARGS) { lvm_info[0].hasTraceback = false; return luaP_callhandler(LuaVM[0], fcinfo); } PG_FUNCTION_INFO_V1(pllua_validator); Datum pllua_validator(PG_FUNCTION_ARGS) { return luaP_validator(LuaVM[1], PG_GETARG_OID(0)); } PG_FUNCTION_INFO_V1(pllua_call_handler); Datum pllua_call_handler(PG_FUNCTION_ARGS) { lvm_info[1].hasTraceback = false; return luaP_callhandler(LuaVM[1], fcinfo); } #if PG_VERSION_NUM >= 90000 #define CODEBLOCK \ ((InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0)))->source_text PG_FUNCTION_INFO_V1(plluau_inline_handler); Datum plluau_inline_handler(PG_FUNCTION_ARGS) { lvm_info[0].hasTraceback = false; return luaP_inlinehandler(LuaVM[0], CODEBLOCK); } PG_FUNCTION_INFO_V1(pllua_inline_handler); Datum pllua_inline_handler(PG_FUNCTION_ARGS) { lvm_info[1].hasTraceback = false; return luaP_inlinehandler(LuaVM[1], CODEBLOCK); } #endif /* p_lua_mem_cxt and p_lua_master_state addresses used as keys for storing * values in lua states. In case when this functions are equal then * visual studio compiler with default settings "optimize" it and as * result both functions have equal addresses which make them unusable as keys, * thats why return 1; return 2; */ int p_lua_mem_cxt(void){return 2;} int p_lua_master_state(void){return 1;} //------------------------------------------------------------------------------------------------------ MemoryContext luaP_getmemctxt(lua_State *L) { MemoryContext mcxt; lua_pushlightuserdata(L, p_lua_mem_cxt); lua_rawget(L, LUA_REGISTRYINDEX); mcxt = lua_touserdata(L, -1); lua_pop(L, 1); return mcxt; } lua_State * pllua_getmaster(lua_State *L) { lua_State *master; lua_pushlightuserdata(L, p_lua_master_state); lua_rawget(L, LUA_REGISTRYINDEX); master = (lua_State *) lua_touserdata(L, -1); lua_pop(L, 1); return master; } int pllua_getmaster_index(lua_State *L) { if (pllua_getmaster(L) == LuaVM[0]) return 0; return 1; } #if LUA_VERSION_NUM < 502 void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup) { luaL_checkstack(L, nup+1, "too many upvalues"); for (; l->name != NULL; l++) { /* fill the table with given functions */ int i; lua_pushstring(L, l->name); for (i = 0; i < nup; i++) /* copy upvalues to the top */ lua_pushvalue(L, -(nup+1)); lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ lua_settable(L, -(nup + 3)); } lua_pop(L, nup); /* remove upvalues */ } #endif int pg_to_regtype(char *typ_name) { Oid result; int32 typmod; /* * Invoke the full parser to deal with special cases such as array syntax. */ #if PG_VERSION_NUM < 90400 parseTypeString(typ_name, &result, &typmod); #else parseTypeString(typ_name, &result, &typmod, true); #endif if (OidIsValid(result)) return result; else return -1; } pllua-1.1.0/pllua.control000066400000000000000000000002321304764261500153520ustar00rootroot00000000000000# pllua extension default_version = '1.0' comment = 'Lua as a procedural language' module_pathname = '$libdir/pllua' relocatable = false schema = 'pllua' pllua-1.1.0/pllua.h000066400000000000000000000054071304764261500141320ustar00rootroot00000000000000/* * PL/Lua * Author: Luis Carvalho * Version: 1.0 * Please check copyright notice at the bottom of this file * $Id: pllua.h,v 1.17 2009/09/19 16:20:45 carvalho Exp $ */ #include "plluacommon.h" /*used as a key for saving lua context lua_pushlightuserdata(p_lua_mem_cxt) instead of lua_pushlightuserdata((void*)L) (in SRF the L is a lua_newthread) */ int p_lua_mem_cxt(void); int p_lua_master_state(void); typedef struct luaP_Buffer { int size; Datum *value; char *null; } luaP_Buffer; /* utils */ void *luaP_toudata (lua_State *L, int ud, const char *tname); luaP_Buffer *luaP_getbuffer (lua_State *L, int n); /* call handler API */ lua_State *luaP_newstate (int trusted); void luaP_close (lua_State *L); Datum luaP_validator (lua_State *L, Oid oid); Datum luaP_callhandler (lua_State *L, FunctionCallInfo fcinfo); #if PG_VERSION_NUM >= 90000 Datum luaP_inlinehandler (lua_State *L, const char *source); #endif /* general API */ void luaP_pushdatum (lua_State *L, Datum dat, Oid type); Datum luaP_todatum (lua_State *L, Oid type, int len, bool *isnull, int idx); void luaP_pushtuple_trg (lua_State *L, TupleDesc desc, HeapTuple tuple, Oid relid, int readonly); HeapTuple luaP_totuple (lua_State *L); HeapTuple luaP_casttuple (lua_State *L, TupleDesc tupdesc); /* SPI */ Oid luaP_gettypeoid (const char *type_name); void luaP_pushdesctable(lua_State *L, TupleDesc desc); void luaP_registerspi(lua_State *L); void luaP_pushcursor (lua_State *L, Portal cursor); void luaP_pushrecord(lua_State *L, Datum record); Portal luaP_tocursor (lua_State *L, int pos); /* ========================================================================= Copyright (c) 2008 Luis Carvalho 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. ========================================================================= */ pllua-1.1.0/pllua.sql.in000066400000000000000000000027211304764261500151030ustar00rootroot00000000000000CREATE FUNCTION pllua_call_handler() RETURNS language_handler AS '$libdir/pllua' LANGUAGE C IMMUTABLE STRICT; -- comment out if VERSION < 9.0 CREATE FUNCTION pllua_inline_handler(internal) RETURNS VOID AS '$libdir/pllua' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION pllua_validator(oid) RETURNS VOID AS '$libdir/pllua' LANGUAGE C IMMUTABLE STRICT; CREATE TRUSTED LANGUAGE pllua HANDLER pllua_call_handler INLINE pllua_inline_handler -- comment out if VERSION < 9.0 VALIDATOR pllua_validator; CREATE FUNCTION plluau_call_handler() RETURNS language_handler AS '$libdir/pllua' LANGUAGE C IMMUTABLE STRICT; -- comment out if VERSION < 9.0 CREATE FUNCTION plluau_inline_handler(internal) RETURNS VOID AS '$libdir/pllua' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION plluau_validator(oid) RETURNS VOID AS '$libdir/pllua' LANGUAGE C IMMUTABLE STRICT; CREATE LANGUAGE plluau HANDLER plluau_call_handler INLINE plluau_inline_handler -- comment out if VERSION < 9.0 VALIDATOR plluau_validator; -- Optional: --CREATE SCHEMA pllua -- CREATE TABLE init (module text); -- PL template installation: --INSERT INTO pg_catalog.pg_pltemplate -- VALUES ('pllua', true, true, 'pllua_call_handler', -- 'pllua_inline_handler', 'pllua_validator', '$libdir/pllua', NULL); --INSERT INTO pg_catalog.pg_pltemplate -- VALUES ('plluau', false, true, 'plluau_call_handler', -- 'plluau_inline_handler', 'plluau_validator', '$libdir/pllua', NULL); -- vim: set syn=sql: pllua-1.1.0/pllua_debug.c000066400000000000000000000051421304764261500152670ustar00rootroot00000000000000#include "pllua_debug.h" /* PostgreSQL */ #include #include #include #include #if PG_VERSION_NUM >= 90300 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static char *_location; void setLINE(char *location) { _location = location; } const char *getLINE(void) { return _location; } #define DMP 2 void stackDump(lua_State *L) { int i=lua_gettop(L); ereport(INFO, (errmsg("%s", "---------------- Stack Dump ----------------"))); while( i ) { int t = lua_type(L, i); switch (t) { case LUA_TSTRING: ereport(INFO, (errmsg("%d:`%s'", i, lua_tostring(L, i)))); break; case LUA_TBOOLEAN: ereport(INFO, (errmsg("%d: %s",i,lua_toboolean(L, i) ? "true" : "false"))); break; case LUA_TNUMBER: ereport(INFO, (errmsg("%d: %g", i, lua_tonumber(L, i)))); break; case LUA_TTABLE: ereport(INFO, (errmsg("%d: table", i))); if (DMP==1){ /* table is in the stack at index 't' */ lua_pushnil(L); /* first key */ while (lua_next(L, i) != 0) { /* uses 'key' (at index -2) and 'value' (at index -1) */ ereport(INFO, (errmsg("===%s - %s\n", lua_tostring(L, -2), lua_typename(L, lua_type(L, -1))))); /* removes 'value'; keeps 'key' for next iteration */ lua_pop(L, 1); } }else if (DMP == 2){ int cnt = 0; lua_pushnil(L); /* first key */ while (lua_next(L, i) != 0) { ++cnt; /* removes 'value'; keeps 'key' for next iteration */ lua_pop(L, 1); } ereport(INFO, (errmsg("===length %i: table", cnt))); } break; default: ereport(INFO, (errmsg("%d: %s", i, lua_typename(L, t)))); break; } i--; } ereport(INFO, (errmsg("%s","--------------- Stack Dump Finished ---------------" ))); } pllua-1.1.0/pllua_debug.h000066400000000000000000000015231304764261500152730ustar00rootroot00000000000000/* * debug helpers * Author: Eugene Sergeev * Please check copyright notice at the bottom of pllua.h */ #ifndef PLLUA_DEBUG_H #define PLLUA_DEBUG_H #include #include #include void setLINE(char *location); const char * getLINE(void); #include #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #define AT __FILE__ ":" TOSTRING(__LINE__) void stackDump (lua_State *L); #define lua_settable(...) setLINE(AT);lua_settable(__VA_ARGS__) #define lua_rawseti(...) setLINE(AT);lua_rawseti(__VA_ARGS__) #define lua_rawset(...) setLINE(AT);lua_rawset(__VA_ARGS__) #define lua_setmetatable(...) setLINE(AT);lua_setmetatable(__VA_ARGS__) #define out(...) ereport(INFO, (errmsg(__VA_ARGS__))) #define dolog(...) ereport(LOG, (errmsg(__VA_ARGS__))) #endif // PLLUA_DEBUG_H pllua-1.1.0/pllua_errors.c000066400000000000000000000207031304764261500155150ustar00rootroot00000000000000#include "pllua_errors.h" #include "plluacommon.h" extern LVMInfo lvm_info[2]; static const char error_type_name[] = "pllua_error"; int luaB_assert (lua_State *L) { luaL_checkany(L, 1); if (!lua_toboolean(L, 1)) return luaL_error(L, "%s", luaL_optstring(L, 2, "assertion failed!")); return lua_gettop(L); } int luaB_error (lua_State *L) { #if LUA_VERSION_NUM >= 503 int level = luaL_optinteger(L, 2, 1); #else int level = luaL_optint(L, 2, 1); #endif lua_settop(L, 1); if (lua_isnoneornil(L, 1)){ if (lua_isnil(L, 1)){ lua_pop(L, 1); } if (level >0){ luaL_where(L, level); lua_pushstring(L, "no exception data"); lua_concat(L, 2); }else{ lua_pushstring(L, "no exception data"); } }else // if (lua_isstring(L, 1) && level > 0) { /* add extra information? */ // luaL_where(L, level); // lua_pushvalue(L, 1); // lua_concat(L, 2); // } else if (lua_istable(L,1)){ set_error_mt(L); } return lua_error(L); } int luaL_error_skip_where (lua_State *L, const char *fmt, ...) { va_list argp; va_start(argp, fmt); /*luaL_where(L, 1);*/ lua_pushvfstring(L, fmt, argp); va_end(argp); /*lua_concat(L, 2);*/ return lua_error(L); } static int error_tostring(lua_State *L) { lua_pushstring(L, "message"); lua_rawget(L, -2); return 1; } static luaL_Reg regs[] = { {"__tostring", error_tostring}, { NULL, NULL } }; void register_error_mt(lua_State *L) { lua_newtable(L); lua_pushlightuserdata(L, (void *) error_type_name); lua_pushvalue(L, -2); lua_rawset(L, LUA_REGISTRYINDEX); luaP_register(L, regs); lua_pop(L, 1); } void set_error_mt(lua_State *L) { luaP_getfield(L, error_type_name); lua_setmetatable(L, -2); } void push_spi_error(lua_State *L, MemoryContext oldcontext) { ErrorData *edata; /* Save error info */ MemoryContextSwitchTo(oldcontext); edata = CopyErrorData(); FlushErrorState(); lua_newtable(L); if (edata->message){ lua_pushstring(L, edata->message); //"no exception data" lua_setfield(L, -2, "message"); } else { lua_pushstring(L, "no exception data"); lua_setfield(L, -2, "message"); } if (edata->detail){ lua_pushstring(L, edata->detail); lua_setfield(L, -2, "detail"); } if (edata->context){ lua_pushstring(L, edata->context); lua_setfield(L, -2, "context"); } if (edata->hint){ lua_pushstring(L, edata->hint); lua_setfield(L, -2, "hint"); } if (edata->sqlerrcode){ lua_pushinteger(L, edata->sqlerrcode); lua_setfield(L, -2, "sqlerrcode"); } set_error_mt(L); FreeErrorData(edata); } static void pllua_parse_error(lua_State *L, ErrorData *edata){ lua_pushnil(L); while (lua_next(L, -2) != 0) { if (lua_type(L, -2) == LUA_TSTRING){ const char *key = lua_tostring(L, -2); if (lua_type(L, -1) == LUA_TSTRING){ if (strcmp(key, "message") == 0){ edata->message = pstrdup( lua_tostring(L, -1) ); } else if (strcmp(key, "detail") == 0){ edata->detail = pstrdup( lua_tostring(L, -1) ); } else if (strcmp(key, "hint") == 0){ edata->hint = pstrdup( lua_tostring(L, -1) ); } else if (strcmp(key, "context") == 0){ edata->context = pstrdup( lua_tostring(L, -1) ); } }else if (lua_type(L, -1) == LUA_TNUMBER){ if (strcmp(key, "sqlerrcode") == 0){ edata->sqlerrcode = (int)( lua_tonumber(L, -1) ); } } } lua_pop(L, 1); } } void luatable_topgerror(lua_State *L) { luatable_report(L, ERROR); } void luatable_report(lua_State *L, int elevel) { ErrorData edata; char *query = NULL; int position = 0; edata.message = NULL; edata.sqlerrcode = 0; edata.detail = NULL; edata.hint = NULL; edata.context = NULL; pllua_parse_error(L, &edata); lua_pop(L, lua_gettop(L)); elevel = Min(elevel, ERROR); ereport(elevel, (errcode(edata.sqlerrcode ? edata.sqlerrcode : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), errmsg_internal("%s", edata.message ? edata.message : "no exception data"), (edata.detail) ? errdetail_internal("%s", edata.detail) : 0, (edata.context) ? errcontext("%s", edata.context) : 0, (edata.hint) ? errhint("%s", edata.hint) : 0, (query) ? internalerrquery(query) : 0, (position) ? internalerrposition(position) : 0)); } /* this is a copy from lua source to call debug.traceback without access it from metatables */ #define LEVELS1 12 /* size of the first part of the stack */ #define LEVELS2 10 /* size of the second part of the stack */ static lua_State *getthread (lua_State *L, int *arg) { if (lua_isthread(L, 1)) { *arg = 1; return lua_tothread(L, 1); } else { *arg = 0; return L; } } static int db_errorfb (lua_State *L) { int level; int firstpart = 1; /* still before eventual `...' */ int arg; lua_State *L1 = getthread(L, &arg); lua_Debug ar; luaL_Buffer b; if (lua_isnumber(L, arg+2)) { level = (int)lua_tointeger(L, arg+2); lua_pop(L, 1); } else level = (L == L1) ? 1 : 0; /* level 0 may be this own function */ if (lua_gettop(L) == arg) lua_pushliteral(L, ""); else if (!lua_isstring(L, arg+1)) return 1; /* message is not a string */ else lua_pushliteral(L, "\n"); luaL_buffinit(L, &b); luaL_addstring(&b, "stack traceback("); luaL_addstring(&b, lvm_info[pllua_getmaster_index(L)].name); luaL_addstring(&b, "):"); luaL_pushresult(&b); //lua_pushliteral(L, "stack traceback:"); while (lua_getstack(L1, level++, &ar)) { if (level > LEVELS1 && firstpart) { /* no more than `LEVELS2' more levels? */ if (!lua_getstack(L1, level+LEVELS2, &ar)) level--; /* keep going */ else { lua_pushliteral(L, "\n\t..."); /* too many levels */ while (lua_getstack(L1, level+LEVELS2, &ar)) /* find last levels */ level++; } firstpart = 0; continue; } lua_pushliteral(L, "\n\t"); lua_getinfo(L1, "Snl", &ar); lua_pushfstring(L, "%s:", ar.short_src); if (ar.currentline > 0) lua_pushfstring(L, "%d:", ar.currentline); if (*ar.namewhat != '\0') /* is there a name? */ lua_pushfstring(L, " in function " LUA_QS, ar.name); else { if (*ar.what == 'm') /* main? */ lua_pushfstring(L, " in main chunk"); else if (*ar.what == 'C' || *ar.what == 't') lua_pushliteral(L, " ?"); /* C function or tail call */ else lua_pushfstring(L, " in function <%s:%d>", ar.short_src, ar.linedefined); } lua_concat(L, lua_gettop(L) - arg); } lua_concat(L, lua_gettop(L) - arg); return 1; } int traceback (lua_State *L) { int stateIndex = pllua_getmaster_index(L); if (lvm_info[stateIndex].hasTraceback) return 1; if (lua_isstring(L, 1)){ /* 'message' not a string? */ lua_newtable(L); lua_pushcfunction(L, db_errorfb); lua_pushstring(L,""); /* empty concat string for context */ lua_pushinteger(L, 2); /* skip this function and traceback */ lua_call(L, 2, 1); /* call debug.traceback */ lvm_info[stateIndex].hasTraceback = true; lua_setfield(L, -2, "context"); lua_swap(L); /*text <-> table*/ lua_setfield(L, -2, "message"); set_error_mt(L); return 1; }else if (lua_istable(L,1)){ lua_pushstring(L,"context"); lua_rawget(L, -2); if (!lua_isstring(L, -1)){ lua_pop(L,1); lua_pushstring(L,""); /* empty concat string for context */ } lua_pushcfunction(L, db_errorfb); lua_swap(L); lua_pushinteger(L, 2); /* skip this function and traceback */ lua_call(L, 2, 1); /* call debug.traceback */ lvm_info[stateIndex].hasTraceback = true; lua_setfield(L, -2, "context"); return 1; } return 1; /* keep it intact */ } pllua-1.1.0/pllua_errors.h000066400000000000000000000033371304764261500155260ustar00rootroot00000000000000#ifndef PLLUA_ERRORS_H #define PLLUA_ERRORS_H #include #include #include #include #define PLLUA_PG_CATCH_RETHROW(source_code) do\ {\ MemoryContext ____oldContext = CurrentMemoryContext;\ PG_TRY();\ {\ source_code\ }\ PG_CATCH();\ {\ lua_pop(L, lua_gettop(L));\ push_spi_error(L, ____oldContext);\ return lua_error(L);\ }PG_END_TRY();\ }while(0) #if defined(PLLUA_DEBUG) #define luapg_error(L, tag) do{\ if (lua_type(L, -1) == LUA_TSTRING){ \ const char *err = pstrdup( lua_tostring((L), -1)); \ lua_pop(L, lua_gettop(L));\ ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), \ errmsg("[pllua]: error: %s", tag), \ errdetail("%s", err)));\ }else {\ luatable_topgerror(L);\ }\ }while(0) #else #define luapg_error(L, tag)do{\ if (lua_type(L, -1) == LUA_TSTRING){ \ const char *err = pstrdup( lua_tostring((L), -1)); \ lua_pop(L, lua_gettop(L));\ ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), \ errmsg("[pllua]: " tag " error"),\ errdetail("%s", err)));\ }else {\ luatable_topgerror(L);\ }\ }while(0) #endif /*shows error as "error text" instead of "[string "anonymous"]:2: error text*/ #define luaL_error luaL_error_skip_where int luaB_assert (lua_State *L); int luaB_error (lua_State *L); int luaL_error_skip_where (lua_State *L, const char *fmt, ...); void register_error_mt(lua_State * L); void set_error_mt(lua_State * L); void push_spi_error(lua_State *L, MemoryContext oldcontext); void luatable_topgerror(lua_State *L); void luatable_report(lua_State *L, int elevel); int traceback (lua_State *L) ; #endif // PLLUA_ERRORS_H pllua-1.1.0/pllua_pgfunc.c000066400000000000000000000241061304764261500154640ustar00rootroot00000000000000/* pgfunc works as a wrapper for postgres functions and as a loader as module for pllua (internal)->internal functions. pgfunc(text - function singnature) pgfunc(text - function singnature, table - options) options = { only_internal = true --false, --[[if only_internal is false, pgfunc accepts any function]] throwable = true --false --[[ throwable makes PG_TRY PG_CATCH for non internal functions]] } Note: Set returning functions are not supported. No checks if function is strict. */ #include "pllua_pgfunc.h" #include "plluacommon.h" #include "pllua.h" #include "pllua_xact_cleanup.h" #include #include #include "pllua_errors.h" #define RESET_CONTEXT_AFTER 1000 static const char pg_func_type_name[] = "pg_func"; static Oid find_lang_oids(const char* lang) { HeapTuple tuple; tuple = SearchSysCache(LANGNAME, CStringGetDatum(lang), 0, 0, 0); if (HeapTupleIsValid(tuple)) { Oid langtupoid = HeapTupleGetOid(tuple); ReleaseSysCache(tuple); return langtupoid; } return 0; } static Oid pllua_oid = 0; static Oid plluau_oid = 0; static Oid get_pllua_oid() { if (pllua_oid !=0) return pllua_oid; return find_lang_oids("pllua"); } static Oid get_plluau_oid() { if (plluau_oid !=0) return plluau_oid; return find_lang_oids("plluau"); } typedef struct{ bool only_internal; bool throwable; } Pgfunc_options; typedef struct{ Oid funcid; int numargs; Oid *argtypes; lua_CFunction callfunc; Oid prorettype; FmgrInfo fi; Pgfunc_options options; } PgFuncInfo, Lua_pgfunc; typedef struct{ FmgrInfo fi; ExprContext econtext; ReturnSetInfo rsinfo; FunctionCallInfoData fcinfo; Oid prorettype; } Lua_pgfunc_srf; #define freeandnil(p) do{ if (p){\ pfree(p);\ p = NULL;\ }}while(0) static void clean_pgfuncinfo(Lua_pgfunc *data) { freeandnil (data->argtypes); } #ifdef PGFUNC_CLEANUP static MemoryContext get_tmpcontext() { MemoryContext mc; mc = AllocSetContextCreate(TopMemoryContext, "pgfunc temporary context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); return mc; } #endif static MemoryContext tmpcontext; #ifdef PGFUNC_CLEANUP static int tmpcontext_usage = 0; #endif static int pg_callable_func(lua_State *L) { MemoryContext m; int i; FunctionCallInfoData fcinfo; Lua_pgfunc *fi; #ifndef PGFUNC_CLEANUP tmpcontext = CurTransactionContext; #endif fi = (Lua_pgfunc *) lua_touserdata(L, lua_upvalueindex(1)); InitFunctionCallInfoData(fcinfo, &fi->fi, fi->numargs, InvalidOid, NULL, NULL); #ifdef PGFUNC_CLEANUP if(tmpcontext_usage> RESET_CONTEXT_AFTER ){ MemoryContextReset(tmpcontext); tmpcontext_usage = 0; } ++tmpcontext_usage; #endif m = MemoryContextSwitchTo(tmpcontext); for (i=0; inumargs; ++i){ fcinfo.arg[i] = luaP_todatum(L, fi->argtypes[i], 0, &fcinfo.argnull[i], i+1); } if(!fi->options.only_internal && fi->options.throwable){ SPI_push(); PG_TRY(); { Datum d = FunctionCallInvoke(&fcinfo); MemoryContextSwitchTo(m); if (fcinfo.isnull) { lua_pushnil(L); } else { luaP_pushdatum(L, d, fi->prorettype); } SPI_pop(); } PG_CATCH(); { lua_pop(L, lua_gettop(L)); push_spi_error(L, m); /*context switch to m inside push_spi_error*/ SPI_pop(); return lua_error(L); }PG_END_TRY(); }else{ Datum d = FunctionCallInvoke(&fcinfo); MemoryContextSwitchTo(m); if (fcinfo.isnull) { lua_pushnil(L); } else { luaP_pushdatum(L, d, fi->prorettype); } } return 1; } static void parse_options(lua_State *L, Pgfunc_options *opt){ lua_pushnil(L); while (lua_next(L, -2) != 0) { if (lua_type(L, -2) == LUA_TSTRING){ const char *key = lua_tostring(L, -2); if (strcmp(key, "only_internal") == 0){ opt->only_internal = lua_toboolean(L, -1); } else if (strcmp(key, "throwable") == 0){ opt->throwable = lua_toboolean(L, -1); } else { luaL_error(L, "pgfunc unknown option \"%s\"", key); } } lua_pop(L, 1); } } static int pgfunc_rowsaux (lua_State *L) { ReturnSetInfo *rsinfo; FunctionCallInfoData *fcinfo; Datum d; Oid prorettype; Lua_pgfunc_srf *srfi; srfi = (Lua_pgfunc_srf *) lua_touserdata(L, lua_upvalueindex(1)); rsinfo = &srfi->rsinfo; fcinfo = &srfi->fcinfo; prorettype = srfi->prorettype; d = FunctionCallInvoke(fcinfo); if ((!fcinfo->isnull)&&(rsinfo->isDone != ExprEndResult)){ luaP_pushdatum(L, d, prorettype); return 1; } lua_pushnil(L); return 1; } static int pgfunc_rows (lua_State *L) { int i; Lua_pgfunc *fi; ReturnSetInfo *rsinfo; ExprContext *econtext; FunctionCallInfoData *fcinfo; Lua_pgfunc_srf *srfi; int argc; BEGINLUA; argc = lua_gettop(L); fi = (Lua_pgfunc *) lua_touserdata(L, lua_upvalueindex(1)); srfi = (Lua_pgfunc_srf *)lua_newuserdata(L, sizeof(Lua_pgfunc_srf)); econtext = &srfi->econtext; rsinfo = &srfi->rsinfo; fcinfo = &srfi->fcinfo; srfi->prorettype = fi->prorettype; fmgr_info(fi->funcid, &srfi->fi); memset(econtext, 0, sizeof(ExprContext)); econtext->ecxt_per_query_memory = CurrentMemoryContext; rsinfo->type = T_ReturnSetInfo; rsinfo->econtext = econtext; rsinfo->allowedModes = (int)(SFRM_ValuePerCall /*| SFRM_Materialize*/); rsinfo->returnMode = SFRM_ValuePerCall;//SFRM_Materialize; rsinfo->setResult = NULL; rsinfo->setDesc = NULL; InitFunctionCallInfoData((*fcinfo), &srfi->fi, fi->numargs, InvalidOid, NULL, (fmNodePtr)rsinfo); for (i=0; inumargs; ++i){ if(i>=argc){ for (i = argc; inumargs; ++i){ fcinfo->arg[i] = 0 ;//Datum; fcinfo->argnull[i] = true; } break; } fcinfo->arg[i] = luaP_todatum(L, fi->argtypes[i], 0, &fcinfo->argnull[i], i+1); } lua_pushcclosure(L, pgfunc_rowsaux, 1); ENDLUAV(1); return 1; } int get_pgfunc(lua_State *L) { Lua_pgfunc *lf; Pgfunc_options opt; MemoryContext m; const char* reg_name = NULL; HeapTuple proctup; Form_pg_proc proc; int luasrc = 0; Oid funcid = 0; BEGINLUA; #ifndef PGFUNC_CLEANUP tmpcontext = CurTransactionContext; #endif opt.only_internal = true; opt.throwable = true; if (lua_gettop(L) == 2){ luaL_checktype(L, 2, LUA_TTABLE); parse_options(L, &opt); }else if (lua_gettop(L) != 1){ return luaL_error(L, "pgfunc(text): wrong arguments"); } if(lua_type(L, 1) == LUA_TSTRING){ reg_name = luaL_checkstring(L, 1); m = MemoryContextSwitchTo(tmpcontext); PG_TRY(); { funcid = DatumGetObjectId(DirectFunctionCall1(regprocedurein, CStringGetDatum(reg_name))); } PG_CATCH();{} PG_END_TRY(); MemoryContextSwitchTo(m); #ifdef PGFUNC_CLEANUP MemoryContextReset(tmpcontext); #endif }else if (lua_type(L, 1) == LUA_TNUMBER){ funcid = luaL_checkinteger(L, 1); } if (!OidIsValid(funcid)){ if (reg_name) return luaL_error(L,"failed to register %s", reg_name); return luaL_error(L,"failed to register function with oid %d", funcid); } proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); if (!HeapTupleIsValid(proctup)){ return luaL_error(L,"cache lookup failed for function %d", funcid); } proc = (Form_pg_proc) GETSTRUCT(proctup); luasrc = ((proc->prolang == get_pllua_oid()) || (proc->prolang == get_plluau_oid())); if ( opt.only_internal &&(proc->prolang != INTERNALlanguageId) &&(!luasrc) ){ ReleaseSysCache(proctup); return luaL_error(L, "supported only SQL/internal functions"); } lf = (Lua_pgfunc *)lua_newuserdata(L, sizeof(Lua_pgfunc)); /*make it g/collected*/ luaP_getfield(L, pg_func_type_name); lua_setmetatable(L, -2); lf->prorettype = proc->prorettype; lf->funcid = funcid; lf->options = opt; { Oid *argtypes; char **argnames; char *argmodes; int argc; MemoryContext cur = CurrentMemoryContext; MemoryContextSwitchTo(tmpcontext); argc = get_func_arg_info(proctup, &argtypes, &argnames, &argmodes); MemoryContextSwitchTo(get_common_ctx()); lf->numargs = argc; lf->argtypes = (Oid*)palloc(argc * sizeof(Oid)); memcpy(lf->argtypes, argtypes, argc * sizeof(Oid)); MemoryContextSwitchTo(cur); #ifdef PGFUNC_CLEANUP MemoryContextReset(tmpcontext); #endif } if (luasrc){ bool isnull; text *t; const char *s; luaL_Buffer b; int pcall_result; Datum prosrc; if((lf->numargs != 1) || (lf->argtypes[0] != INTERNALOID) || (lf->prorettype != INTERNALOID)){ luaL_error(L, "pgfunc accepts only 'internal' pllua/u functions with internal argument"); } prosrc = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "[pgfunc]: null lua prosrc"); luaL_buffinit(L, &b); luaL_addstring(&b,"do "); t = DatumGetTextP(prosrc); luaL_addlstring(&b, VARDATA(t), VARSIZE(t) - VARHDRSZ); luaL_addstring(&b, " end"); luaL_pushresult(&b); s = lua_tostring(L, -1); ReleaseSysCache(proctup); clean_pgfuncinfo(lf); if (luaL_loadbuffer(L, s, strlen(s), "pgfunc chunk")) luaL_error(L, "compile"); lua_remove(L, -2); /*delete source element*/ pcall_result = lua_pcall(L, 0, 1, 0); lua_remove(L, -2); /*delete chunk*/ if(pcall_result == 0){ ENDLUAV(1); return 1; } if( pcall_result == LUA_ERRRUN) luaL_error(L,"%s %s","Runtime error:",lua_tostring(L, -1)); else if(pcall_result == LUA_ERRMEM) luaL_error(L,"%s %s","Memory error:",lua_tostring(L, -1)); else if(pcall_result == LUA_ERRERR) luaL_error(L,"%s %s","Error:",lua_tostring(L, -1)); return luaL_error(L, "pgfunc unknown error"); } if(proc->proretset) { lua_pushcclosure(L, pgfunc_rows, 1); } else { fmgr_info(funcid, &lf->fi); lua_pushcclosure(L, pg_callable_func, 1); } ReleaseSysCache(proctup); ENDLUAV(1); return 1; } static void __newmetatable (lua_State *L, const char *tname) { lua_newtable(L); lua_pushlightuserdata(L, (void *) tname); lua_pushvalue(L, -2); lua_rawset(L, LUA_REGISTRYINDEX); } static int gc_pg_func(lua_State *L) { Lua_pgfunc *lf = lua_touserdata(L, 1); clean_pgfuncinfo(lf); return 0; } static luaL_Reg regs[] = { {"__gc", gc_pg_func}, { NULL, NULL } }; #ifdef PGFUNC_CLEANUP static bool cxt_initialized = false; #endif void register_funcinfo_mt(lua_State *L) { __newmetatable(L, pg_func_type_name); luaP_register(L, regs); lua_pop(L, 1); #ifdef PGFUNC_CLEANUP if (!cxt_initialized){ tmpcontext = get_tmpcontext(); cxt_initialized = true; } #endif } pllua-1.1.0/pllua_pgfunc.h000066400000000000000000000005641304764261500154730ustar00rootroot00000000000000/* * pgfunc interface * Author: Eugene Sergeev * Please check copyright notice at the bottom of pllua.h */ #ifndef PLLUA_PGFUNC_H #define PLLUA_PGFUNC_H #include #include #include #include int get_pgfunc(lua_State * L); void register_funcinfo_mt(lua_State * L); #endif // PLLUA_PGFUNC_H pllua-1.1.0/pllua_subxact.c000066400000000000000000000064771304764261500156660ustar00rootroot00000000000000#include "pllua_subxact.h" #include #include #include #include "pllua_debug.h" #include "plluacommon.h" #include "rtupdescstk.h" #include "pllua_errors.h" typedef struct { ResourceOwner resowner; MemoryContext mcontext; } SubTransactionBlock; static SubTransactionBlock stb_SubTranBlock(){ SubTransactionBlock stb; stb.mcontext = NULL; stb.resowner = NULL; return stb; } static void stb_enter(lua_State *L, SubTransactionBlock *block){ if (!IsTransactionOrTransactionBlock()) luaL_error(L, "out of transaction"); block->resowner = CurrentResourceOwner; block->mcontext = CurrentMemoryContext; BeginInternalSubTransaction(NULL); /* Do not want to leave the previous memory context */ MemoryContextSwitchTo(block->mcontext); } static void stb_exit(SubTransactionBlock *block, bool success){ if (success) ReleaseCurrentSubTransaction(); else RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(block->mcontext); CurrentResourceOwner = block->resowner; /* * AtEOSubXact_SPI() should not have popped any SPI context, but just * in case it did, make sure we remain connected. */ SPI_restore_connection(); } /* all exceptions should be thrown only through luaL_error * we might me be here if there is a postgres unhandled exception * and lua migth be in an inconsistant state that's why the process aborted */ #define WRAP_SUBTRANSACTION(source_code) do\ {\ RTupDescStack funcxt;\ RTupDescStack prev;\ SubTransactionBlock subtran;\ funcxt = rtds_initStack(L);\ rtds_inuse(funcxt);\ prev = rtds_set_current(funcxt);\ subtran = stb_SubTranBlock();\ stb_enter(L, &subtran);\ PG_TRY();\ {\ source_code\ }\ PG_CATCH();\ {\ ErrorData *edata;\ edata = CopyErrorData();\ ereport(FATAL, (errmsg("Unhandled exception: %s", edata->message)));\ }\ PG_END_TRY();\ stb_exit(&subtran, status == 0);\ if (status) rtds_unref(funcxt);\ rtds_set_current(prev);\ }while(0) int subt_luaB_pcall (lua_State *L) { int status = 0; luaL_checkany(L, 1); WRAP_SUBTRANSACTION( status = lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0); ); lua_pushboolean(L, (status == 0)); lua_insert(L, 1); return lua_gettop(L); /* return status + all results */ } int subt_luaB_xpcall (lua_State *L) { int status = 0; luaL_checkany(L, 2); lua_settop(L, 2); lua_insert(L, 1); /* put error function under function to be called */ WRAP_SUBTRANSACTION( status = lua_pcall(L, 0, LUA_MULTRET, 1); ); lua_pushboolean(L, (status == 0)); lua_replace(L, 1); return lua_gettop(L); /* return status + all results */ } int use_subtransaction(lua_State *L){ int status = 0; if (lua_gettop(L) < 1){ return luaL_error(L, "subtransaction has no function argument"); } if (lua_type(L, 1) != LUA_TFUNCTION){ return luaL_error(L, "subtransaction first arg must be a lua function"); } WRAP_SUBTRANSACTION( status = lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0); ); lua_pushboolean(L, (status == 0)); lua_insert(L, 1); return lua_gettop(L); /* return status + all results */ } pllua-1.1.0/pllua_subxact.h000066400000000000000000000006421304764261500156570ustar00rootroot00000000000000/* * Subtransaction wrapper * Author: Eugene Sergeev * Please check copyright notice at the bottom of pllua.h */ #ifndef PLLUA_SUBXACT_H #define PLLUA_SUBXACT_H #include #include #include #include int use_subtransaction(lua_State * L); int subt_luaB_pcall (lua_State *L); int subt_luaB_xpcall (lua_State *L); #endif // PLLUA_SUBXACT_H pllua-1.1.0/pllua_xact_cleanup.c000066400000000000000000000055451304764261500166560ustar00rootroot00000000000000#include "pllua_xact_cleanup.h" #define MTOCOMMON {MemoryContext ___m;\ ___m = MemoryContextSwitchTo(cmn_ctx) #define MTOPREV MemoryContextSwitchTo(___m);} struct RSStack; static MemoryContext cmn_ctx = NULL; typedef struct { void *data; RSDtorCallback dtor; } Resource; typedef struct RSNode { void *data; RSDtorCallback dtor; struct RSNode *next; struct RSNode *prev; struct RSStack *tail; } RSNode, *RSNodePtr; typedef struct RSStack { RSNodePtr top; } RSStack, *RSStaskPtr; static RSStaskPtr resource_stk = NULL; static RSNodePtr rsp_push(RSStaskPtr S, void *d, RSDtorCallback dtor) { RSNodePtr np; if (S == NULL) return NULL; MTOCOMMON; np = (RSNodePtr) palloc(sizeof(RSNode)); MTOPREV; np->tail = S; np -> data = d; np -> dtor = dtor; np -> next = S -> top; np -> prev = NULL; if (S->top != NULL) S -> top->prev = np; S -> top = np; return np; } void *unregister_resource(void* d) { RSStaskPtr S; RSNodePtr np; if (d == NULL) return NULL; np = (RSNodePtr)d; S = np->tail; if (S->top == np){ S->top = np->next; if (S->top){ S->top->prev = NULL; } }else{ if (np->prev) np->prev->next = np->next; if (np->next) np->next->prev = np->prev; } pfree(np); return NULL; } void *register_resource(void *d, RSDtorCallback dtor) { return (void*)rsp_push(resource_stk, d, dtor); } static int rsp_isempty(RSStaskPtr S) { return (S -> top == NULL); } static Resource rsp_pop(RSStaskPtr S) { Resource hold; RSNodePtr temp; hold.data = NULL; hold.dtor = NULL; if (rsp_isempty(S)) { return hold; } hold.data = S -> top -> data; hold.dtor = S -> top -> dtor; temp = S -> top; S -> top = S -> top -> next; if (S->top != NULL) S -> top->prev = NULL; pfree(temp); return hold; } static RSStaskPtr rsp_initStack() { RSStaskPtr sp; MTOCOMMON; sp = (RSStaskPtr) palloc(sizeof(RSStack)); MTOPREV; sp->top = NULL; return sp; } static void clean(RSStaskPtr S){ Resource res = rsp_pop(S); while (res.data || res.dtor) { if (res.dtor){ (res.dtor)(res.data); }else{ pfree(res.data); } res = rsp_pop(S); } } void pllua_xact_cb(XactEvent event, void *arg) { //TODO: check events (void)event; (void)arg; clean(resource_stk); } void pllua_init_common_ctx() { cmn_ctx = AllocSetContextCreate(TopMemoryContext, "PL/Lua common context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); resource_stk = rsp_initStack(); } void pllua_delete_common_ctx() { pfree(resource_stk); MemoryContextDelete(cmn_ctx); } MemoryContext get_common_ctx() { return cmn_ctx; } pllua-1.1.0/pllua_xact_cleanup.h000066400000000000000000000010721304764261500166520ustar00rootroot00000000000000/* * functions for resource management * Author: Eugene Sergeev * Please check copyright notice at the bottom of pllua.h */ #ifndef PLLUA_XACT_CLEANUP_H #define PLLUA_XACT_CLEANUP_H #include "plluacommon.h" typedef void (*RSDtorCallback) (void *data); MemoryContext get_common_ctx(void); void pllua_init_common_ctx(void); void pllua_delete_common_ctx(void); void pllua_xact_cb(XactEvent event, void *arg); void *register_resource(void *d, RSDtorCallback dtor); void *unregister_resource(void* d); #endif // PLLUA_XACT_CLEANUP_H pllua-1.1.0/plluaapi.c000066400000000000000000001265141304764261500146220ustar00rootroot00000000000000/* * plluaapi.c: PL/Lua API * Author: Luis Carvalho * Please check copyright notice at the bottom of pllua.h */ #include "pllua.h" #include "rowstamp.h" #include "lua_int64.h" #include "rtupdescstk.h" #include "pllua_pgfunc.h" #include "pllua_subxact.h" #include "pllua_errors.h" /* * [[ Uses of REGISTRY ]] * [general] * REG[light(L)] = memcontext * [type] * REG[oid(type)] = typeinfo * REG[PLLUA_TYPEINFO] = typeinfo_MT * REG[PLLUA_DATUM] = datum_MT * [trigger] * REG[rel_id] = desc_table * REG[relname] = rel_table * [call handler] * REG[light(info)] = func * REG[oid(func)] = info * REG[light(thread)] = thread */ /* extended function info */ typedef struct luaP_Info { RTupDescStack funcxt_wp; /* weak if init_weak used */ bool code_storage; int oid; int vararg; Oid result; bool result_isset; struct RowStamp stamp; /* detect pg_proc row changes */ lua_State *L; /* thread for SETOF iterator */ Oid arg[1]; } luaP_Info; /* extended type info */ typedef struct luaP_Typeinfo { int oid; int16 len; char type; char align; bool byval; Oid elem; FmgrInfo input; FmgrInfo output; TupleDesc tupdesc; } luaP_Typeinfo; /* raw datum */ typedef struct luaP_Datum { int issaved; Datum datum; luaP_Typeinfo *ti; } luaP_Datum; static const char PLLUA_TYPEINFO[] = "typeinfo"; static const char PLLUA_DATUM[] = "datum"; #define PLLUA_LOCALVAR "_U" #define PLLUA_SHAREDVAR "shared" #define PLLUA_SPIVAR "server" #define PLLUA_TRIGGERVAR "trigger" #define PLLUA_CHUNKNAME "pllua chunk" #define PLLUA_INIT_EXIST \ "select 1 from pg_catalog.pg_tables where schemaname='pllua'" \ "and tablename='init'" #define PLLUA_INIT_LIST "select module from pllua.init" #define MaxArraySize ((Size) (MaxAllocSize / sizeof(Datum))) /* back compatibility to 8.2 */ #if PG_VERSION_NUM < 80300 #define SET_VARSIZE(ptr, len) VARATT_SIZEP(ptr) = len #define att_addlength_pointer(len, typ, ptr) \ att_addlength(len, typ, PointerGetDatum(ptr)) #define att_align_nominal(len, typ) att_align(len, typ) #endif #define info(msg) ereport(INFO, (errmsg("%s", msg))) #define argerror(type) \ elog(ERROR, "[pllua]: type '%s' (%d) not supported as argument", \ format_type_be(type), (type)) #define resulterror(type) \ elog(ERROR, "[pllua]: type '%s' (%d) not supported as result", \ format_type_be(type), (type)) #define datum2string(d, f) \ DatumGetCString(DirectFunctionCall1((f), (d))) #define text2string(d) datum2string((d), textout) static int luaP_panic(lua_State *L){ (void)L; ereport(FATAL, (errmsg("pllua unhandled exception"))); return 0; } /* string2text is simpler, so we implement it here with allocation in upper * memory context */ static Datum string2text (const char *str) { int l = strlen(str); text *dat = (text *) SPI_palloc(l + VARHDRSZ); /* in upper context */ SET_VARSIZE(dat, l + VARHDRSZ); memcpy(VARDATA(dat), str, l); return PointerGetDatum(dat); } /* copy dat to upper memory context */ static Datum datumcopy (Datum dat, luaP_Typeinfo *ti) { if (!ti->byval) { /* by reference? */ Size l = datumGetSize(dat, false, ti->len); void *copy = SPI_palloc(l); memcpy(copy, DatumGetPointer(dat), l); return PointerGetDatum(copy); } return dat; } /* ======= Type ======= */ static int luaP_typeinfogc (lua_State *L) { luaP_Typeinfo *ti = lua_touserdata(L, 1); if (ti->tupdesc) FreeTupleDesc(ti->tupdesc); return 0; } static luaP_Typeinfo *luaP_gettypeinfo (lua_State *L, int oid) { luaP_Typeinfo *ti; lua_push_oidstring(L, oid); lua_rawget(L, LUA_REGISTRYINDEX); if (lua_isnil(L, -1)) { /* not cached? */ HeapTuple type; Form_pg_type typeinfo; MemoryContext mcxt = luaP_getmemctxt(L); /* query system */ type = SearchSysCache(TYPEOID, ObjectIdGetDatum(oid), 0, 0, 0); if (!HeapTupleIsValid(type)) elog(ERROR, "[pllua]: cache lookup failed for type %u", oid); typeinfo = (Form_pg_type) GETSTRUCT(type); /* cache */ ti = lua_newuserdata(L, sizeof(luaP_Typeinfo)); ti->len = typeinfo->typlen; ti->type = typeinfo->typtype; ti->align = typeinfo->typalign; ti->byval = typeinfo->typbyval; ti->elem = typeinfo->typelem; fmgr_info_cxt(typeinfo->typinput, &ti->input, mcxt); fmgr_info_cxt(typeinfo->typoutput, &ti->output, mcxt); ti->tupdesc = NULL; if (ti->type == TYPTYPE_COMPOSITE) { TupleDesc td = lookup_rowtype_tupdesc(oid, typeinfo->typtypmod); MemoryContext m = MemoryContextSwitchTo(mcxt); ti->tupdesc = CreateTupleDescCopyConstr(td); MemoryContextSwitchTo(m); BlessTupleDesc(ti->tupdesc); ReleaseTupleDesc(td); } ReleaseSysCache(type); lua_pushlightuserdata(L, (void *) PLLUA_TYPEINFO); lua_rawget(L, LUA_REGISTRYINDEX); /* Typeinfo_MT */ lua_setmetatable(L, -2); lua_push_oidstring(L, oid); lua_pushvalue(L, -2); lua_rawset(L, LUA_REGISTRYINDEX); /* REG[oid] = typeinfo */ lua_pop(L, 2); /* nil and typeinfo */ } else { ti = lua_touserdata(L, -1); lua_pop(L, 1); } return ti; } /* ======= Datum ======= */ static int luaP_datumtostring (lua_State *L) { luaP_Datum *d = lua_touserdata(L, 1); lua_pushstring(L, OutputFunctionCall(&d->ti->output, d->datum)); return 1; } static int luaP_datumgc (lua_State *L) { luaP_Datum *d = lua_touserdata(L, 1); if (d->issaved) pfree(DatumGetPointer(d->datum)); return 0; } static int luaP_datumsave (lua_State *L) { luaP_Datum *d = luaP_toudata(L, 1, PLLUA_DATUM); if (d == NULL) { const char *msg = lua_pushfstring(L, "%s expected, got %s", PLLUA_DATUM, luaL_typename(L, 1)); luaL_argerror(L, 1, msg); } if (!d->ti->byval) { /* by reference? */ Size l = datumGetSize(d->datum, false, d->ti->len); MemoryContext mcxt = luaP_getmemctxt(L); MemoryContext m = MemoryContextSwitchTo(mcxt); Pointer copy = palloc(l); Pointer dp = DatumGetPointer(d->datum); memcpy(copy, dp, l); MemoryContextSwitchTo(m); pfree(dp); d->issaved = 1; d->datum = PointerGetDatum(copy); } return 1; } static luaP_Datum *luaP_pushrawdatum (lua_State *L, Datum dat, luaP_Typeinfo *ti) { luaP_Datum *d = lua_newuserdata(L, sizeof(luaP_Datum)); d->issaved = 0; d->datum = dat; d->ti = ti; lua_pushlightuserdata(L, (void *) PLLUA_DATUM); lua_rawget(L, LUA_REGISTRYINDEX); /* Datum_MT */ lua_setmetatable(L, -2); return d; } /* ======= Trigger ======= */ static void luaP_preptrigger (lua_State *L, TriggerData *tdata) { const char *relname; char *namespace; lua_pushglobaltable(L); lua_pushstring(L, PLLUA_TRIGGERVAR); lua_newtable(L); /* when */ if (TRIGGER_FIRED_BEFORE(tdata->tg_event)) lua_pushstring(L, "before"); else if (TRIGGER_FIRED_AFTER(tdata->tg_event)) lua_pushstring(L, "after"); else elog(ERROR, "[pllua]: unknown trigger 'when' event"); lua_setfield(L, -2, "when"); /* level */ if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) lua_pushstring(L, "row"); else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event)) lua_pushstring(L, "statement"); else elog(ERROR, "[pllua]: unknown trigger 'level' event"); lua_setfield(L, -2, "level"); /* operation */ if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) lua_pushstring(L, "insert"); else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) lua_pushstring(L, "update"); else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) lua_pushstring(L, "delete"); #if PG_VERSION_NUM >= 80400 else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event)) lua_pushstring(L, "truncate"); #endif else elog(ERROR, "[pllua]: unknown trigger 'operation' event"); lua_setfield(L, -2, "operation"); /* relation */ relname = NameStr(tdata->tg_relation->rd_rel->relname); namespace = get_namespace_name(tdata->tg_relation->rd_rel->relnamespace); lua_createtable(L, 0, 3); lua_pushstring(L, relname); lua_setfield(L, -2, "name"); luaP_pushdesctable(L, tdata->tg_relation->rd_att); lua_push_oidstring(L, (int) tdata->tg_relation->rd_id); lua_pushvalue(L, -2); /* attribute table */ lua_rawset(L, LUA_REGISTRYINDEX); /* cache desc */ lua_setfield(L, -2, "attributes"); lua_pushinteger(L, (int) tdata->tg_relation->rd_id); lua_setfield(L, -2, "oid"); lua_pushstring(L, namespace); lua_setfield(L, -2, "namespace"); lua_setfield(L, -2, "relation"); /* row */ if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) { if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) { luaP_pushtuple_trg(L, tdata->tg_relation->rd_att, tdata->tg_newtuple, tdata->tg_relation->rd_id, 0); lua_setfield(L, -2, "row"); /* new row */ luaP_pushtuple_trg(L, tdata->tg_relation->rd_att, tdata->tg_trigtuple, tdata->tg_relation->rd_id, 1); lua_setfield(L, -2, "old"); /* old row */ } else { /* insert or delete */ luaP_pushtuple_trg(L, tdata->tg_relation->rd_att, tdata->tg_trigtuple, tdata->tg_relation->rd_id, 0); lua_setfield(L, -2, "row"); /* old row */ } } /* trigger name */ lua_pushstring(L, tdata->tg_trigger->tgname); lua_setfield(L, -2, "name"); /* done setting up trigger; now set global */ lua_rawset(L, -3); /* _G[PLLUA_TRIGGERVAR] = table */ lua_pop(L, 1); /* _G */ } static Datum luaP_gettriggerresult (lua_State *L) { HeapTuple tuple; lua_getglobal(L, PLLUA_TRIGGERVAR); lua_getfield(L, -1, "row"); tuple = luaP_totuple(L); lua_pop(L, 2); return PointerGetDatum(tuple); } static void luaP_cleantrigger (lua_State *L) { rtds_tryclean(rtds_get_current()); //fi->functx; lua_pushglobaltable(L); lua_pushstring(L, PLLUA_TRIGGERVAR); lua_pushnil(L); lua_rawset(L, -3); lua_pop(L, 1); /* _G */ } /* ======= luaP_newstate: create a new Lua VM ======= */ static int luaP_modinit (lua_State *L) { int status; if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "[pllua]: could not connect to SPI manager"); status = SPI_execute(PLLUA_INIT_EXIST, 1, 0); if (status < 0) lua_pushfstring(L, "[pllua]: error reading pllua.init: %d", status); if (SPI_processed == 0) /* pllua.init does not exist? */ status = 0; else { status = SPI_execute(PLLUA_INIT_LIST, 1, 0); if (status < 0) lua_pushfstring(L, "[pllua]: error loading modules from pllua.init: %d", status); else { status = 0; if (SPI_processed > 0) { /* any rows? */ unsigned int i; for (i = 0; i < SPI_processed; i++) { bool isnull; /* push module name */ lua_pushstring(L, text2string(heap_getattr(SPI_tuptable->vals[i], 1, SPI_tuptable->tupdesc, &isnull))); /* first column */ lua_getglobal(L, "require"); lua_pushvalue(L, -2); /* module name */ status = lua_pcall(L, 1, 1, 0); if (status) break; /* error? */ if (!lua_isnil(L, -1)) { /* make sure global table is set? */ lua_pushglobaltable(L); lua_pushvalue(L, -3); /* key */ lua_pushvalue(L, -3); /* value */ lua_rawset(L, -3); /* _G[key] = value */ lua_pop(L, 1); /* _G */ } else lua_pop(L, 1); /* module name */ } } } } if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "[pllua]: could not disconnect from SPI manager"); return status; } static int luaP_globalnewindex (lua_State *L) { return luaL_error(L, "attempt to set global var '%s'", lua_tostring(L, -2)); } static int luaP_setshared (lua_State *L) { luaL_checkstring(L, 1); if (lua_gettop(L) == 1) lua_pushboolean(L, 1); lua_settop(L, 2); /* key, value */ lua_pushvalue(L, -1); lua_insert(L, -3); lua_pushglobaltable(L); lua_insert(L, -3); lua_rawset(L, -3); lua_pop(L, 1); /* global table */ return 1; } static int luaP_print (lua_State *L) { int i, n = lua_gettop(L); /* nargs */ const char *s; luaL_Buffer b; luaL_buffinit(L, &b); lua_getglobal(L, "tostring"); for (i = 1; i <= n; i++) { lua_pushvalue(L, -1); /* tostring */ lua_pushvalue(L, i); /* arg */ lua_call(L, 1, 1); s = lua_tostring(L, -1); if (s == NULL) return luaL_error(L, "cannot convert to string"); if (i > 1) luaL_addchar(&b, '\t'); luaL_addlstring(&b, s, strlen(s)); lua_pop(L, 1); } luaL_pushresult(&b); s = lua_tostring(L, -1); info(s); lua_pop(L, 1); return 0; } #define PLLUA_REPORT(elevel) do{\ if (lua_type(L, 1)== LUA_TTABLE){\ luatable_report(L, elevel);\ return 0;\ }\ luaL_checkstring(L, 1);\ ereport(elevel, (errmsg("%s", lua_tostring(L, 1))));\ return 0;\ }while(0) static int luaP_info (lua_State *L) { PLLUA_REPORT(INFO); } static int luaP_log (lua_State *L) { PLLUA_REPORT(LOG); } static int luaP_notice (lua_State *L) { PLLUA_REPORT(NOTICE); } static int luaP_warning (lua_State *L) { PLLUA_REPORT(WARNING); } #undef PLLUA_REPORT static int luaP_fromstring (lua_State *L) { int oid = luaP_gettypeoid(luaL_checkstring(L, 1)); const char *s = luaL_checkstring(L, 2); luaP_Typeinfo *ti = luaP_gettypeinfo(L, oid); int inoid = oid; Datum v; /* from getTypeIOParam in lsyscache.c */ if (ti->type == TYPTYPE_BASE && OidIsValid(ti->elem)) inoid = ti->elem; v = InputFunctionCall(&ti->input, (char *) s, inoid, 0); /* typmod = 0 */ luaP_pushdatum(L, v, oid); return 1; } #ifdef PLLUA_DEBUG static int luaP_memstat(lua_State *L) { (void)L; MemoryContextStats(TopMemoryContext); return 0; } #endif static const luaL_Reg luaP_funcs[] = { {"assert", luaB_assert}, {"error", luaB_error}, {"fromstring", luaP_fromstring}, {"info", luaP_info}, {"log", luaP_log}, #ifdef PLLUA_DEBUG {"memstat", luaP_memstat}, #endif {"notice", luaP_notice}, {"pcall", subt_luaB_pcall}, {"pgfunc", get_pgfunc}, {"print", luaP_print}, {"setshared", luaP_setshared}, {"subtransaction", use_subtransaction}, {"warning", luaP_warning}, {"xpcall", subt_luaB_xpcall}, {NULL, NULL} }; void luaP_close (lua_State *L) { MemoryContext mcxt = luaP_getmemctxt(L); MemoryContextDelete(mcxt); lua_close(L); } lua_State *luaP_newstate (int trusted) { int status; MemoryContext mcxt = AllocSetContextCreate(TopMemoryContext, "PL/Lua context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); lua_State *L = luaL_newstate(); lua_atpanic(L, luaP_panic); /* version */ lua_pushliteral(L, PLLUA_VERSION); lua_setglobal(L, "_PLVERSION"); /* memory context */ lua_pushlightuserdata(L, p_lua_mem_cxt); lua_pushlightuserdata(L, (void *) mcxt); lua_rawset(L, LUA_REGISTRYINDEX); lua_pushlightuserdata(L, p_lua_master_state); lua_pushlightuserdata(L, (void *) L); lua_rawset(L, LUA_REGISTRYINDEX); /* core libs */ if (trusted) { const luaL_Reg luaP_trusted_libs[] = { #if LUA_VERSION_NUM <= 501 {"", luaopen_base}, #else {"_G", luaopen_base}, {LUA_COLIBNAME, luaopen_coroutine}, #if LUA_VERSION_NUM < 503 {LUA_BITLIBNAME, luaopen_bit32}, #endif #endif {LUA_TABLIBNAME, luaopen_table}, {LUA_STRLIBNAME, luaopen_string}, {LUA_MATHLIBNAME, luaopen_math}, #ifdef LUA_JITLIBNAME {LUA_JITLIBNAME, luaopen_jit}, #endif {LUA_OSLIBNAME, luaopen_os}, /* restricted */ {LUA_LOADLIBNAME, luaopen_package}, /* just for pllua.init modules */ {NULL, NULL} }; const char *os_funcs[] = {"date", "clock", "time", "difftime", NULL}; const luaL_Reg *reg = luaP_trusted_libs; const char **s = os_funcs; for (; reg->func; reg++) { #if LUA_VERSION_NUM <= 501 lua_pushcfunction(L, reg->func); lua_pushstring(L, reg->name); lua_call(L, 1, 0); #else luaL_requiref(L, reg->name, reg->func, 1); /* set in global table */ lua_pop(L, 1); /* remove lib */ #endif } /* restricted os lib */ lua_getglobal(L, LUA_OSLIBNAME); lua_newtable(L); /* new os */ for (; *s; s++) { lua_getfield(L, -2, *s); lua_setfield(L, -2, *s); } lua_setglobal(L, LUA_OSLIBNAME); lua_pop(L, 1); } else luaL_openlibs(L); register_error_mt(L); register_funcinfo_mt(L); register_int64(L); /* setup typeinfo and raw datum MTs */ lua_pushlightuserdata(L, (void *) PLLUA_TYPEINFO); lua_newtable(L); /* luaP_Typeinfo MT */ lua_pushcfunction(L, luaP_typeinfogc); lua_setfield(L, -2, "__gc"); lua_rawset(L, LUA_REGISTRYINDEX); lua_pushlightuserdata(L, (void *) PLLUA_DATUM); lua_newtable(L); /* luaP_Datum MT */ lua_pushcfunction(L, luaP_datumtostring); lua_setfield(L, -2, "__tostring"); lua_pushcfunction(L, luaP_datumgc); lua_setfield(L, -2, "__gc"); lua_createtable(L, 0, 1); lua_pushcfunction(L, luaP_datumsave); lua_setfield(L, -2, "save"); lua_setfield(L, -2, "__index"); lua_rawset(L, LUA_REGISTRYINDEX); /* load pllua.init modules */ status = luaP_modinit(L); if (status != 0) /* SPI or module loading error? */ elog(ERROR, "%s", lua_tostring(L, -1)); /* set alias for _G */ lua_pushglobaltable(L); lua_setglobal(L, PLLUA_SHAREDVAR); /* _G.shared = _G */ /* globals */ lua_pushglobaltable(L); luaP_register(L, luaP_funcs); lua_pop(L, 1); /* SPI */ luaP_registerspi(L); lua_setglobal(L, PLLUA_SPIVAR); if (trusted) { const char *package_keys[] = { /* to be removed */ "preload", "loadlib", "loaders", "seeall", NULL}; const char *global_keys[] = { /* to be removed */ "require", "module", "dofile", "loadfile", "jit", NULL}; const char **s; /* clean package module */ lua_getglobal(L, "package"); for (s = package_keys; *s; s++) { lua_pushnil(L); lua_setfield(L, -2, *s); } lua_pop(L, 1); /* package table */ /* clean global table */ for (s = global_keys; *s; s++) { lua_pushnil(L); lua_setglobal(L, *s); } /* set _G as read-only */ lua_pushglobaltable(L); lua_createtable(L, 0, 1); lua_pushcfunction(L, luaP_globalnewindex); lua_setfield(L, -2, "__newindex"); lua_pushvalue(L, -1); /* metatable */ lua_setfield(L, -2, "__metatable"); lua_setmetatable(L, -2); lua_pop(L, 1); /* _G */ } return L; } /* ======= luaP_pushfunction ======= */ static luaP_Info *luaP_newinfo (lua_State *L, int nargs, int oid, Form_pg_proc procst) { Oid *argtype = procst->proargtypes.values; Oid rettype = procst->prorettype; bool isset = procst->proretset; luaP_Info *fi; int i; luaP_Typeinfo *ti; bool code_storage = ((nargs == 1) &&(argtype[0] == INTERNALOID) &&(rettype == INTERNALOID)); fi = lua_newuserdata(L, sizeof(luaP_Info) + nargs * sizeof(Oid)); fi->funcxt_wp = NULL; fi->oid = oid; fi->code_storage = code_storage; if(!code_storage){ /* read arg types */ for (i = 0; i < nargs; i++) { ti = luaP_gettypeinfo(L, argtype[i]); if (ti->type == TYPTYPE_PSEUDO) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("[pllua]: functions cannot take type '%s'", format_type_be(argtype[i])))); fi->arg[i] = argtype[i]; } /* read result type */ ti = luaP_gettypeinfo(L, rettype); if (ti->type == TYPTYPE_PSEUDO && rettype != VOIDOID && rettype != TRIGGEROID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("[pllua]: functions cannot return type '%s'", format_type_be(rettype)))); }else{ fi->arg[0] = INTERNALOID; } fi->vararg = rettype == TRIGGEROID; /* triggers are vararg */ fi->result = rettype; fi->result_isset = isset; fi->L = NULL; return fi; } /* test argument and return types, compile function, store it at registry, * and return info at the top of stack */ static void luaP_newfunction (lua_State *L, int oid, HeapTuple proc, luaP_Info **fi) { int nargs; /* fcinfo->nargs */ Form_pg_proc procst; bool isnull; Datum prosrc, *argname; const char *source, *fname; text *t; luaL_Buffer b; int init = (*fi == NULL); /* not initialized? */ const char *chunk_name = NULL;//PLLUA_CHUNKNAME; /* read proc info */ procst = (Form_pg_proc) GETSTRUCT(proc); prosrc = SysCacheGetAttr(PROCOID, proc, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "[pllua]: null prosrc"); nargs = procst->pronargs; /* get info userdata */ if (init) { lua_push_oidstring(L, oid); *fi = luaP_newinfo(L, nargs, oid, procst); } lua_pushlightuserdata(L, (void *) *fi); /* check #argnames */ if ((nargs > 0)&&((*fi)->code_storage == 0)) { int nnames; Datum argnames = SysCacheGetAttr(PROCOID, proc, Anum_pg_proc_proargnames, &isnull); if (!isnull) deconstruct_array(DatumGetArrayTypeP(argnames), TEXTOID, -1, false, 'i', &argname, NULL, &nnames); if (nnames != nargs) (*fi)->vararg = 1; else { /* check empty names */ int i; for (i = 0; i < nnames && !(*fi)->vararg; i++) { if (VARSIZE(DatumGetTextP(argname[i])) == VARHDRSZ) /* empty? */ (*fi)->vararg = 1; } } } /* prepare buffer */ luaL_buffinit(L, &b); /* read func name */ fname = NameStr(procst->proname); /* prepare header: "local upvalue,f f=function(" */ luaL_addlstring(&b, "local " PLLUA_LOCALVAR ",", 6 + sizeof(PLLUA_LOCALVAR)); luaL_addlstring(&b, fname, strlen(fname)); luaL_addchar(&b, ' '); luaL_addlstring(&b, fname, strlen(fname)); luaL_addlstring(&b, "=function(", 10); /* read arg names */ if((*fi)->code_storage == 0){ if ((*fi)->vararg) luaL_addlstring(&b, "...", 3); else { int i; for (i = 0; i < nargs; i++) { if (i > 0) luaL_addchar(&b, ','); t = DatumGetTextP(argname[i]); luaL_addlstring(&b, VARDATA(t), VARSIZE(t) - VARHDRSZ); } } } luaL_addlstring(&b, ") ", 2); /* read source */ t = DatumGetTextP(prosrc); luaL_addlstring(&b, VARDATA(t), VARSIZE(t) - VARHDRSZ); /* prepare footer: " end return f" */ luaL_addlstring(&b, " end return ", 12); luaL_addlstring(&b, fname, strlen(fname)); /* create function */ luaL_pushresult(&b); source = lua_tostring(L, -1); #if defined(PLLUA_DEBUG) chunk_name = source; #else chunk_name = fname; #endif if (luaL_loadbuffer(L, source, strlen(source), chunk_name)) luapg_error(L, "compile"); lua_remove(L, -2); /* source */ if (lua_pcall(L, 0, 1, 0)) luapg_error(L, "call"); rowstamp_set(&(*fi)->stamp, proc); /* row-stamp info */ lua_pushvalue(L, -1); /* func */ if (init) { lua_insert(L, -5); lua_rawset(L, LUA_REGISTRYINDEX); /* REG[light_info] = func */ lua_rawset(L, LUA_REGISTRYINDEX); /* REG[oid] = info */ } else { lua_insert(L, -3); lua_rawset(L, LUA_REGISTRYINDEX); /* REG[light_info] = func */ } } /* leaves function info (fi) for oid in stack */ static luaP_Info *luaP_pushfunction (lua_State *L, int oid) { luaP_Info *fi = NULL; HeapTuple proc; proc = SearchSysCache(PROCOID, ObjectIdGetDatum((Oid) oid), 0, 0, 0); if (!HeapTupleIsValid(proc)) elog(ERROR, "[pllua]: cache lookup failed for function %u", (Oid) oid); lua_push_oidstring(L, oid); lua_rawget(L, LUA_REGISTRYINDEX); if (lua_isnil(L, -1)) { /* not interned? */ lua_pop(L, 1); /* nil */ luaP_newfunction(L, oid, proc, &fi); } else { fi = lua_touserdata(L, -1); lua_pop(L, 1); /* info udata */ lua_pushlightuserdata(L, (void *) fi); if (rowstamp_check(&fi->stamp, proc)) /* not replaced? */ lua_rawget(L, LUA_REGISTRYINDEX); else { lua_pushnil(L); lua_rawset(L, LUA_REGISTRYINDEX); /* REG[old_light_info] = nil */ luaP_newfunction(L, oid, proc, &fi); } } ReleaseSysCache(proc); return fi; } /* ======= luaP_pushargs ======= */ static void luaP_pusharray (lua_State *L, char **p, int ndims, int *dims, int *lb, bits8 **bitmap, int *bitmask, luaP_Typeinfo *ti, Oid typeelem) { int i; lua_newtable(L); if (ndims == 1) { /* vector? */ for (i = 0; i < (*dims); i++) { if (*bitmap == NULL || ((**bitmap) & (*bitmask)) != 0) { /* not NULL? */ luaP_pushdatum(L, fetch_att(*p, ti->byval, ti->len), typeelem); lua_rawseti(L, -2, (*lb) + i); *p = att_addlength_pointer(*p, ti->len, *p); *p = (char *) att_align_nominal(*p, ti->align); } if (*bitmap) { /* advance bitmap pointer? */ *bitmask <<= 1; if (*bitmask == 0x100) { (*bitmap)++; *bitmask = 1; } } } } else { /* multidimensional array */ for (i = 0; i < (*dims); i++) { luaP_pusharray(L, p, ndims - 1, dims + 1, lb + 1, bitmap, bitmask, ti, typeelem); lua_rawseti(L, -2, (*lb) + i); } } } void luaP_pushdatum (lua_State *L, Datum dat, Oid type) { switch (type) { /* base and domain types */ case BOOLOID: lua_pushboolean(L, (int) (dat != 0)); break; case FLOAT4OID: lua_pushnumber(L, (lua_Number) DatumGetFloat4(dat)); break; case FLOAT8OID: lua_pushnumber(L, (lua_Number) DatumGetFloat8(dat)); break; case INT2OID: lua_pushinteger(L, (lua_Integer) DatumGetInt16(dat)); break; case INT4OID: lua_pushinteger(L, (lua_Integer) DatumGetInt32(dat)); break; case INT8OID: setInt64lua(L,(DatumGetInt64(dat))); break; case TEXTOID: lua_pushstring(L, text2string(dat)); break; case BPCHAROID: lua_pushstring(L, datum2string(dat, bpcharout)); break; case VARCHAROID: lua_pushstring(L, datum2string(dat, varcharout)); break; case REFCURSOROID: { Portal cursor = SPI_cursor_find(text2string(dat)); if (cursor != NULL) luaP_pushcursor(L, cursor); else lua_pushnil(L); break; } case RECORDOID: luaP_pushrecord(L, dat); break; default: { luaP_Typeinfo *ti; ti = luaP_gettypeinfo(L, type); switch (ti->type) { case TYPTYPE_COMPOSITE: { HeapTupleHeader tup = DatumGetHeapTupleHeader(dat); int i; const char *key; bool isnull; Datum value; lua_createtable(L, 0, ti->tupdesc->natts); for (i = 0; i < ti->tupdesc->natts; i++) { Form_pg_attribute att = ti->tupdesc->attrs[i]; key = NameStr(att->attname); value = GetAttributeByNum(tup, att->attnum, &isnull); if (!isnull) { luaP_pushdatum(L, value, att->atttypid); lua_setfield(L, -2, key); } } break; } case TYPTYPE_PSEUDO: if (type != VOIDOID) argerror(type); break; case TYPTYPE_BASE: case TYPTYPE_DOMAIN: if (ti->elem != 0 && ti->len == -1) { /* array? */ ArrayType *arr = DatumGetArrayTypeP(dat); char *p = ARR_DATA_PTR(arr); bits8 *bitmap = ARR_NULLBITMAP(arr); int bitmask = 1; luaP_Typeinfo *te = luaP_gettypeinfo(L, ti->elem); luaP_pusharray(L, &p, ARR_NDIM(arr), ARR_DIMS(arr), ARR_LBOUND(arr), &bitmap, &bitmask, te, ti->elem); } else luaP_pushrawdatum(L, dat, ti); break; #if PG_VERSION_NUM >= 80300 case TYPTYPE_ENUM: lua_pushinteger(L, (lua_Integer) DatumGetInt32(dat)); /* 4-byte long */ break; #endif default: argerror(type); } } } } static void luaP_pushargs (lua_State *L, FunctionCallInfo fcinfo, luaP_Info *fi) { int i; for (i = 0; i < fcinfo->nargs; i++) { if (fcinfo->argnull[i]) lua_pushnil(L); else luaP_pushdatum(L, fcinfo->arg[i], fi->arg[i]); } } /* ======= luaP_getresult ======= */ /* assume table is in top, returns size */ static int luaP_getarraydims (lua_State *L, int *ndims, int *dims, int *lb, luaP_Typeinfo *ti, Oid typeelem, int typmod, bool *hasnulls) { int size = 0; int nitems = 0; *ndims = -1; *hasnulls = false; lua_pushnil(L); while (lua_next(L, -2)) { if (lua_isnumber(L, -2)) { int n; int k = lua_tointeger(L, -2); /* set dims and lb */ if (*ndims < 0) { /* first visit? */ *lb = k; *dims = 1; } else { if (*lb > k) { *dims += *lb - k; *lb = k; } if (*dims - 1 + *lb < k) /* max < k? */ *dims = k - *lb + 1; } /* set ndims */ if (lua_type(L, -1) == LUA_TTABLE) { int d = -1, l = -1; if (*ndims == MAXDIM) elog(ERROR, "[pllua]: table exceeds max number of dimensions"); if (*ndims > 1) { d = dims[1]; l = lb[1]; } size += luaP_getarraydims(L, &n, dims + 1, lb + 1, ti, typeelem, typmod, hasnulls); if (*ndims > 1) { /* update dims and bounds? */ if (l < lb[1]) { lb[1] = l; *hasnulls = true; } if (d + l > dims[1] + lb[1]) { dims[1] = d + l - lb[1]; *hasnulls = true; } } } else { bool isnull; Datum d = luaP_todatum(L, typeelem, typmod, &isnull, -1); Pointer v = DatumGetPointer(d); n = 0; if (ti->len == -1) /* varlena? */ v = (Pointer) PG_DETOAST_DATUM(d); size = att_addlength_pointer(size, ti->len, v); size = att_align_nominal(size, ti->align); if (size > MaxAllocSize) elog(ERROR, "[pllua]: array size exceeds the maximum allowed"); } n++; if (*ndims < 0) *ndims = n; else if (*ndims != n) elog(ERROR, "[pllua]: table is asymetric"); } nitems++; lua_pop(L, 1); } if (!(*hasnulls)) *hasnulls = (nitems > 0 && nitems != *dims); return size; } static void luaP_toarray (lua_State *L, char **p, int ndims, int *dims, int *lb, bits8 **bitmap, int *bitmask, int *bitval, luaP_Typeinfo *ti, Oid typeelem, int typmod) { int i; bool isnull; if (ndims == 1) { /* vector? */ for (i = 0; i < (*dims); i++) { Pointer v; lua_rawgeti(L, -1, (*lb) + i); v = DatumGetPointer(luaP_todatum(L, typeelem, typmod, &isnull, -1)); if (!isnull) { *bitval |= *bitmask; if (ti->len > 0) { if (ti->byval) store_att_byval(*p, PointerGetDatum(v), ti->len); else memmove(*p, v, ti->len); *p += att_align_nominal(ti->len, ti->align); } else { int inc; Assert(!ti->byval); inc = att_addlength_pointer(0, ti->len, v); memmove(*p, v, inc); *p += att_align_nominal(inc, ti->align); } if (!ti->byval) pfree(v); } else if (!(*bitmap)) elog(ERROR, "[pllua]: no support for null elements"); if (*bitmap) { *bitmask <<= 1; if (*bitmask == 0x100) { *(*bitmap)++ = *bitval; *bitval = 0; *bitmask = 1; } } lua_pop(L, 1); } if (*bitmap && *bitmask != 1) **bitmap = *bitval; } else { /* multidimensional array */ for (i = 0; i < (*dims); i++) { lua_rawgeti(L, -1, (*lb) + i); luaP_toarray(L, p, ndims - 1, dims + 1, lb + 1, bitmap, bitmask, bitval, ti, typeelem, typmod); lua_pop(L, 1); } } } Datum luaP_todatum (lua_State *L, Oid type, int typmod, bool *isnull, int idx) { Datum dat = 0; /* NULL */ *isnull = lua_isnil(L, idx); if (!(*isnull || type == VOIDOID)) { switch (type) { /* base and domain types */ case BOOLOID: dat = BoolGetDatum(lua_toboolean(L, idx)); break; case FLOAT4OID: dat = Float4GetDatum((float4) lua_tonumber(L, idx)); break; case FLOAT8OID: dat = Float8GetDatum((float8) lua_tonumber(L, idx)); break; case INT2OID: dat = Int16GetDatum(lua_tointeger(L, idx)); break; case INT4OID: dat = Int32GetDatum(lua_tointeger(L, idx)); break; case INT8OID: dat = Int64GetDatum(get64lua(L, idx)); break; case TEXTOID: { const char *s = lua_tostring(L, idx); if (s == NULL) elog(ERROR, "[pllua]: string expected for datum conversion, got %s", lua_typename(L, lua_type(L, idx))); dat = string2text(s); break; } case REFCURSOROID: { Portal cursor = luaP_tocursor(L, idx); dat = string2text(cursor->name); break; } default: { luaP_Typeinfo *ti; ti = luaP_gettypeinfo(L, type); switch (ti->type) { case TYPTYPE_COMPOSITE: if (lua_type(L, idx) == LUA_TTABLE) { int i; luaP_Buffer *b; if (lua_type(L, idx) != LUA_TTABLE) elog(ERROR, "[pllua]: table expected for record result, got %s", lua_typename(L, lua_type(L, idx))); /* create tuple */ b = luaP_getbuffer(L, ti->tupdesc->natts); for (i = 0; i < ti->tupdesc->natts; i++) { lua_getfield(L, idx, NameStr(ti->tupdesc->attrs[i]->attname)); /* only simple types allowed in record */ b->value[i] = luaP_todatum(L, ti->tupdesc->attrs[i]->atttypid, ti->tupdesc->attrs[i]->atttypmod, b->null + i, idx); lua_pop(L, 1); } /* make copy in upper executor memory context */ dat = PointerGetDatum(SPI_returntuple(heap_form_tuple(ti->tupdesc, b->value, b->null), ti->tupdesc)); } else { /* tuple */ HeapTuple tuple = luaP_casttuple(L, ti->tupdesc); if (tuple == NULL) elog(ERROR, "[pllua]: table or tuple expected for record result, got %s", lua_typename(L, lua_type(L, idx))); dat = PointerGetDatum(SPI_returntuple(tuple, ti->tupdesc)); } break; case TYPTYPE_BASE: case TYPTYPE_DOMAIN: if (ti->elem != 0 && ti->len == idx) { /* array? */ luaP_Typeinfo *te; int ndims, dims[MAXDIM], lb[MAXDIM]; int i, size; bool hasnulls; ArrayType *a; if (lua_type(L, idx) != LUA_TTABLE) elog(ERROR, "[pllua]: table expected for array conversion, got %s", lua_typename(L, lua_type(L, idx))); te = luaP_gettypeinfo(L, ti->elem); for (i = 0; i < MAXDIM; i++) dims[i] = lb[i] = idx; size = luaP_getarraydims(L, &ndims, dims, lb, te, ti->elem, typmod, &hasnulls); if (size == 0) { /* empty array? */ a = (ArrayType *) SPI_palloc(sizeof(ArrayType)); SET_VARSIZE(a, sizeof(ArrayType)); a->ndim = 0; a->dataoffset = 0; a->elemtype = ti->elem; } else { int nitems = 1; int offset; char *p; bits8 *bitmap; int bitmask = 1; int bitval = 0; for (i = 0; i < ndims; i++) { nitems *= dims[i]; if (nitems > MaxArraySize) elog(ERROR, "[pllua]: array size exceeds maximum allowed"); } if (hasnulls) { offset = ARR_OVERHEAD_WITHNULLS(ndims, nitems); size += offset; } else { offset = 0; size += ARR_OVERHEAD_NONULLS(ndims); } a = (ArrayType *) SPI_palloc(size); SET_VARSIZE(a, size); a->ndim = ndims; a->dataoffset = offset; a->elemtype = ti->elem; memcpy(ARR_DIMS(a), dims, ndims * sizeof(int)); memcpy(ARR_LBOUND(a), lb, ndims * sizeof(int)); p = ARR_DATA_PTR(a); bitmap = ARR_NULLBITMAP(a); luaP_toarray(L, &p, ndims, dims, lb, &bitmap, &bitmask, &bitval, te, ti->elem, typmod); } dat = PointerGetDatum(a); } else { luaP_Datum *d = luaP_toudata(L, idx, PLLUA_DATUM); if (d == NULL) elog(ERROR, "[pllua]: raw datum expected for datum conversion, got %s", lua_typename(L, lua_type(L, idx))); dat = datumcopy(d->datum, ti); } break; #if PG_VERSION_NUM >= 80300 case TYPTYPE_ENUM: dat = Int32GetDatum(lua_tointeger(L, idx)); break; #endif case TYPTYPE_PSEUDO: default: resulterror(type); } } } } return dat; } static Datum luaP_getresult (lua_State *L, FunctionCallInfo fcinfo, Oid type) { Datum dat = luaP_todatum(L, type, 0, &fcinfo->isnull, -1); lua_settop(L, 0); return dat; } /* ======= luaP_callhandler ======= */ static void luaP_cleanthread (lua_State *L, lua_State **thread, luaP_Info *fi) { fi->funcxt_wp = rtds_free_if_not_used(fi->funcxt_wp); lua_pushlightuserdata(L, (void *) *thread); lua_pushnil(L); lua_rawset(L, LUA_REGISTRYINDEX); *thread = NULL; } Datum luaP_validator (lua_State *L, Oid oid) { if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "[pllua]: could not connect to SPI manager"); PG_TRY(); { luaP_pushfunction(L, (int) oid); lua_pop(L, 1); } PG_CATCH(); { if (L != NULL) { lua_settop(L, 0); /* clear Lua stack */ luaP_cleantrigger(L); } PG_RE_THROW(); } PG_END_TRY(); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "[pllua]: could not disconnect from SPI manager"); return 0; /* VOID */ } Datum luaP_callhandler (lua_State *L, FunctionCallInfo fcinfo) { Datum retval = 0; int base = 0; luaP_Info *fi; RTupDescStack prev; bool istrigger; if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "[pllua]: could not connect to SPI manager"); istrigger = CALLED_AS_TRIGGER(fcinfo); fi = luaP_pushfunction(L, (int) fcinfo->flinfo->fn_oid); if (fi->code_storage == 1){ luaL_error(L, "attempt to call non-callable function"); } if (fi->funcxt_wp == NULL){ fi->funcxt_wp = rtds_initStack_weak(L, &fi->funcxt_wp); } rtds_inuse(fi->funcxt_wp); prev = rtds_set_current(fi->funcxt_wp); PG_TRY(); { if ((fi->result == TRIGGEROID && !istrigger) || (fi->result != TRIGGEROID && istrigger)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("[pllua]: trigger function can only be called as trigger"))); if (istrigger) { TriggerData *trigdata = (TriggerData *) fcinfo->context; int i, nargs; luaP_preptrigger(L, trigdata); /* set global trigger table */ nargs = trigdata->tg_trigger->tgnargs; for (i = 0; i < nargs; i++) /* push args */ lua_pushstring(L, trigdata->tg_trigger->tgargs[i]); //trigger call if (lua_pcall(L, nargs, 0, 0)) { #if defined(PLLUA_DEBUG) luapg_error(L, getLINE()); #else luapg_error(L, "runtime"); #endif } if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event) && TRIGGER_FIRED_BEFORE(trigdata->tg_event)) /* return? */ retval = luaP_gettriggerresult(L); luaP_cleantrigger(L); } else { /* called as function */ if (fi->result_isset) { /* SETOF? */ int status, hasresult; ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; if (fi->L == NULL) { /* first call? */ if (!rsi || !IsA(rsi, ReturnSetInfo) || (rsi->allowedModes & SFRM_ValuePerCall) == 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("[pllua]: set-valued function called in context" "that cannot accept a set"))); rsi->returnMode = SFRM_ValuePerCall; fi->L = lua_newthread(L); lua_pushlightuserdata(L, (void *) fi->L); lua_pushvalue(L, -2); /* thread */ lua_rawset(L, LUA_REGISTRYINDEX); lua_pop(L, 1); /* new thread */ } lua_xmove(L, fi->L, 1); /* function */ luaP_pushargs(fi->L, fcinfo, fi); #if LUA_VERSION_NUM <= 501 status = lua_resume(fi->L, fcinfo->nargs); #else status = lua_resume(fi->L, fi->L, fcinfo->nargs); #endif rtds_notinuse(fi->funcxt_wp); hasresult = !lua_isnone(fi->L, 1); if (status == LUA_YIELD && hasresult) { rsi->isDone = ExprMultipleResult; /* SRF: next */ retval = luaP_getresult(fi->L, fcinfo, fi->result); } else if (status == 0 || !hasresult) { /* last call? */ rsi->isDone = ExprEndResult; /* SRF: done */ fcinfo->isnull = true; retval = (Datum) 0; luaP_cleanthread(L, &fi->L, fi); } else { #if defined(PLLUA_DEBUG) luapg_error(fi->L, getLINE()); #else luapg_error(fi->L, "runtime"); #endif } } else { int status = 0; luaP_pushargs(L, fcinfo, fi); base = lua_gettop(L) - fcinfo->nargs; /* function index */ lua_pushcfunction(L, traceback); /* push traceback function */ lua_insert(L, base); /* put it under chunk and args */ //func call status = lua_pcall(L, fcinfo->nargs, 1, base); lua_remove(L, base); /* remove traceback function */ if (status){ fi->funcxt_wp = rtds_unref(fi->funcxt_wp); #if defined(PLLUA_DEBUG) luapg_error(L, getLINE()); #else luapg_error(L, "runtime"); #endif } fi->funcxt_wp = rtds_unref(fi->funcxt_wp); retval = luaP_getresult(L, fcinfo, fi->result); } } /* stack should be clean here: lua_gettop(L) == 0 */ } PG_CATCH(); { if (L != NULL) { luaP_cleantrigger(L);//fi->funcxt ref-- if (fi->result_isset && fi->L != NULL) /* clean thread? */ luaP_cleanthread(L, &fi->L, fi); lua_settop(L, 0); /* clear Lua stack */ } fcinfo->isnull = true; retval = (Datum) 0; PG_RE_THROW(); } PG_END_TRY(); rtds_set_current(prev); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "[pllua]: could not disconnect from SPI manager"); return retval; } #include "rtupdesc.h" #if PG_VERSION_NUM >= 90000 /* ======= luaP_inlinehandler ======= */ Datum luaP_inlinehandler (lua_State *L, const char *source) { RTupDescStack funcxt; RTupDescStack prev; int base = 0; int status = 0; if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "[pllua]: could not connect to SPI manager"); funcxt = rtds_initStack(L); rtds_inuse(funcxt); prev = rtds_set_current(funcxt); PG_TRY(); { const char *chunk_name = "anonymous";//PLLUA_CHUNKNAME; #if defined(PLLUA_DEBUG) chunk_name = source; #endif if (luaL_loadbuffer(L, source, strlen(source), chunk_name)) luapg_error(L, "compile"); base = lua_gettop(L) ; /* function index */ lua_pushcfunction(L, traceback); /* push traceback function */ lua_insert(L, base); /* put it under chunk and args */ status = lua_pcall(L, 0, 0, base); lua_remove(L, base); /* remove traceback function */ } PG_CATCH(); { funcxt = rtds_unref(funcxt); rtds_set_current(prev); if (L != NULL) { lua_settop(L, 0); /* clear Lua stack */ lua_gc(L, LUA_GCCOLLECT, 0); } PG_RE_THROW(); } PG_END_TRY(); funcxt = rtds_unref(funcxt); rtds_set_current(prev); if (status) { lua_gc(L, LUA_GCCOLLECT, 0); #if defined(PLLUA_DEBUG) setLINE(AT); luapg_error(L, getLINE()); #else luapg_error(L, "runtime"); #endif } if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "[pllua]: could not disconnect from SPI manager"); PG_RETURN_VOID(); } #endif pllua-1.1.0/plluacommon.h000066400000000000000000000046571304764261500153510ustar00rootroot00000000000000#ifndef PLLUACOMMON_H #define PLLUACOMMON_H /* PostgreSQL */ #include #include #include #include #include #if PG_VERSION_NUM >= 90300 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Lua */ #include #include #include #if LUA_VERSION_NUM <= 501 #define lua_pushglobaltable(L) lua_pushvalue(L, LUA_GLOBALSINDEX) #define lua_setuservalue lua_setfenv #define lua_getuservalue lua_getfenv #define lua_rawlen lua_objlen #define luaP_register(L,l) luaL_register(L, NULL, (l)) #else #define luaP_register(L,l) luaL_setfuncs(L, (l), 0) #endif #if !defined LUA_VERSION_NUM || LUA_VERSION_NUM==501 /* ** Adapted from Lua 5.2.0 */ void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); #endif #define PLLUA_VERSION "PL/Lua 1.1" #if defined(PLLUA_DEBUG) #include "pllua_debug.h" #define BEGINLUA int ____stk=lua_gettop(L) #define ENDLUA ____stk =lua_gettop(L)-____stk; if(0!=____stk) ereport(INFO, (errmsg("stk %s>%i",AT,____stk))) #define ENDLUAV(v) ____stk =(lua_gettop(L)-____stk-v); if(0!=____stk) ereport(INFO, (errmsg("stk %s>%i",AT,____stk))) #else #define BEGINLUA #define ENDLUA #define ENDLUAV(v) #endif #define lua_push_oidstring(L, oid) do\ {\ luaL_Buffer b;\ luaL_buffinit(L, &b);\ lua_pushinteger(L, oid);\ luaL_addstring(&b, "oid_");\ luaL_addvalue(&b);\ luaL_pushresult(&b);\ }while(0) typedef struct { const char* name; bool hasTraceback; } LVMInfo; /* get MemoryContext for state L */ MemoryContext luaP_getmemctxt (lua_State *L); lua_State *pllua_getmaster (lua_State *L); int pllua_getmaster_index(lua_State *L); #define lua_swap(L) lua_insert(L, -2) #define luaP_getfield(L, s) \ lua_pushlightuserdata((L), (void *)(s)); \ lua_rawget((L), LUA_REGISTRYINDEX) #define MTOLUA(state) {MemoryContext ___mcxt,___m;\ ___mcxt = luaP_getmemctxt(state); \ ___m = MemoryContextSwitchTo(___mcxt) #define MTOPG MemoryContextSwitchTo(___m);} int pg_to_regtype(char *typ_name); #endif // PLLUACOMMON_H pllua-1.1.0/plluaspi.c000066400000000000000000000761401304764261500146430ustar00rootroot00000000000000/* * plluaspi.c: PL/Lua SPI * Author: Luis Carvalho * Please check copyright notice at the bottom of pllua.h * $Id: plluaspi.c,v 1.19 2008/03/31 22:57:45 carvalho Exp $ */ #include "pllua.h" #include "pllua_xact_cleanup.h" #include "pllua_errors.h" #ifndef SPI_prepare_cursor #define SPI_prepare_cursor(cmd, nargs, argtypes, copts) \ SPI_prepare(cmd, nargs, argtypes) #endif #define SPI_plan void static const char PLLUA_BUFFER[] = "luaP_Buffer"; static const char PLLUA_TUPTABLE[] = "luaP_Tuptable"; static const char PLLUA_TUPLEMT[] = "tuple"; static const char PLLUA_P_TUPLEMT[] = "ptuple"; static const char PLLUA_PLANMT[] = "plan"; static const char PLLUA_CURSORMT[] = "cursor"; static const char PLLUA_TUPTABLEMT[] = "tupletable"; #include "rtupdesc.h" typedef struct luaP_Tuple { int changed; Oid relid; HeapTuple tuple; TupleDesc tupdesc; Datum *value; bool *null; RTupDesc *rtupdesc; } luaP_Tuple; typedef struct luaP_Tuptable { int size; Portal cursor; SPITupleTable *tuptable; TupleDesc tupdesc; RTupDesc *rtupdesc; } luaP_Tuptable; typedef struct luaP_Cursor { Portal cursor; RTupDesc *rtupdesc; void *tupleQueue; void *resptr; } luaP_Cursor; typedef struct luaP_Plan { int nargs; int issaved; SPI_plan *plan; Oid type[1]; } luaP_Plan; /* ======= Utils ======= */ static int luaP_typeerror (lua_State *L, int narg, const char *tname) { const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, luaL_typename(L, narg)); return luaL_argerror(L, narg, msg); } #define luaP_getfield(L, s) \ lua_pushlightuserdata((L), (void *)(s)); \ lua_rawget((L), LUA_REGISTRYINDEX) static void luaP_newmetatable (lua_State *L, const char *tname) { lua_newtable(L); lua_pushlightuserdata(L, (void *) tname); lua_pushvalue(L, -2); lua_rawset(L, LUA_REGISTRYINDEX); } void *luaP_toudata (lua_State *L, int ud, const char *tname) { void *p = lua_touserdata(L, ud); if (p != NULL) { /* value is userdata? */ if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ luaP_getfield(L, tname); /* get metatable */ if (lua_rawequal(L, -1, -2)) { /* MTs match? */ lua_pop(L, 2); /* MTs */ return p; } } } return NULL; } static void *luaP_checkudata (lua_State *L, int ud, const char *tname) { void *p = luaP_toudata(L, ud, tname); if (p == NULL) luaP_typeerror(L, ud, tname); return p; } void luaP_pushdesctable (lua_State *L, TupleDesc desc) { int i; lua_newtable(L); for (i = 0; i < desc->natts; i++) { lua_pushstring(L, NameStr(desc->attrs[i]->attname)); lua_pushinteger(L, i); lua_rawset(L, -3); /* t[att] = i */ } } static void luaP_pushtuple_cmn (lua_State *L, HeapTuple tuple, int readonly, RTupDesc* rtupdesc) { luaP_Tuple *t; TupleDesc tupleDesc; int i, n; BEGINLUA; tupleDesc = rtupdesc->tupdesc; n = tupleDesc->natts; t = lua_newuserdata(L, sizeof(luaP_Tuple) + n * (sizeof(Datum) + sizeof(bool))); t->value = (Datum *) (t + 1); t->null = (bool *) (t->value + n); t->rtupdesc = rtupdesc_ref(rtupdesc); for (i = 0; i < n; i++) { bool isnull; t->value[i] = heap_getattr(tuple, tupleDesc->attrs[i]->attnum, tupleDesc, &isnull); t->null[i] = isnull; } if (readonly) { t->changed = -1; } else { t->changed = 0; } t->tupdesc = 0; t->relid = 0; t->tuple = tuple; luaP_getfield(L, PLLUA_TUPLEMT); lua_setmetatable(L, -2); ENDLUAV(1); } static luaP_Tuple* luaP_PTuple_rawctr(lua_State * L, HeapTuple tuple, int readonly, RTupDesc* rtupdesc); static luaP_Tuple* luaP_pushPTuple(lua_State * L, size_t size, luaP_Tuple *ptr); #define LUAP_pushtuple_from_ptr(L,t) luaP_pushPTuple(L,0,t) void luaP_pushrecord(lua_State *L, Datum record){ HeapTupleHeader header = DatumGetHeapTupleHeader(record); TupleDesc tupdesc; HeapTupleData tuple; RTupDesc *shared_desc; PG_TRY(); { tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(header), HeapTupleHeaderGetTypMod(header)); /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(header); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_tableOid = InvalidOid; tuple.t_data = header; shared_desc = rtupdesc_ctor(L, tupdesc); luaP_pushtuple_cmn(L, &tuple, true, shared_desc); rtupdesc_unref(shared_desc); ReleaseTupleDesc(tupdesc); } PG_CATCH(); { luaL_error(L, "record to lua error"); } PG_END_TRY(); } //////////////////////////////////////////////////////////////////////////////// #define FETCH_CSR_Q 50 #define TUPLE_QUEUE_SIZE FETCH_CSR_Q + 1 typedef struct { int head, tail; luaP_Tuple* data[TUPLE_QUEUE_SIZE]; } TupleQueue, *TupleQueuePtr; static TupleQueuePtr tq_initQueue(lua_State *L) { TupleQueuePtr qp; MTOLUA(L); qp = (TupleQueuePtr) palloc(sizeof(TupleQueue)); MTOPG; qp -> head = qp -> tail = 0; return qp; } static int tq_isempty(TupleQueuePtr Q) { return (Q -> head == Q -> tail); } static void tq_enqueue(TupleQueuePtr Q, luaP_Tuple* n) { if (Q -> tail == TUPLE_QUEUE_SIZE - 1) Q -> tail = 0; else ++(Q -> tail); if (Q -> tail == Q -> head) { //Queue is full return; } Q -> data[Q -> tail] = n; } static luaP_Tuple* tq_dequeue(TupleQueuePtr Q) { if (tq_isempty(Q)) { return NULL; } if (Q -> head == TUPLE_QUEUE_SIZE - 1) Q -> head = 0; else ++(Q -> head); return Q -> data[Q -> head]; } #undef TUPLE_QUEUE_SIZE static int luaP_rowsaux (lua_State *L) { luaP_Cursor *c; luaP_Tuple* t; unsigned int i; uint32 processed = 0; BEGINLUA; c = (luaP_Cursor *) lua_touserdata(L, lua_upvalueindex(1)); if (c->tupleQueue && tq_isempty(c->tupleQueue)){ pfree(c->tupleQueue); c->tupleQueue = NULL; } if (c->tupleQueue == NULL){ PLLUA_PG_CATCH_RETHROW( SPI_cursor_fetch(c->cursor, 1, FETCH_CSR_Q); ); if (SPI_processed == 0){ SPI_freetuptable(SPI_tuptable); c->rtupdesc = rtupdesc_unref(c->rtupdesc); c->resptr = unregister_resource(c->resptr); SPI_cursor_close(c->cursor); c->cursor = NULL; lua_pushnil(L); ENDLUAV(1); return 1; } if(c->rtupdesc == NULL){ c->rtupdesc = rtupdesc_ctor(L,SPI_tuptable->tupdesc); } c->tupleQueue = tq_initQueue(L); for (i = 0; i < SPI_processed; i++) { HeapTuple tuple = SPI_tuptable->vals[i]; t = luaP_PTuple_rawctr(L, tuple, 1, c->rtupdesc); tq_enqueue(c->tupleQueue, t); processed++; } SPI_freetuptable(SPI_tuptable); } t = tq_dequeue(c->tupleQueue); LUAP_pushtuple_from_ptr(L, t); ENDLUAV(1); return 1; } #undef FETCH_CSR_Q //////////////////////////////////////////////////////////////////////////////// /* ======= Buffer ======= */ luaP_Buffer *luaP_getbuffer (lua_State *L, int n) { int i; luaP_Buffer *b; luaP_getfield(L, PLLUA_BUFFER); b = (luaP_Buffer *) lua_touserdata(L, -1); lua_pop(L, 1); if (b == NULL || n > b->size) { /* resize? */ lua_pushlightuserdata(L, (void *) PLLUA_BUFFER); b = (luaP_Buffer *) lua_newuserdata(L, sizeof(luaP_Buffer) + n * (sizeof(Datum) + sizeof(char))); b->size = n; b->value = (Datum *) (b + 1); b->null = (char *) (b->value + n); lua_rawset(L, LUA_REGISTRYINDEX); } for (i = 0; i < n; i++) { b->value[i] = 0; b->null[i] = 'n'; } return b; } static void luaP_fillbuffer (lua_State *L, int pos, Oid *type, luaP_Buffer *b) { lua_pushnil(L); while (lua_next(L, pos)) { int k = lua_tointeger(L, -2); if (k > 0) { bool isnull; k--; /* zero based */ b->value[k] = luaP_todatum(L, type[k], 0, &isnull, -1); b->null[k] = (isnull) ? 'n' : ' '; } lua_pop(L, 1); } } static void cursor_cleanup_p(void *d, int gccall){ luaP_Cursor *c = (luaP_Cursor *)d; if (c->tupleQueue){ luaP_Tuple* t = tq_dequeue(c->tupleQueue); while(t){ pfree(t); t = tq_dequeue(c->tupleQueue); } c->tupleQueue = NULL; c->rtupdesc = rtupdesc_unref(c->rtupdesc); } if (gccall == 0) c->resptr = NULL;//if garbage collected else c->resptr = unregister_resource(c->resptr);//transaction end } static void cursor_cleanup(void *d){ cursor_cleanup_p(d,0); } static int luaP_cursorgc (lua_State *L) { luaP_Cursor *c = (luaP_Cursor *) lua_touserdata(L, 1); if (c->tupleQueue){ cursor_cleanup_p(c, 1); if (PortalIsValid(c->cursor) && (c->cursor->status == PORTAL_READY)){ c->resptr = unregister_resource(c->resptr); SPI_cursor_close(c->cursor); } } return 0; } /* ======= Tuple ======= */ static int luaP_tuplegc (lua_State *L) { luaP_Tuple *t = (luaP_Tuple *) lua_touserdata(L, 1); rtupdesc_unref(t->rtupdesc); return 0; } static luaP_Tuple* luaP_PTuple_rawctr(lua_State * L, HeapTuple tuple, int readonly, RTupDesc* rtupdesc){ luaP_Tuple *t; TupleDesc tupleDesc; int i, n; tupleDesc = rtupdesc->tupdesc; n = tupleDesc->natts; MTOLUA(L); t = palloc(sizeof(luaP_Tuple) + n * (sizeof(Datum) + sizeof(bool))); MTOPG; t->value = (Datum *) (t + 1); t->null = (bool *) (t->value + n); t->rtupdesc = rtupdesc_ref(rtupdesc); for (i = 0; i < n; i++) { bool isnull; t->value[i] = heap_getattr(tuple, tupleDesc->attrs[i]->attnum, tupleDesc, &isnull); t->null[i] = isnull; } if (readonly) { t->changed = -1; } else { t->changed = 0; } t->tupdesc = 0; t->relid = 0; t->tuple = tuple; return t; } static luaP_Tuple* luaP_pushPTuple(lua_State * L, size_t size, luaP_Tuple *ptr) { luaP_Tuple ** udata = (luaP_Tuple **)lua_newuserdata(L, sizeof(luaP_Tuple *)); if (ptr == NULL){ MTOLUA(L); *udata = palloc(size); MTOPG; }else{ *udata = ptr; } luaP_getfield(L, PLLUA_P_TUPLEMT); lua_setmetatable(L, -2); return *udata; } static int luaP_p_tuplegc (lua_State *L) { luaP_Tuple *t = *(luaP_Tuple **) lua_touserdata(L, 1); rtupdesc_unref(t->rtupdesc); pfree(t); return 0; } static int luaP_p_tupleindex (lua_State *L) { const char *name; int i =-1; int idx = -1; luaP_Tuple *t = *(luaP_Tuple **) lua_touserdata(L, 1); if (lua_type(L, 2) == LUA_TNUMBER){ i = (int)lua_tonumber(L, 2); if (t->rtupdesc){ TupleDesc tupleDesc = rtupdesc_gettup(t->rtupdesc); i= i-1; //Lua[1] == C[0] if (tupleDesc == NULL){ ereport(WARNING, (errmsg("access to lost tuple desc at index %i", i+1))); lua_pushnil(L); return 1; } if ((i >= 0)&&(i < tupleDesc->natts)) { if (!t->null[i]) luaP_pushdatum(L, t->value[i], tupleDesc->attrs[i]->atttypid); else lua_pushnil(L); } else { //ereport(WARNING, (errmsg("tuple has no field at index %i", i+1))); //lua_pushnil(L); return luaL_error(L, "tuple has no field at index %d", i+1); } return 1; } lua_pushnil(L); return 1; } name = luaL_checkstring(L, 2); if (t->rtupdesc){ TupleDesc tupleDesc = rtupdesc_gettup(t->rtupdesc); if (tupleDesc == NULL){ ereport(WARNING, (errmsg("access to lost tuple desc at '%s'", name))); lua_pushnil(L); return 1; } for (i = 0; i< tupleDesc->natts; ++i){ if (strcmp(NameStr(tupleDesc->attrs[i]->attname),name) == 0){ idx = i; break; } } i = idx; if (i >= 0) { if (!t->null[i]) luaP_pushdatum(L, t->value[i], tupleDesc->attrs[i]->atttypid); else lua_pushnil(L); } else { //ereport(WARNING, (errmsg("tuple has no field '%s'", name))); //lua_pushnil(L); return luaL_error(L, "tuple has no field '%s'", name); } return 1; } lua_pushnil(L); return 1; } static int luaP_tupleindex (lua_State *L) { luaP_Tuple *t = (luaP_Tuple *) lua_touserdata(L, 1); const char *name = luaL_checkstring(L, 2); int i =-1; int idx = -1; if (t->rtupdesc){ TupleDesc tupleDesc = rtupdesc_gettup(t->rtupdesc); if (tupleDesc == NULL){ ereport(WARNING, (errmsg("access to lost tuple desc at '%s'", name))); lua_pushnil(L); return 1; } for (i = 0; i< tupleDesc->natts; ++i){ if (strcmp(NameStr(tupleDesc->attrs[i]->attname),name) == 0){ idx = i; break; } } i = idx; if (i >= 0) { if (!t->null[i]) luaP_pushdatum(L, t->value[i], tupleDesc->attrs[i]->atttypid); else lua_pushnil(L); } else { ereport(WARNING, (errmsg("tuple has no field '%s'", name))); lua_pushnil(L); } return 1; } //triggers data lua_push_oidstring(L, (int) t->relid); lua_rawget(L, LUA_REGISTRYINDEX); lua_getfield(L, -1, name); i = luaL_optinteger(L, -1, -1); if (i >= 0) { if (!t->null[i]) luaP_pushdatum(L, t->value[i], t->tupdesc->attrs[i]->atttypid); else lua_pushnil(L); } else lua_pushnil(L); return 1; } static int luaP_tuplenewindex (lua_State *L) { luaP_Tuple *t = (luaP_Tuple *) lua_touserdata(L, 1); const char *name = luaL_checkstring(L, 2); int i; if (t->changed == -1) /* read-only? */ return luaL_error(L, "tuple is read-only"); lua_push_oidstring(L, (int) t->relid); lua_rawget(L, LUA_REGISTRYINDEX); lua_getfield(L, -1, name); i = luaL_optinteger(L, -1, -1); lua_settop(L, 3); if (i >= 0) { /* found? */ bool isnull; t->value[i] = luaP_todatum(L, t->tupdesc->attrs[i]->atttypid, t->tupdesc->attrs[i]->atttypmod, &isnull, -1); t->null[i] = isnull; t->changed = 1; } else return luaL_error(L, "column not found in relation: '%s'", name); return 0; } static int luaP_tupletostring (lua_State *L) { lua_pushfstring(L, "%s: %p", PLLUA_TUPLEMT, lua_touserdata(L, 1)); return 1; } void luaP_pushtuple_trg (lua_State *L, TupleDesc desc, HeapTuple tuple, Oid relid, int readonly) { luaP_Tuple *t; int i, n; BEGINLUA; n = desc->natts; t = lua_newuserdata(L, sizeof(luaP_Tuple) + n * (sizeof(Datum) + sizeof(bool))); if (readonly) { t->changed = -1; } else { t->changed = 0; } t->value = (Datum *) (t + 1); t->null = (bool *) (t->value + n); t->rtupdesc = 0; for (i = 0; i < n; i++) { bool isnull; t->value[i] = heap_getattr(tuple, desc->attrs[i]->attnum, desc, &isnull); t->null[i] = isnull; } t->tupdesc = desc; t->relid = relid; t->tuple = tuple; luaP_getfield(L, PLLUA_TUPLEMT); lua_setmetatable(L, -2); ENDLUAV(1); } /* adapted from SPI_modifytuple */ static HeapTuple luaP_copytuple (luaP_Tuple *t) { HeapTuple tuple = heap_form_tuple(t->tupdesc, t->value, t->null); /* copy identification info */ tuple->t_data->t_ctid = t->tuple->t_data->t_ctid; tuple->t_self = t->tuple->t_self; tuple->t_tableOid = t->tuple->t_tableOid; if (t->tupdesc->tdhasoid) HeapTupleSetOid(tuple, HeapTupleGetOid(t->tuple)); return SPI_copytuple(tuple); /* in upper mem context */ } static luaP_Tuple *luaP_checktuple (lua_State *L, int pos) { luaP_Tuple *t = (luaP_Tuple *) lua_touserdata(L, pos); if (t != NULL) { if (lua_getmetatable(L, pos)) { luaP_getfield(L, PLLUA_TUPLEMT); if (!lua_rawequal(L, -1, -2)) /* not tuple? */ t = NULL; lua_pop(L, 2); /* metatables */ } } return t; } /* tuple on top of stack */ HeapTuple luaP_totuple (lua_State *L) { luaP_Tuple *t = luaP_checktuple(L, -1); if (t == NULL) return NULL; /* not a tuple */ return (t->changed == 1) ? luaP_copytuple(t) : t->tuple; } /* tuple on top of stack */ HeapTuple luaP_casttuple (lua_State *L, TupleDesc tupdesc) { luaP_Tuple *t = luaP_checktuple(L, -1); int i; luaP_Buffer *b; if (t == NULL) return NULL; /* not a tuple */ lua_push_oidstring(L, (int) t->relid); lua_rawget(L, LUA_REGISTRYINDEX); /* tuple desc table */ b = luaP_getbuffer(L, tupdesc->natts); for (i = 0; i < tupdesc->natts; i++) { int j; lua_getfield(L, -1, NameStr(tupdesc->attrs[i]->attname)); j = luaL_optinteger(L, -1, -1); if (j >= 0) { if (t->changed == -1) /* read-only? */ b->value[i] = heap_getattr(t->tuple, t->tupdesc->attrs[j]->attnum, t->tupdesc, b->null + i); else { b->value[i] = t->value[j]; b->null[i] = t->null[j]; } } lua_pop(L, 1); } lua_pop(L, 1); /* desc table */ return heap_form_tuple(tupdesc, b->value, b->null); } /* ======= TupleTable ======= */ static void luaP_pushtuptable (lua_State *L, Portal cursor) { luaP_Tuptable *t; BEGINLUA; luaP_getfield(L, PLLUA_TUPTABLE); t = (luaP_Tuptable *) lua_touserdata(L, -1); if (t == NULL) { /* not initialized? */ lua_pop(L, 1); t = (luaP_Tuptable *) lua_newuserdata(L, sizeof(luaP_Tuptable)); t->rtupdesc = 0; luaP_getfield(L, PLLUA_TUPTABLEMT); lua_setmetatable(L, -2); lua_pushlightuserdata(L, (void *) PLLUA_TUPTABLE); lua_pushvalue(L, -2); /* tuptable */ lua_rawset(L, LUA_REGISTRYINDEX); } t->size = SPI_processed; t->tuptable = SPI_tuptable; t->rtupdesc = rtupdesc_ctor(L,SPI_tuptable->tupdesc); if (cursor == NULL || (cursor != NULL && t->cursor != cursor)) { t->cursor = cursor; } /* reset tuptable env */ lua_newtable(L); /* env */ lua_setuservalue(L, -2); ENDLUAV(1); } static int luaP_tuptableindex (lua_State *L) { luaP_Tuptable *t = (luaP_Tuptable *) lua_touserdata(L, 1); int k = lua_tointeger(L, 2); if (k == 0) { /* attributes? */ lua_pushnil(L); } else if (k > 0 && k <= t->size) { lua_getuservalue(L, 1); lua_rawgeti(L, -1, k); if (lua_isnil(L, -1)) { /* not interned? */ lua_pop(L, 1); /* nil */ luaP_pushtuple_cmn(L, t->tuptable->vals[k - 1], 1, t->rtupdesc); lua_pushvalue(L, -1); lua_rawseti(L, -3, k); } }else{ lua_pushnil(L); } return 1; } static int luaP_tuptablelen (lua_State *L) { luaP_Tuptable *t = (luaP_Tuptable *) lua_touserdata(L, 1); lua_pushinteger(L, t->size); return 1; } static int luaP_tuptablegc (lua_State *L) { //in case SELECT * FROM get_rows('name'); not sure if collected... luaP_Tuptable *t = (luaP_Tuptable *) lua_touserdata(L, 1); rtupdesc_unref(t->rtupdesc); SPI_freetuptable(t->tuptable); return 0; } static int luaP_tuptabletostring (lua_State *L) { lua_pushfstring(L, "%s: %p", PLLUA_TUPTABLEMT, lua_touserdata(L, 1)); return 1; } /* ======= Cursor ======= */ void luaP_pushcursor (lua_State *L, Portal cursor) { luaP_Cursor *c = (luaP_Cursor *) lua_newuserdata(L, sizeof(luaP_Cursor)); c->cursor = cursor; c->rtupdesc = NULL; c->tupleQueue = NULL; c->resptr = register_resource(c, cursor_cleanup); luaP_getfield(L, PLLUA_CURSORMT); lua_setmetatable(L, -2); } Portal luaP_tocursor (lua_State *L, int pos) { luaP_Cursor *c = (luaP_Cursor *) luaP_checkudata(L, pos, PLLUA_CURSORMT); return c->cursor; } static int luaP_cursortostring (lua_State *L) { luaP_Cursor *c = (luaP_Cursor *) lua_touserdata(L, 1); lua_pushfstring(L, "%s: %p [%s]", PLLUA_CURSORMT, c, c->cursor->name); return 1; } static int luaP_cursorfetch (lua_State *L) { luaP_Cursor *c = (luaP_Cursor *) luaP_checkudata(L, 1, PLLUA_CURSORMT); #if LUA_VERSION_NUM >= 503 SPI_cursor_fetch(c->cursor, 1, luaL_optinteger(L, 2, FETCH_ALL)); #else SPI_cursor_fetch(c->cursor, 1, luaL_optlong(L, 2, FETCH_ALL)); #endif if (SPI_processed > 0) /* any rows? */ luaP_pushtuptable(L, c->cursor); else lua_pushnil(L); return 1; } static int luaP_cursormove (lua_State *L) { luaP_Cursor *c = (luaP_Cursor *) luaP_checkudata(L, 1, PLLUA_CURSORMT); #if LUA_VERSION_NUM >= 503 SPI_cursor_move(c->cursor, 1, luaL_optinteger(L, 2, 0)); #else SPI_cursor_move(c->cursor, 1, luaL_optlong(L, 2, 0)); #endif return 0; } #if PG_VERSION_NUM >= 80300 static int luaP_cursorposfetch (lua_State *L) { luaP_Cursor *c = (luaP_Cursor *) luaP_checkudata(L, 1, PLLUA_CURSORMT); FetchDirection fd = (lua_toboolean(L, 3)) ? FETCH_RELATIVE : FETCH_ABSOLUTE; #if LUA_VERSION_NUM >= 503 SPI_scroll_cursor_fetch(c->cursor, fd, luaL_optinteger(L, 2, FETCH_ALL)); #else SPI_scroll_cursor_fetch(c->cursor, fd, luaL_optlong(L, 2, FETCH_ALL)); #endif if (SPI_processed > 0) /* any rows? */ luaP_pushtuptable(L, c->cursor); else lua_pushnil(L); return 1; } static int luaP_cursorposmove (lua_State *L) { luaP_Cursor *c = (luaP_Cursor *) luaP_checkudata(L, 1, PLLUA_CURSORMT); FetchDirection fd = (lua_toboolean(L, 3)) ? FETCH_RELATIVE : FETCH_ABSOLUTE; #if LUA_VERSION_NUM >= 503 SPI_scroll_cursor_move(c->cursor, fd, luaL_optinteger(L, 2, 0)); #else SPI_scroll_cursor_move(c->cursor, fd, luaL_optlong(L, 2, 0)); #endif return 0; } #endif static int luaP_cursorclose (lua_State *L) { luaP_Cursor *c = (luaP_Cursor *) luaP_checkudata(L, 1, PLLUA_CURSORMT); //c->resptr is null, cursor registered if used as upvalue c->resptr = unregister_resource(c->resptr); SPI_cursor_close(c->cursor); return 0; } /* ======= Plan ======= */ static int luaP_plangc (lua_State *L) { luaP_Plan *p = (luaP_Plan *) lua_touserdata(L, 1); if (p->issaved) SPI_freeplan(p->plan); return 0; } static int luaP_plantostring (lua_State *L) { lua_pushfstring(L, "plan: %p", lua_touserdata(L, 1)); return 1; } static int luaP_executeplan (lua_State *L) { luaP_Plan *p = (luaP_Plan *) luaP_checkudata(L, 1, PLLUA_PLANMT); bool ro = (bool) lua_toboolean(L, 3); #if LUA_VERSION_NUM >= 503 long c = luaL_optinteger(L, 4, 0); #else long c = luaL_optlong(L, 4, 0); #endif int result = -1; Datum *values = NULL; char *nulls = NULL; if (p->nargs > 0) { luaP_Buffer *b; if (lua_type(L, 2) != LUA_TTABLE) luaP_typeerror(L, 2, "table"); b = luaP_getbuffer(L, p->nargs); luaP_fillbuffer(L, 2, p->type, b); values = b->value; nulls = b->null; } PLLUA_PG_CATCH_RETHROW( result = SPI_execute_plan(p->plan, values, nulls, ro, c); ); if (result < 0) return luaL_error(L, "SPI_execute_plan error: %d", result); if (((result == SPI_OK_SELECT) ||(result == SPI_OK_UPDATE_RETURNING) ||(result == SPI_OK_INSERT_RETURNING) ||(result == SPI_OK_DELETE_RETURNING) )&& SPI_processed > 0) /* any rows? */ luaP_pushtuptable(L, NULL); else lua_pushnil(L); return 1; } static int luaP_saveplan (lua_State *L) { luaP_Plan *p = (luaP_Plan *) luaP_checkudata(L, 1, PLLUA_PLANMT); PLLUA_PG_CATCH_RETHROW( p->plan = SPI_saveplan(p->plan); ); switch (SPI_result) { case SPI_ERROR_ARGUMENT: return luaL_error(L, "null plan to be saved"); case SPI_ERROR_UNCONNECTED: return luaL_error(L, "unconnected procedure"); } p->issaved = 1; return 1; } static int luaP_issavedplan (lua_State *L) { luaP_Plan *p = (luaP_Plan *) luaP_checkudata(L, 1, PLLUA_PLANMT); lua_pushboolean(L, p->issaved); return 1; } static int luaP_getcursorplan (lua_State *L) { luaP_Plan *p = (luaP_Plan *) luaP_checkudata(L, 1, PLLUA_PLANMT); bool ro = (bool) lua_toboolean(L, 3); const char *name = lua_tostring(L, 4); Portal cursor = NULL; Datum *values = NULL; char *nulls = NULL; if (SPI_is_cursor_plan(p->plan)) { if (p->nargs > 0) { luaP_Buffer *b; if (lua_type(L, 2) != LUA_TTABLE) luaP_typeerror(L, 2, "table"); b = luaP_getbuffer(L, p->nargs); luaP_fillbuffer(L, 2, p->type, b); values = b->value; nulls = b->null; } PLLUA_PG_CATCH_RETHROW( cursor = SPI_cursor_open(name, p->plan, values, nulls, ro); ); if (cursor == NULL) return luaL_error(L, "error opening cursor"); luaP_pushcursor(L, cursor); } else lua_pushnil(L); return 1; } static int luaP_rowsplan (lua_State *L) { luaP_Plan *p = (luaP_Plan *) luaP_checkudata(L, 1, PLLUA_PLANMT); Portal cursor = NULL; Datum *values = NULL; char *nulls = NULL; if (!SPI_is_cursor_plan(p->plan)) return luaL_error(L, "Plan is not iterable"); if (p->nargs > 0) { luaP_Buffer *b; if (lua_type(L, 2) != LUA_TTABLE) luaP_typeerror(L, 2, "table"); b = luaP_getbuffer(L, p->nargs); luaP_fillbuffer(L, 2, p->type, b); values = b->value; nulls = b->null; } PLLUA_PG_CATCH_RETHROW( cursor = SPI_cursor_open(NULL, p->plan, values, nulls, 1); ); if (cursor == NULL) return luaL_error(L, "error opening cursor"); luaP_pushcursor(L, cursor); lua_pushboolean(L, 0); /* not inited */ lua_pushcclosure(L, luaP_rowsaux, 2); return 1; } /* ======= SPI ======= */ Oid luaP_gettypeoid (const char *type_name) { #if PG_VERSION_NUM < 80300 List *namelist = stringToQualifiedNameList(type_name, NULL); HeapTuple typetup = typenameType(NULL, makeTypeNameFromNameList(namelist)); #else List *namelist = stringToQualifiedNameList(type_name); HeapTuple typetup = typenameType(NULL, makeTypeNameFromNameList(namelist), NULL); #endif Oid typeoid = HeapTupleGetOid(typetup); ReleaseSysCache(typetup); list_free(namelist); return typeoid; } static int luaP_prepare (lua_State *L) { int nargs, cursoropt; const char *q = luaL_checkstring(L, 1); luaP_Plan *p; if (lua_isnoneornil(L, 2)) nargs = 0; else { if (lua_type(L, 2) != LUA_TTABLE) luaP_typeerror(L, 2, "table"); nargs = lua_rawlen(L, 2); } cursoropt = luaL_optinteger(L, 3, 0); (void)cursoropt; p = (luaP_Plan *) lua_newuserdata(L, sizeof(luaP_Plan) + nargs * sizeof(Oid)); p->issaved = 0; p->nargs = nargs; if (nargs > 0) { /* read types? */ lua_pushnil(L); while (lua_next(L, 2)) { int k = lua_tointeger(L, -2); if (k > 0) { const char *s = luaL_checkstring(L, -1); Oid type = luaP_gettypeoid(s); if (type == InvalidOid) return luaL_error(L, "invalid type to plan: %s", s); p->type[k - 1] = type; } lua_pop(L, 1); } } PLLUA_PG_CATCH_RETHROW( p->plan = SPI_prepare_cursor(q, nargs, p->type, cursoropt); ); if (SPI_result < 0) return luaL_error(L, "SPI_prepare error: %d", SPI_result); luaP_getfield(L, PLLUA_PLANMT); lua_setmetatable(L, -2); return 1; } static int luaP_execute (lua_State *L) { int result = -1; PLLUA_PG_CATCH_RETHROW( result = SPI_execute(luaL_checkstring(L, 1), (bool) lua_toboolean(L, 2), #if LUA_VERSION_NUM >= 503 luaL_optinteger(L, 3, 0)); #else luaL_optlong(L, 3, 0)); #endif ); if (result < 0) return luaL_error(L, "SPI_execute_plan error: %d", result); if (result == SPI_OK_SELECT && SPI_processed > 0) /* any rows? */ luaP_pushtuptable(L, NULL); else lua_pushnil(L); return 1; } /* returns cursor */ static int luaP_find (lua_State *L) { Portal cursor = SPI_cursor_find(luaL_checkstring(L, 1)); if (cursor != NULL) luaP_pushcursor(L, cursor); else lua_pushnil(L); return 1; } static int luaP_rows (lua_State *L) { Portal cursor; PLLUA_PG_CATCH_RETHROW( SPI_plan *p = SPI_prepare_cursor(luaL_checkstring(L, 1), 0, NULL, 0); if (SPI_result < 0) return luaL_error(L, "SPI_prepare error: %d", SPI_result); if (!SPI_is_cursor_plan(p)) return luaL_error(L, "Statement is not iterable"); cursor = SPI_cursor_open(NULL, p, NULL, NULL, 1); SPI_freeplan(p); if (cursor == NULL) return luaL_error(L, "error opening cursor"); luaP_pushcursor(L, cursor); lua_pushboolean(L, 0); /* not inited */ lua_pushcclosure(L, luaP_rowsaux, 2); ); return 1; } /* ======= luaP_registerspi ======= */ static const luaL_Reg luaP_Plan_funcs[] = { {"execute", luaP_executeplan}, {"save", luaP_saveplan}, {"issaved", luaP_issavedplan}, {"getcursor", luaP_getcursorplan}, {"rows", luaP_rowsplan}, {NULL, NULL} }; static const luaL_Reg luaP_Cursor_funcs[] = { {"fetch", luaP_cursorfetch}, {"move", luaP_cursormove}, #if PG_VERSION_NUM >= 80300 {"posfetch", luaP_cursorposfetch}, {"posmove", luaP_cursorposmove}, #endif {"close", luaP_cursorclose}, {NULL, NULL} }; static const luaL_Reg luaP_SPI_funcs[] = { {"prepare", luaP_prepare}, {"execute", luaP_execute}, {"find", luaP_find}, {"rows", luaP_rows}, {NULL, NULL} }; static const luaL_Reg luaP_Tuple_mt[] = { {"__index", luaP_tupleindex}, {"__newindex", luaP_tuplenewindex}, {"__tostring", luaP_tupletostring}, {"__gc", luaP_tuplegc}, {NULL, NULL} }; static const luaL_Reg luaP_p_Tuple_mt[] = { {"__index", luaP_p_tupleindex}, //{"__newindex", luaP_tuplenewindex}, //{"__tostring", luaP_tupletostring}, {"__gc", luaP_p_tuplegc}, {NULL, NULL} }; static const luaL_Reg luaP_Tuptable_mt[] = { {"__index", luaP_tuptableindex}, {"__len", luaP_tuptablelen}, {"__tostring", luaP_tuptabletostring}, {"__gc", luaP_tuptablegc}, {NULL, NULL} }; static const luaL_Reg luaP_Cursor_mt[] = { {"__tostring", luaP_cursortostring}, {"__gc", luaP_cursorgc}, {NULL, NULL} }; static const luaL_Reg luaP_Plan_mt[] = { {"__gc", luaP_plangc}, {"__tostring", luaP_plantostring}, {NULL, NULL} }; void luaP_registerspi (lua_State *L) { /* tuple */ luaP_newmetatable(L, PLLUA_TUPLEMT); luaP_register(L, luaP_Tuple_mt); lua_pop(L, 1); /* ptuple */ luaP_newmetatable(L, PLLUA_P_TUPLEMT); luaP_register(L, luaP_p_Tuple_mt); lua_pop(L, 1); /* tuptable */ luaP_newmetatable(L, PLLUA_TUPTABLEMT); luaP_register(L, luaP_Tuptable_mt); lua_pop(L, 1); /* cursor */ luaP_newmetatable(L, PLLUA_CURSORMT); lua_newtable(L); luaP_register(L, luaP_Cursor_funcs); lua_setfield(L, -2, "__index"); luaP_register(L, luaP_Cursor_mt); lua_pop(L, 1); /* plan */ luaP_newmetatable(L, PLLUA_PLANMT); lua_newtable(L); luaP_register(L, luaP_Plan_funcs); lua_setfield(L, -2, "__index"); luaP_register(L, luaP_Plan_mt); lua_pop(L, 1); /* SPI */ lua_newtable(L); #if PG_VERSION_NUM >= 80300 lua_newtable(L); /* cursor options */ lua_pushinteger(L, CURSOR_OPT_BINARY); lua_setfield(L, -2, "binary"); lua_pushinteger(L, CURSOR_OPT_SCROLL); lua_setfield(L, -2, "scroll"); lua_pushinteger(L, CURSOR_OPT_NO_SCROLL); lua_setfield(L, -2, "noscroll"); lua_pushinteger(L, CURSOR_OPT_INSENSITIVE); lua_setfield(L, -2, "insensitive"); lua_pushinteger(L, CURSOR_OPT_HOLD); /* ignored */ lua_setfield(L, -2, "hold"); lua_pushinteger(L, CURSOR_OPT_FAST_PLAN); lua_setfield(L, -2, "fastplan"); lua_setfield(L, -2, "option"); #endif luaP_register(L, luaP_SPI_funcs); } pllua-1.1.0/rowstamp.h000066400000000000000000000014531304764261500146660ustar00rootroot00000000000000 #ifndef _ROWSTAMP_H_ #define _ROWSTAMP_H_ /* * Row version check changed in 8.3 */ #if PG_VERSION_NUM < 80300 #define ROWSTAMP_PRE83 #endif /* * Row version info */ typedef struct RowStamp { TransactionId xmin; #ifdef ROWSTAMP_PRE83 CommandId cmin; #else ItemPointerData tid; #endif } RowStamp; static void rowstamp_set(RowStamp *stamp, HeapTuple tup) { stamp->xmin = HeapTupleHeaderGetXmin(tup->t_data); #ifdef ROWSTAMP_PRE83 stamp->cmin = HeapTupleHeaderGetCmin(tup->t_data); #else stamp->tid = tup->t_self; #endif } static bool rowstamp_check(RowStamp *stamp, HeapTuple tup) { return stamp->xmin == HeapTupleHeaderGetXmin(tup->t_data) #ifdef ROWSTAMP_PRE83 && stamp->cmin == HeapTupleHeaderGetCmin(tup->t_data); #else && ItemPointerEquals(&stamp->tid, &tup->t_self); #endif } #endif pllua-1.1.0/rtupdesc.c000066400000000000000000000027071304764261500146410ustar00rootroot00000000000000#include "rtupdesc.h" static int obj_count = 0; RTupDesc *rtupdesc_ctor(lua_State *state, TupleDesc tupdesc) { void* p; RTupDesc* rtupdesc = 0; MTOLUA(state); p = palloc(sizeof(RTupDesc)); if (p){ rtupdesc = (RTupDesc*)p; rtupdesc->ref_count = 1; rtupdesc->tupdesc = CreateTupleDescCopy(tupdesc); obj_count += 1; rtupdesc->weakNodeStk = rtds_push_current(p); } MTOPG; return rtupdesc; } RTupDesc *rtupdesc_ref(RTupDesc *rtupdesc) { if(rtupdesc) rtupdesc->ref_count += 1; return rtupdesc; } RTupDesc *rtupdesc_unref(RTupDesc *rtupdesc) { if(rtupdesc){ rtupdesc->ref_count -= 1; if (rtupdesc->ref_count == 0){ rtupdesc_dtor(rtupdesc); return NULL; } } return rtupdesc; } void rtupdesc_freedesc(RTupDesc *rtupdesc) { if (rtupdesc && rtupdesc->tupdesc){ FreeTupleDesc(rtupdesc->tupdesc); rtupdesc->tupdesc = NULL; obj_count -= 1; } } TupleDesc rtupdesc_gettup(RTupDesc *rtupdesc) { return (rtupdesc ? rtupdesc->tupdesc : NULL); } void rtupdesc_dtor(RTupDesc *rtupdesc) { if(rtupdesc){ rtds_remove_node(rtupdesc->weakNodeStk); if (rtupdesc->tupdesc){ FreeTupleDesc(rtupdesc->tupdesc); rtupdesc->tupdesc = NULL; obj_count -= 1; } pfree(rtupdesc); } } int rtupdesc_obj_count(void) { return obj_count; } pllua-1.1.0/rtupdesc.h000066400000000000000000000010371304764261500146410ustar00rootroot00000000000000#ifndef RTUPDESC_H #define RTUPDESC_H #include "plluacommon.h" #include "rtupdescstk.h" typedef struct _RTupDesc{ int ref_count; RTDNodePtr weakNodeStk; TupleDesc tupdesc; } RTupDesc; RTupDesc* rtupdesc_ctor(lua_State * state, TupleDesc tupdesc); RTupDesc* rtupdesc_ref(RTupDesc* rtupdesc); RTupDesc* rtupdesc_unref(RTupDesc* rtupdesc); TupleDesc rtupdesc_gettup(RTupDesc* rtupdesc); void rtupdesc_freedesc(RTupDesc* rtupdesc); void rtupdesc_dtor(RTupDesc* rtupdesc); int rtupdesc_obj_count(void); #endif // RTUPDESC_H pllua-1.1.0/rtupdescstk.c000066400000000000000000000066401304764261500153630ustar00rootroot00000000000000#include "rtupdescstk.h" #include "rtupdesc.h" #include "pllua_xact_cleanup.h" static void* current_func_cxt = NULL; RTupDescStack rtds_set_current(void *s){ RTupDescStack prev = current_func_cxt; current_func_cxt = s; return prev; } RTupDescStack rtds_get_current(void){ return current_func_cxt; } static void clean(RTupDescStack S){ void *top = rtds_pop(S); while (top) { RTupDesc* rtupdesc = (RTupDesc*)top; rtupdesc_freedesc(rtupdesc); rtupdesc->weakNodeStk = NULL; top = rtds_pop(S); } } void rtds_tryclean(RTupDescStack S){ if (S == NULL) return; S->ref_count -= 1; if (S->ref_count != 0) return; clean(S); } void *rtds_pop(RTupDescStack S) { void *hold; RTDNodePtr temp; if (rtds_isempty(S)) { return NULL; } hold = S -> top -> data; temp = S -> top; S -> top = S -> top -> next; if (S->top != NULL) S -> top->prev = NULL; pfree(temp); return hold; } static RTDNodePtr rtds_push(RTupDescStack S, void *d) { RTDNodePtr np; if (S == NULL) return NULL; MTOLUA(S->L); np = (RTDNodePtr) palloc(sizeof(RTDNode)); MTOPG; np->tail = S; np -> data = d; np -> next = S -> top; np -> prev = NULL; if (S->top != NULL) S -> top->prev = np; S -> top = np; return np; } int rtds_isempty(RTupDescStack S) { return (S -> top == NULL); } static void force_free(/*RTupDescStack*/void *d){ RTupDescStack p; if (d == NULL) return; p = d; if (p->cleanup_ptr){ (*p->cleanup_ptr) = NULL; } clean(p); pfree(d); } RTupDescStack rtds_initStack(lua_State *L) { RTupDescStack sp; L = pllua_getmaster(L); MTOLUA(L); sp = (RTupDescStack) palloc(sizeof(RTupDescStackType)); MTOPG; sp->ref_count = 0; sp->L = L; sp->top = NULL; sp->resptr = register_resource(sp, force_free); sp->cleanup_ptr = NULL; return sp; } RTupDescStack rtds_initStack_weak(lua_State *L, RTupDescStack* wp) { RTupDescStack sp; L = pllua_getmaster(L); MTOLUA(L); sp = (RTupDescStack) palloc(sizeof(RTupDescStackType)); MTOPG; sp->ref_count = 0; sp->L = L; sp->cleanup_ptr = wp; sp->top = NULL; sp->resptr = register_resource(sp, force_free); return sp; } RTDNodePtr rtds_push_current(void *d) { return rtds_push(current_func_cxt,d); } RTupDescStack rtds_unref(RTupDescStack S) { if (S == NULL) return NULL; rtds_notinuse(S); return rtds_free_if_not_used(S); } void rtds_remove_node(RTDNodePtr np) { RTupDescStack S; if (np == NULL) return; S = np->tail; if (S->top == np){ S->top = np->next; if (S->top){ S->top->prev = NULL; } }else{ if (np->prev) np->prev->next = np->next; if (np->next) np->next->prev = np->prev; } pfree(np); } int rtds_get_length(RTupDescStack S) { int count = 0; RTDNodePtr node = S->top; while(node){ count += 1; node = node->next; } return count; } void rtds_inuse(RTupDescStack S) { S->ref_count += 1; } void rtds_notinuse(RTupDescStack S) { S->ref_count -= 1; } RTupDescStack rtds_free_if_not_used(RTupDescStack S) { if (S == NULL) return NULL; if (S->ref_count == 0){ clean(S); S->resptr = unregister_resource(S->resptr); pfree(S); return NULL; } return S; } pllua-1.1.0/rtupdescstk.h000066400000000000000000000023371304764261500153670ustar00rootroot00000000000000/* * functions for tupledesc resource management * Author: Eugene Sergeev * Please check copyright notice at the bottom of pllua.h */ #ifndef RTUPDESCSTK_H #define RTUPDESCSTK_H #include "plluacommon.h" #include struct stackType; typedef struct RTDnode { void *data; struct RTDnode *next; struct RTDnode *prev; struct stackType *tail; } RTDNode, *RTDNodePtr; typedef struct stackType { int ref_count; lua_State *L; RTDNodePtr top; void *resptr; struct stackType **cleanup_ptr; /*func ptr to this struct*/ } RTupDescStackType, *RTupDescStack; RTupDescStack rtds_set_current(void *s); RTupDescStack rtds_get_current(void); int rtds_get_length(RTupDescStack S); RTupDescStack rtds_initStack(lua_State *L); RTupDescStack rtds_initStack_weak(lua_State *L, RTupDescStack *wp); int rtds_isempty(RTupDescStack S); void *rtds_pop(RTupDescStack S); void rtds_tryclean(RTupDescStack S); RTDNodePtr rtds_push_current(void *d); void rtds_remove_node(RTDNodePtr np); void rtds_inuse(RTupDescStack S); void rtds_notinuse(RTupDescStack S); RTupDescStack rtds_unref(RTupDescStack S); RTupDescStack rtds_free_if_not_used(RTupDescStack S); #endif // RTUPDESCSTK_H pllua-1.1.0/sql/000077500000000000000000000000001304764261500134355ustar00rootroot00000000000000pllua-1.1.0/sql/biginttest.sql000066400000000000000000000002271304764261500163330ustar00rootroot00000000000000CREATE FUNCTION int64_minus_one(value bigint) RETURNS bigint AS $$ return value - 1; $$ LANGUAGE pllua; select int64_minus_one(9223372036854775807); pllua-1.1.0/sql/error_info.sql000066400000000000000000000025021304764261500163210ustar00rootroot00000000000000do $$ local testfunc = function () error("my error") end local f = function() local status, err = pcall(testfunc) if (err) then error(err) end end f() $$language pllua; create or replace function pg_temp.function_with_error() returns integer as $$ local testfunc = function () error("my error") end local f = function() local status, err = pcall(testfunc) if (err) then error(err) end end f() $$language plluau; create or replace function pg_temp.second_function() returns void as $$ local k = server.execute('select pg_temp.function_with_error()') [0] $$language plluau; do $$ server.execute('select pg_temp.second_function()') $$language pllua; do $$ local status, err = subtransaction(function() assert(1==2) end) if (err) then error(err) end $$language pllua; do $$ info({message="info message", hint="info hint", detail="info detail"}) $$language pllua; do $$ info("info message") $$language pllua; do $$ warning({message="warning message", hint="warning hint", detail="warning detail"}) $$language pllua; do $$ warning("warning message") $$language pllua; do $$ error({message="error message", hint="error hint", detail="error detail"}) $$language pllua; do $$ error("error message") $$language pllua; do $$ info() $$language pllua; do $$ warning() $$language pllua; do $$ error() $$language pllua; pllua-1.1.0/sql/pgfunctest.sql000066400000000000000000000067321304764261500163500ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION i_void(internal) RETURNS internal AS $BODY$ -- mymodule.lua local M = {} -- public interface -- private local x = 1 local function baz() print 'test' end function M.foo() print("foo", x) end function M.bar() M.foo() baz() print "bar" end function M.getAnswer() return 42 end return M $BODY$ LANGUAGE pllua; CREATE OR REPLACE FUNCTION pg_temp.pgfunc_test() RETURNS SETOF text AS $$ local quote_ident = pgfunc("quote_ident(text)") coroutine.yield(quote_ident("int")) local right = pgfunc("right(text,int)") coroutine.yield(right('abcde', 2)) local factorial = pgfunc("factorial(int8)") coroutine.yield(tostring(factorial(50))) local i_void = pgfunc("i_void(internal)") coroutine.yield(i_void.getAnswer()) $$ LANGUAGE pllua; select pg_temp.pgfunc_test(); do $$ print(pgfunc('quote_nullable(text)')(nil)) $$ language pllua; create or replace function pg_temp.throw_error(text) returns void as $$ begin raise exception '%', $1; end $$ language plpgsql; do $$ pgfunc('pg_temp.throw_error(text)',{only_internal=false})("exception test") $$ language pllua; do $$ local f = pgfunc('pg_temp.throw_error(text)',{only_internal=false}) print(pcall(f, "exception test")) $$ language pllua; create or replace function pg_temp.no_throw() returns jsonb as $$ select '{"a":5, "b":10}'::jsonb $$ language sql; do $$ local f = pgfunc('pg_temp.no_throw()',{only_internal=false, throwable=false}) print(f()) $$ language pllua; CREATE or replace FUNCTION pg_temp.arg_count(a1 integer,a2 integer,a3 integer,a4 integer,a5 integer ,a6 integer,a7 integer,a8 integer,a9 integer,a10 integer ,a11 integer,a12 integer,a13 integer,a14 integer,a15 integer ) returns integer AS $$ begin return a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12+a13+a14+a15; end $$ LANGUAGE plpgsql; do $$ local f = pgfunc([[pg_temp.arg_count(integer, integer, integer, integer, integer, integer, integer, integer, integer, integer, integer, integer, integer, integer, integer ) ]],{only_internal=false}); print(f(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)) $$ language pllua; CREATE or replace FUNCTION pg_temp.inoutf(a integer, INOUT b text, INOUT c text) AS $$ begin c = a||'c:'||c; b = 'b:'||b; end $$ LANGUAGE plpgsql; do $$ local f = pgfunc('pg_temp.inoutf(integer,text,text)',{only_internal=false}); local r = f(5, 'ABC', 'd') print(r.b) print(r.c) $$ language pllua; do $$ local f = pgfunc('generate_series(int,int)') print('4-6') for rr in f(4,6) do print(rr) end print('1-3') for rr in f(1,3) do print(rr) end $$ language pllua; do $$ local f = pgfunc('generate_series(int,int)') for rr in f(1,3) do for rr in f(41,43) do print(rr) end print(rr) end $$ language pllua; -- Type wrapper create extension hstore; do $$ local hstore = { fromstring = function(text) return fromstring('hstore',text) end, akeys = pgfunc('akeys(hstore)',{only_internal = false}), each = pgfunc('each(hstore)',{only_internal = false}) --orig:each(IN hs hstore, OUT key text, OUT value text) } local v = hstore.fromstring[[ "paperback" => "542", "publisher" => "postgresql.org", "language" => "English", "ISBN-13" => "978-0000000000", "weight" => "24.1 ounces" ]] print(v) for _,v in ipairs(hstore.akeys(v)) do print (v) end for hv in hstore.each(v) do print ("key = " .. hv.key .. " value = "..hv.value) end $$ language pllua; create or replace function getnull() returns text as $$ begin return null; end $$ language plpgsql; do $$ local a = pgfunc('getnull()',{only_internal = false}) print(a()) $$ language pllua; pllua-1.1.0/sql/plluatest.sql000066400000000000000000000241731304764261500162020ustar00rootroot00000000000000\set ECHO none --\i pllua.sql CREATE EXTENSION pllua; \set ECHO all -- minimal function CREATE FUNCTION hello(name text) RETURNS text AS $$ return string.format("Hello, %s!", name) $$ LANGUAGE pllua; SELECT hello('PostgreSQL'); -- null handling CREATE FUNCTION max(a integer, b integer) RETURNS integer AS $$ if a == nil then return b end -- first arg is NULL? if b == nil then return a end -- second arg is NULL? return a > b and a or b -- return max(a, b) $$ LANGUAGE pllua; SELECT max(1,2), max(2,1), max(2,null), max(null, 2), max(null, null); -- plain recursive CREATE FUNCTION fib(n int) RETURNS int AS $$ if n < 3 then return n else return fib(n - 1) + fib(n - 2) end $$ LANGUAGE pllua; SELECT fib(4); -- memoized CREATE FUNCTION fibm(n integer) RETURNS integer AS $$ if n < 3 then return n else local v = _U[n] if not v then v = fibm(n - 1) + fibm(n - 2) _U[n] = v end return v end end do _U = {} $$ LANGUAGE pllua; SELECT fibm(4); -- tail recursive CREATE FUNCTION fibt(n integer) RETURNS integer AS $$ return _U(n, 0, 1) end _U = function(n, a, b) if n < 1 then return b else return _U(n - 1, b, a + b) end $$ LANGUAGE pllua; SELECT fibt(4); -- iterator CREATE FUNCTION fibi() RETURNS integer AS $$ while true do _U.curr, _U.next = _U.next, _U.curr + _U.next coroutine.yield(_U.curr) end end do _U = {curr = 0, next = 1} fibi = coroutine.wrap(fibi) $$ LANGUAGE pllua; SELECT fibi(), fibi(), fibi(), fibi(), fibi(); SELECT fibi(), fibi(), fibi(), fibi(), fibi(); -- upvalue CREATE FUNCTION counter() RETURNS int AS $$ while true do _U = _U + 1 coroutine.yield(_U) end end do _U = 0 -- counter counter = coroutine.wrap(counter) $$ LANGUAGE pllua; SELECT counter(); SELECT counter(); SELECT counter(); -- record input CREATE TYPE greeting AS (how text, who text); CREATE FUNCTION makegreeting (g greeting, f text) RETURNS text AS $$ return string.format(f, g.how, g.who) $$ LANGUAGE pllua; SELECT makegreeting(('how', 'who'), '%s, %s!'); -- array, record output CREATE FUNCTION greetingset (how text, who text[]) RETURNS SETOF greeting AS $$ for _, name in ipairs(who) do coroutine.yield{how=how, who=name} end $$ LANGUAGE pllua; SELECT makegreeting(greetingset, '%s, %s!') FROM (SELECT greetingset('Hello', ARRAY['foo', 'bar', 'psql'])) AS q; -- more array, upvalue CREATE FUNCTION perm (a text[]) RETURNS SETOF text[] AS $$ _U(a, #a) end do _U = function (a, n) -- permgen in PiL if n == 0 then coroutine.yield(a) -- return next SRF row else for i = 1, n do a[n], a[i] = a[i], a[n] -- i-th element as last one _U(a, n - 1) -- recurse on head a[n], a[i] = a[i], a[n] -- restore i-th element end end end $$ LANGUAGE pllua; SELECT * FROM perm(array['1', '2', '3']); -- shared variables CREATE FUNCTION getcounter() RETURNS integer AS $$ if shared.counter == nil then -- not cached? setshared("counter", 0) end return counter -- _G.counter == shared.counter $$ LANGUAGE pllua; CREATE FUNCTION setcounter(c integer) RETURNS void AS $$ if shared.counter == nil then -- not cached? setshared("counter", c) else counter = c -- _G.counter == shared.counter end $$ LANGUAGE pllua; SELECT getcounter(); SELECT setcounter(5); SELECT getcounter(); -- SPI usage CREATE TABLE sometable ( sid int4, sname text, sdata text); INSERT INTO sometable VALUES (1, 'name', 'data'); CREATE FUNCTION get_rows (i_name text) RETURNS SETOF sometable AS $$ if _U == nil then -- plan not cached? local cmd = "SELECT sid, sname, sdata FROM sometable WHERE sname = $1" _U = server.prepare(cmd, {"text"}):save() end local c = _U:getcursor({i_name}, true) -- read-only while true do local r = c:fetch(1) if r == nil then break end r = r[1] coroutine.yield{sid=r.sid, sname=r.sname, sdata=r.sdata} end c:close() $$ LANGUAGE pllua; SELECT * FROM get_rows('name'); CREATE TABLE tree (id INT PRIMARY KEY, lchild INT, rchild INT); CREATE FUNCTION filltree (t text, n int) RETURNS void AS $$ local p = server.prepare("insert into " .. t .. " values($1, $2, $3)", {"int4", "int4", "int4"}) for i = 1, n do local lchild, rchild = 2 * i, 2 * i + 1 -- siblings p:execute{i, lchild, rchild} -- insert values end $$ LANGUAGE pllua; SELECT filltree('tree', 10); CREATE FUNCTION preorder (t text, s int) RETURNS SETOF int AS $$ coroutine.yield(s) local q = server.execute("select * from " .. t .. " where id=" .. s, true, 1) -- read-only, only 1 result if q ~= nil then local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query if lchild ~= nil then preorder(t, lchild) end if rchild ~= nil then preorder(t, rchild) end end $$ LANGUAGE pllua; SELECT * from preorder('tree', 1); CREATE FUNCTION postorder (t text, s int) RETURNS SETOF int AS $$ local p = _U[t] if p == nil then -- plan not cached? p = server.prepare("select * from " .. t .. " where id=$1", {"int4"}) _U[t] = p:save() end local c = p:getcursor({s}, true) -- read-only local q = c:fetch(1) -- one row if q ~= nil then local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query c:close() if lchild ~= nil then postorder(t, lchild) end if rchild ~= nil then postorder(t, rchild) end end coroutine.yield(s) end do _U = {} -- plan cache $$ LANGUAGE pllua; SELECT * FROM postorder('tree', 1); -- trigger CREATE FUNCTION treetrigger() RETURNS trigger AS $$ local row, operation = trigger.row, trigger.operation if operation == "update" then trigger.row = nil -- updates not allowed elseif operation == "insert" then local id, lchild, rchild = row.id, row.lchild, row.rchild if lchild == rchild or id == lchild or id == rchild -- avoid loops or (lchild ~= nil and _U.intree(lchild)) -- avoid cycles or (rchild ~= nil and _U.intree(rchild)) or (_U.nonemptytree() and not _U.isleaf(id)) -- not leaf? then trigger.row = nil -- skip operation end else -- operation == "delete" if not _U.isleafparent(row.id) then -- not both leaf parent? trigger.row = nil end end end do local getter = function(cmd, ...) local plan = server.prepare(cmd, {...}):save() return function(...) return plan:execute({...}, true) ~= nil end end _U = { -- plan closures nonemptytree = getter("select * from tree"), intree = getter("select node from (select id as node from tree " .. "union select lchild from tree union select rchild from tree) as q " .. "where node=$1", "int4"), isleaf = getter("select leaf from (select lchild as leaf from tree " .. "union select rchild from tree except select id from tree) as q " .. "where leaf=$1", "int4"), isleafparent = getter("select lp from (select id as lp from tree " .. "except select ti.id from tree ti join tree tl on ti.lchild=tl.id " .. "join tree tr on ti.rchild=tr.id) as q where lp=$1", "int4") } $$ LANGUAGE pllua; CREATE TRIGGER tree_trigger BEFORE INSERT OR UPDATE OR DELETE ON tree FOR EACH ROW EXECUTE PROCEDURE treetrigger(); SELECT * FROM tree WHERE id = 1; UPDATE tree SET rchild = 1 WHERE id = 1; SELECT * FROM tree WHERE id = 10; DELETE FROM tree where id = 10; DELETE FROM tree where id = 1; -- passthru types CREATE FUNCTION echo_int2(arg int2) RETURNS int2 AS $$ return arg $$ LANGUAGE pllua; SELECT echo_int2('12345'); CREATE FUNCTION echo_int4(arg int4) RETURNS int4 AS $$ return arg $$ LANGUAGE pllua; SELECT echo_int4('1234567890'); CREATE FUNCTION echo_int8(arg int8) RETURNS int8 AS $$ return arg $$ LANGUAGE pllua; SELECT echo_int8('1234567890'); SELECT echo_int8('12345678901236789'); SELECT echo_int8('1234567890123456789'); CREATE FUNCTION echo_text(arg text) RETURNS text AS $$ return arg $$ LANGUAGE pllua; SELECT echo_text('qwe''qwe'); CREATE FUNCTION echo_bytea(arg bytea) RETURNS bytea AS $$ return arg $$ LANGUAGE pllua; SELECT echo_bytea('qwe''qwe'); SELECT echo_bytea(E'q\\000w\\001e''q\\\\we'); CREATE FUNCTION echo_timestamptz(arg timestamptz) RETURNS timestamptz AS $$ return arg $$ LANGUAGE pllua; SELECT echo_timestamptz('2007-01-06 11:11 UTC') AT TIME ZONE 'UTC'; CREATE FUNCTION echo_timestamp(arg timestamp) RETURNS timestamp AS $$ return arg $$ LANGUAGE pllua; SELECT echo_timestamp('2007-01-06 11:11'); CREATE FUNCTION echo_date(arg date) RETURNS date AS $$ return arg $$ LANGUAGE pllua; SELECT echo_date('2007-01-06'); CREATE FUNCTION echo_time(arg time) RETURNS time AS $$ return arg $$ LANGUAGE pllua; SELECT echo_time('11:11'); CREATE FUNCTION echo_arr(arg text[]) RETURNS text[] AS $$ return arg $$ LANGUAGE pllua; SELECT echo_arr(array['a', 'b', 'c']); CREATE DOMAIN mynum AS numeric(6,3); CREATE FUNCTION echo_mynum(arg mynum) RETURNS mynum AS $$ return arg $$ LANGUAGE pllua; SELECT echo_mynum(666.777); CREATE TYPE mytype AS (id int2, val mynum, val_list numeric[]); CREATE FUNCTION echo_mytype(arg mytype) RETURNS mytype AS $$ return arg $$ LANGUAGE pllua; SELECT echo_mytype((1::int2, 666.777, array[1.0, 2.0]) ); CREATE FUNCTION nested_server_rows () RETURNS SETOF text as $$ for left in server.rows('select generate_series as left from generate_series(3,4) ') do for right in server.rows('select generate_series as right from generate_series(5,6) ') do local s = left.left.." "..right.right coroutine.yield(s) end end $$ language pllua; select nested_server_rows(); CREATE OR REPLACE FUNCTION pg_temp.srf() RETURNS SETOF integer AS $$ coroutine.yield(1) coroutine.yield(nil) coroutine.yield(2) $$ LANGUAGE pllua; select quote_nullable(pg_temp.srf()); CREATE OR REPLACE FUNCTION pg_temp.srf() RETURNS SETOF integer AS $$ coroutine.yield(1) coroutine.yield() coroutine.yield(2) $$ LANGUAGE pllua; select quote_nullable(pg_temp.srf()); CREATE or replace FUNCTION pg_temp.inoutf(a integer, INOUT b text, INOUT c text) AS $$ begin c = a||'c:'||c; b = 'b:'||b; end $$ LANGUAGE plpgsql; do $$ local a = server.execute("SELECT pg_temp.inoutf(5, 'ABC', 'd') as val "); local r = a[1].val print(r.b) print(r.c) $$ language pllua; -- body reload SELECT hello('PostgreSQL'); CREATE OR REPLACE FUNCTION hello(name text) RETURNS text AS $$ return string.format("Bye, %s!", name) $$ LANGUAGE pllua; SELECT hello('PostgreSQL'); pllua-1.1.0/sql/subtransaction.sql000066400000000000000000000024261304764261500172210ustar00rootroot00000000000000CREATE TABLE accounts ( id bigserial NOT NULL, account_name text, balance integer, CONSTRAINT accounts_pkey PRIMARY KEY (id), CONSTRAINT no_minus CHECK (balance >= 0) ); insert into accounts(account_name, balance) values('joe', 200); insert into accounts(account_name, balance) values('mary', 50); CREATE OR REPLACE FUNCTION pg_temp.sub_test() RETURNS SETOF text AS $$ local f = function() local p = server.prepare("UPDATE accounts SET balance = balance + $2 WHERE account_name = $1", {"text","int4"}) p:execute{'joe', 100} p:execute{'mary',-100} return true end local status, err = subtransaction(f) coroutine.yield(tostring(status)) f = function() local p = server.prepare("UPDATE accounts SET balance = balance + $2 WHERE account_name = $1", {"text","int4"}) p:execute{'joe', -100} p:execute{'mary', 100} return true end status, err = subtransaction(f) coroutine.yield(tostring(status)) $$ LANGUAGE pllua; select pg_temp.sub_test(); do $$ local status, result = subtransaction(function() server.execute('select 1,'); -- < special SQL syntax error end); print (status, result) status, result = pcall(function() server.execute('select 1,'); -- < special SQL syntax error end); print (status, result) print ('done') $$ language pllua;