pllua-1.0/0000775000175000017500000000000012153452061012650 5ustar carvalhocarvalhopllua-1.0/pllua.control0000664000175000017500000000023212153451174015370 0ustar carvalhocarvalho# pllua extension default_version = '1.0' comment = 'Lua as a procedural language' module_pathname = '$libdir/pllua' relocatable = false schema = 'pllua' pllua-1.0/docs/0000775000175000017500000000000012153452071013601 5ustar carvalhocarvalhopllua-1.0/docs/style.css0000664000175000017500000001217512153451174015464 0ustar carvalhocarvalho/* Basic style */ /* $Id: style.css,v 1.4 2008-01-09 17:36:55 carvalho Exp $ */ /* ===== Typography and colors ===== */ body { font-size: 11pt; font-family: 'Lucida Grande', Geneva, Verdana, Arial, Helvetica, sans-serif; background-color: #567; color: #222; text-align: center; } #page { background-color: #fff; border: 1px solid #868fa0; text-align: left; } a, h2 a:hover, h3 a:hover { color: #900; text-decoration: none; } a:hover { color: #000080; background-color: #f0f0ff; text-decoration: underline; } a:visited { color: #000080; } #header { background-color: #fff; font-size: 1.4em; text-align: center; } #headerimg { background: url("images/splash.jpg") no-repeat bottom center; } #horizbar { margin-top: 10px; padding: 2px 0 2px; background-color: #eee; /* font-size: 1.05em; */ text-align: center; border-top: 1px solid #868fa0; border-bottom: 1px solid #868fa0; } #horizbar a:hover { background-color: #e0e0ff; } .content { line-height: 1.2em; text-align: justify; } .content .comment { text-align: center; } #footer { background-color: #eee; border-top: 1px solid #868fa0; } small { font-family: 'Lucida Grande', Geneva, Verdana, Arial, Helvetica, sans-serif; font-size: 0.9em; line-height: 1.5em; } h1, h2, h3, h4 { font-family: 'Lucida Grande', Geneva, Verdana, Arial, Helvetica, sans-serif; font-weight: bold; } h1 { font-size: 1.5em; } .description { font-size: 1.2em; text-align: left; } h2 { font-size: 1.25em; } h3 { font-size: 1.15em; } h4 { font-size: 1.05em; } #sidebar { background-color: #eee; } #sidebar h2 { font-family: 'Lucida Grande', Verdana, Sans-Serif; font-size: 1.1em; background: #d0d0ff; } h1, h1 a, h1 a:hover, h1 a:visited, .description { text-decoration: none; color: #000080; } h2, h2 a, h2 a:visited, h3, h3 a, h3 a:visited, h4, h4 a, h4 a:visited { color: #000080; } h2, h2 a, h2 a:hover, h2 a:visited, h3, h3 a, h3 a:hover, h3 a:visited, #sidebar h2, cite { text-decoration: none; } #sidebar { font: 1em 'Lucida Grande', Verdana, Arial, Sans-Serif; } small, #sidebar ul ul li, #sidebar ul ol li, .comment, blockquote, strike { color: #868fa0; } /* ===== Structure ===== */ body { margin: 0; padding: 0; } #page { margin: 0px auto; padding: 0; width: 85%; } #header { padding: 0; margin: 0 auto; width: 100%; } #headerimg { padding: 0; margin: 0 auto; height: 200px; width: 100%; } #title { height: 200px; width: 100%; } .content { padding: 0 0 20px 0; margin: 5px 50px 30px 50px; width: 90%; } .content .comment { margin: 10px 0; } #footer { padding: 0; margin: 0 auto; width: 100%; clear: both; } #footer p { margin: 0; padding: 20px 0; text-align: center; } #sidebar { padding: 20px 0 5px 0; margin-left: 605px; width: 130px; } /* ===== Headers ===== */ h1 { padding-top: 8px; margin: 0; } h2 { margin: 30px 0 0; } #sidebar h2 { margin: 0; padding-left: 5px; } h3 { padding: 0; margin: 30px 0 0; } #header h3 { margin: 5px; } h4 { padding: 0; margin: 20px 0 -10px; } /* ===== Images ===== */ p img { padding: 0; max-width: 100%; } #header img { margin-top: 20px; } img.centered { display: block; margin-left: auto; margin-right: auto; } img.alignright { padding: 4px; margin: 0 0 2px 7px; display: inline; } img.alignleft { padding: 4px; margin: 0 7px 2px 0; display: inline; } .alignright { float: right; } .alignleft { float: left } /* ===== Lists ===== */ html>body .content ul { margin-left: 0px; padding: 0 0 0 30px; padding-left: 10px; text-indent: -10px; } html>body .content li { margin: 7px 0 8px 10px; } .content ol { padding: 0 0 0 35px; margin: 0; } .content ol li { margin: 0; padding: 0; } .comment ul, .comment li { display: inline; list-style-type: none; list-style-image: none; } #sidebar ul, #sidebar ul ol { margin: 0; padding: 0; } #sidebar ul li { list-style-type: none; list-style-image: none; margin-bottom: 15px; } #sidebar ul p, #sidebar ul select { margin: 5px 0 8px; } #sidebar ul ul, #sidebar ul ol { margin: 5px 0 0 10px; } #sidebar ul ul ul, #sidebar ul ol { margin: 0 0 0 10px; } ol li, #sidebar ul ol li { list-style: decimal outside; } #sidebar ul ul li, #sidebar ul ol li { margin: 3px 0 0; padding: 0; } /* ===== Tables ===== */ table { border: 1px solid black; border-collapse: collapse; margin-left: auto; margin-right: auto; } th, td { border: 1px solid black; padding: 5px; } /* ===== Misc ===== */ acronym, abbr, span.caps { font-size: 0.9em; letter-spacing: .07em; cursor: help; } acronym, abbr { border-bottom: 1px dashed #666; } blockquote { margin: 15px 30px 0 10px; padding-left: 20px; } blockquote cite { margin: 5px 0 0; display: block; } .center { text-align: center; } a img { border: none; } code { font-family: 'Courier New', Courier, Fixed, Monospace; } pre { margin: 10px 20px 10px 20px; padding: 5px; text-align: left; font-size: 10pt; font-family: 'Courier New', Courier, Fixed, Monospace; background-color: #eee; border: 1px solid #868fa0; } pllua-1.0/docs/index.html0000664000175000017500000007713512153451174015616 0ustar carvalhocarvalho PL/Lua documentation
Introduction  ::   Languages  ::   Types  ::   Functions  ::   Database Access  ::   Triggers  ::   Installation

Introduction

What PL/Lua is

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.

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 section the two flavors of PL/Lua are described; the Functions section details how functions are registered in PL/Lua and how arguments are treated; Database access presents the SPI interface to PL/Lua; and Triggers shows how triggers can be declared.

PL/Lua is licensed under the same license as Lua -- the MIT license -- and so can be freely used for academic and commercial purposes. Please refer to the 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:

Table 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.

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, 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:

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:

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:

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:

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:

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 we know that composite types can be accessed as tables with keys corresponding to attribute names:

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

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:

# 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 from Programming in 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, 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:

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:

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:

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:

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

DO $$
  -- Lua chunk
$$ LANGUAGE [pllua | plluau];

compiles and executes the Lua chunk. Here are some examples:

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, and server.prepare prepares, but does not execute, a SQL command into a plan.

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

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. The returned plan should not be used outside the current invocation of server.prepare since it is freed by SPI_finish. Use plan:save 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.

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.

plan:rows(args)

Returns a function so that the construction

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:

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:

CREATE TABLE sometable ( sid int, sname text, sdata text);

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():

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:

CREATE TABLE tree (id int PRIMARY KEY, lchild int, rchild int);

which we can fill using:

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:

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.

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)
and 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.

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.

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:

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. 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:

$ make && sudo 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:

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.

pllua-1.0/pllua.h0000664000175000017500000000660712153451174014153 0ustar carvalhocarvalho/* * 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 $ */ /* PostgreSQL */ #include #include #include #include #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 #define PLLUA_VERSION "PL/Lua 1.0" 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); void luaP_pushtuple (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 *typename); void luaP_pushdesctable(lua_State *L, TupleDesc desc); void luaP_registerspi(lua_State *L); void luaP_pushcursor (lua_State *L, Portal cursor); 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.0/Makefile0000664000175000017500000000153012153451174014313 0ustar carvalhocarvalho# Makefile for PL/Lua # $Id: Makefile,v 1.12 2009/09/20 14:20:48 carvalho Exp $ # Lua specific # General LUAINC = -I/usr/local/include LUALIB = -L/usr/local/lib -llua # LuaJIT #LUAINC = -I/usr/local/include/luajit-2.0 #LUALIB = -L/usr/local/lib -lluajit-5.1 # Debian/Ubuntu #LUAINC = -I/usr/include/lua5.1 #LUALIB = -llua5.1 # Fink #LUAINC = -I/sw/include -I/sw/include/postgresql #LUALIB = -L/sw/lib -llua # Lua for Windows #LUAINC = -IC:/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 OBJS = pllua.o plluaapi.o plluaspi.o PG_CPPFLAGS = $(LUAINC) SHLIB_LINK = $(LUALIB) #PG_CONFIG = /usr/local/pgsql/bin/pg_config PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) pllua-1.0/rowstamp.h0000664000175000017500000000145312153451174014704 0ustar carvalhocarvalho #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.0/pllua.c0000664000175000017500000000357512153451174014147 0ustar carvalhocarvalho/* * 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" PG_MODULE_MAGIC; static lua_State *L[2] = {NULL, NULL}; /* Lua VMs */ Datum _PG_init(PG_FUNCTION_ARGS); Datum _PG_fini(PG_FUNCTION_ARGS); Datum pllua_validator(PG_FUNCTION_ARGS); Datum pllua_call_handler(PG_FUNCTION_ARGS); Datum plluau_validator(PG_FUNCTION_ARGS); Datum plluau_call_handler(PG_FUNCTION_ARGS); #if PG_VERSION_NUM >= 90000 Datum pllua_inline_handler(PG_FUNCTION_ARGS); Datum plluau_inline_handler(PG_FUNCTION_ARGS); #endif PG_FUNCTION_INFO_V1(_PG_init); Datum _PG_init(PG_FUNCTION_ARGS) { L[0] = luaP_newstate(0); /* untrusted */ L[1] = luaP_newstate(1); /* trusted */ PG_RETURN_VOID(); } PG_FUNCTION_INFO_V1(_PG_fini); Datum _PG_fini(PG_FUNCTION_ARGS) { luaP_close(L[0]); luaP_close(L[1]); PG_RETURN_VOID(); } PG_FUNCTION_INFO_V1(plluau_validator); Datum plluau_validator(PG_FUNCTION_ARGS) { return luaP_validator(L[0], PG_GETARG_OID(0)); } PG_FUNCTION_INFO_V1(plluau_call_handler); Datum plluau_call_handler(PG_FUNCTION_ARGS) { return luaP_callhandler(L[0], fcinfo); } PG_FUNCTION_INFO_V1(pllua_validator); Datum pllua_validator(PG_FUNCTION_ARGS) { return luaP_validator(L[1], PG_GETARG_OID(0)); } PG_FUNCTION_INFO_V1(pllua_call_handler); Datum pllua_call_handler(PG_FUNCTION_ARGS) { return luaP_callhandler(L[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) { return luaP_inlinehandler(L[0], CODEBLOCK); } PG_FUNCTION_INFO_V1(pllua_inline_handler); Datum pllua_inline_handler(PG_FUNCTION_ARGS) { return luaP_inlinehandler(L[1], CODEBLOCK); } #endif pllua-1.0/sql/0000775000175000017500000000000012153451174013453 5ustar carvalhocarvalhopllua-1.0/sql/plluatest.sql0000664000175000017500000002216612153451174016220 0ustar carvalhocarvalho\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]) ); -- 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.0/expected/0000775000175000017500000000000012153451174014455 5ustar carvalhocarvalhopllua-1.0/expected/plluatest.out0000644000175000017500000002750212153451174017227 0ustar carvalhocarvalho\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); NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "tree_pkey" for table "tree" 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) -- 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.0/pllua.sql0000664000175000017500000000272112153451174014514 0ustar carvalhocarvalhoCREATE 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.0/pllua.sql.in0000664000175000017500000000272112153451174015121 0ustar carvalhocarvalhoCREATE 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.0/pllua--1.0.sql0000664000175000017500000000322012153451174015060 0ustar carvalhocarvalho\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.0/plluaapi.c0000664000175000017500000011726012153451174014636 0ustar carvalhocarvalho/* * plluaapi.c: PL/Lua API * Author: Luis Carvalho * Please check copyright notice at the bottom of pllua.h */ #include "pllua.h" #include "rowstamp.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 { 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; int2 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 luaP_error(L, tag) \ ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), \ errmsg("[pllua]: " tag " error"), \ errdetail("%s", lua_tostring((L), -1)))) #define datum2string(d, f) \ DatumGetCString(DirectFunctionCall1((f), (d))) #define text2string(d) datum2string((d), textout) /* 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; } /* get MemoryContext for state L */ static MemoryContext luaP_getmemctxt (lua_State *L) { MemoryContext mcxt; lua_pushlightuserdata(L, (void *) L); lua_rawget(L, LUA_REGISTRYINDEX); mcxt = (MemoryContext) lua_touserdata(L, -1); lua_pop(L, 1); return mcxt; } /* ======= 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_pushinteger(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 == 'c') { /* complex? */ 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_pushinteger(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; 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 (name) */ relname = NameStr(tdata->tg_relation->rd_rel->relname); lua_getfield(L, LUA_REGISTRYINDEX, relname); if (lua_isnil(L, -1)) { /* not cached? */ lua_pop(L, 1); lua_createtable(L, 0, 2); lua_pushstring(L, relname); lua_setfield(L, -2, "name"); luaP_pushdesctable(L, tdata->tg_relation->rd_att); lua_pushinteger(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_pushvalue(L, -1); lua_setfield(L, LUA_REGISTRYINDEX, relname); /* cache relation */ } lua_setfield(L, -2, "relation"); /* row */ if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) { if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) { luaP_pushtuple(L, tdata->tg_relation->rd_att, tdata->tg_newtuple, tdata->tg_relation->rd_id, 0); lua_setfield(L, -2, "row"); /* new row */ luaP_pushtuple(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(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) { 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? */ 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; } static int luaP_info (lua_State *L) { luaL_checkstring(L, 1); info(lua_tostring(L, 1)); return 0; } static int luaP_log (lua_State *L) { luaL_checkstring(L, 1); ereport(LOG, (errmsg("%s", lua_tostring(L, 1)))); return 0; } static int luaP_notice (lua_State *L) { luaL_checkstring(L, 1); ereport(NOTICE, (errmsg("%s", lua_tostring(L, 1)))); return 0; } static int luaP_warning (lua_State *L) { luaL_checkstring(L, 1); ereport(WARNING, (errmsg("%s", lua_tostring(L, 1)))); return 0; } 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 == 'b' && OidIsValid(ti->elem)) inoid = ti->elem; v = InputFunctionCall(&ti->input, (char *) s, inoid, 0); /* typmod = 0 */ luaP_pushdatum(L, v, oid); return 1; } static const luaL_Reg luaP_funcs[] = { {"setshared", luaP_setshared}, {"log", luaP_log}, {"print", luaP_print}, {"info", luaP_info}, {"notice", luaP_notice}, {"warning", luaP_warning}, {"fromstring", luaP_fromstring}, {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(); /* version */ lua_pushliteral(L, PLLUA_VERSION); lua_setglobal(L, "_PLVERSION"); /* memory context */ lua_pushlightuserdata(L, (void *) L); lua_pushlightuserdata(L, (void *) mcxt); 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}, {LUA_BITLIBNAME, luaopen_bit32}, #endif {LUA_TABLIBNAME, luaopen_table}, {LUA_STRLIBNAME, luaopen_string}, {LUA_MATHLIBNAME, luaopen_math}, {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); /* 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", 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; fi = lua_newuserdata(L, sizeof(luaP_Info) + nargs * sizeof(Oid)); fi->oid = oid; /* read arg types */ for (i = 0; i < nargs; i++) { ti = luaP_gettypeinfo(L, argtype[i]); if (ti->type == 'p') /* pseudo-type? */ 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 == 'p' && rettype != VOIDOID && rettype != TRIGGEROID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("[pllua]: functions cannot return type '%s'", format_type_be(rettype)))); 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 *s, *fname; text *t; luaL_Buffer b; int init = (*fi == NULL); /* not initialized? */ /* 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_pushinteger(L, oid); *fi = luaP_newinfo(L, nargs, oid, procst); } lua_pushlightuserdata(L, (void *) *fi); /* check #argnames */ if (nargs > 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)->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); s = lua_tostring(L, -1); if (luaL_loadbuffer(L, s, strlen(s), PLLUA_CHUNKNAME)) luaP_error(L, "compile"); lua_remove(L, -2); /* source */ if (lua_pcall(L, 0, 1, 0)) luaP_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_pushinteger(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 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; } default: { luaP_Typeinfo *ti = luaP_gettypeinfo(L, type); switch (ti->type) { case 'c': { /* complex? */ 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 'p': /* pseudo? */ if (type != VOIDOID) argerror(type); break; case 'b': /* base? */ case 'd': /* 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 'e': /* 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); 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)); 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) { Datum dat = 0; /* NULL */ *isnull = lua_isnil(L, -1); if (!(*isnull || type == VOIDOID)) { switch (type) { /* base and domain types */ case BOOLOID: dat = BoolGetDatum(lua_toboolean(L, -1)); break; case FLOAT4OID: dat = Float4GetDatum((float4) lua_tonumber(L, -1)); break; case FLOAT8OID: dat = Float8GetDatum((float8) lua_tonumber(L, -1)); break; case INT2OID: dat = Int16GetDatum(lua_tointeger(L, -1)); break; case INT4OID: dat = Int32GetDatum(lua_tointeger(L, -1)); break; case TEXTOID: { const char *s = lua_tostring(L, -1); if (s == NULL) elog(ERROR, "[pllua]: string expected for datum conversion, got %s", lua_typename(L, lua_type(L, -1))); dat = string2text(s); break; } case REFCURSOROID: { Portal cursor = luaP_tocursor(L, -1); dat = string2text(cursor->name); break; } default: { luaP_Typeinfo *ti = luaP_gettypeinfo(L, type); switch (ti->type) { case 'c': /* complex? */ if (lua_type(L, -1) == LUA_TTABLE) { int i; luaP_Buffer *b; if (lua_type(L, -1) != LUA_TTABLE) elog(ERROR, "[pllua]: table expected for record result, got %s", lua_typename(L, lua_type(L, -1))); /* create tuple */ b = luaP_getbuffer(L, ti->tupdesc->natts); for (i = 0; i < ti->tupdesc->natts; i++) { lua_getfield(L, -1, 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); 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, -1))); dat = PointerGetDatum(SPI_returntuple(tuple, ti->tupdesc)); } break; case 'b': /* base? */ case 'd': /* domain? */ if (ti->elem != 0 && ti->len == -1) { /* array? */ luaP_Typeinfo *te; int ndims, dims[MAXDIM], lb[MAXDIM]; int i, size; bool hasnulls; ArrayType *a; if (lua_type(L, -1) != LUA_TTABLE) elog(ERROR, "[pllua]: table expected for array conversion, got %s", lua_typename(L, lua_type(L, -1))); te = luaP_gettypeinfo(L, ti->elem); for (i = 0; i < MAXDIM; i++) dims[i] = lb[i] = -1; 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, -1, PLLUA_DATUM); if (d == NULL) elog(ERROR, "[pllua]: raw datum expected for datum conversion, got %s", lua_typename(L, lua_type(L, -1))); dat = datumcopy(d->datum, ti); } break; #if PG_VERSION_NUM >= 80300 case 'e': /* enum? */ dat = Int32GetDatum(lua_tointeger(L, -1)); break; #endif case 'p': /* 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); lua_settop(L, 0); return dat; } /* ======= luaP_callhandler ======= */ static void luaP_cleanthread (lua_State *L, lua_State **thread) { 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; luaP_Info *fi; 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); 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]); if (lua_pcall(L, nargs, 0, 0)) luaP_error(L, "runtime"); 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 hasresult = !lua_isnoneornil(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); } else luaP_error(fi->L, "runtime"); } else { luaP_pushargs(L, fcinfo, fi); if (lua_pcall(L, fcinfo->nargs, 1, 0)) luaP_error(L, "runtime"); retval = luaP_getresult(L, fcinfo, fi->result); } } /* stack should be clean here: lua_gettop(L) == 0 */ } PG_CATCH(); { if (L != NULL) { luaP_cleantrigger(L); if (fi->result_isset && fi->L != NULL) /* clean thread? */ luaP_cleanthread(L, &fi->L); lua_settop(L, 0); /* clear Lua stack */ } fcinfo->isnull = true; retval = (Datum) 0; PG_RE_THROW(); } PG_END_TRY(); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "[pllua]: could not disconnect from SPI manager"); return retval; } #if PG_VERSION_NUM >= 90000 /* ======= luaP_inlinehandler ======= */ Datum luaP_inlinehandler (lua_State *L, const char *source) { if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "[pllua]: could not connect to SPI manager"); PG_TRY(); { if (luaL_loadbuffer(L, source, strlen(source), PLLUA_CHUNKNAME)) luaP_error(L, "compile"); if (lua_pcall(L, 0, 0, 0)) luaP_error(L, "runtime"); } PG_CATCH(); { if (L != NULL) lua_settop(L, 0); /* clear Lua stack */ PG_RE_THROW(); } PG_END_TRY(); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "[pllua]: could not disconnect from SPI manager"); PG_RETURN_VOID(); } #endif pllua-1.0/plluaspi.c0000664000175000017500000005025512153451174014660 0ustar carvalhocarvalho/* * 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" #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_PLANMT[] = "plan"; static const char PLLUA_CURSORMT[] = "cursor"; static const char PLLUA_TUPTABLEMT[] = "tupletable"; typedef struct luaP_Tuple { int changed; Oid relid; HeapTuple tuple; TupleDesc desc; Datum *value; bool *null; } luaP_Tuple; typedef struct luaP_Tuptable { int size; Portal cursor; SPITupleTable *tuptable; } luaP_Tuptable; typedef struct luaP_Cursor { Portal cursor; } 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 int luaP_rowsaux (lua_State *L) { luaP_Cursor *c = (luaP_Cursor *) lua_touserdata(L, lua_upvalueindex(1)); int init = lua_toboolean(L, lua_upvalueindex(2)); SPI_cursor_fetch(c->cursor, 1, 1); if (SPI_processed > 0) { /* any row? */ if (!init) { /* register tupdesc */ lua_pushinteger(L, (int) InvalidOid); luaP_pushdesctable(L, SPI_tuptable->tupdesc); lua_rawset(L, LUA_REGISTRYINDEX); lua_pushboolean(L, 1); lua_replace(L, lua_upvalueindex(2)); } luaP_pushtuple(L, SPI_tuptable->tupdesc, SPI_tuptable->vals[0], InvalidOid, 1); } else { SPI_cursor_close(c->cursor); lua_pushnil(L); } return 1; } /* ======= 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); b->null[k] = (isnull) ? 'n' : ' '; } lua_pop(L, 1); } } /* ======= Tuple ======= */ 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; lua_pushinteger(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->changed == -1) { /* read-only? */ bool isnull; Datum v = heap_getattr(t->tuple, t->desc->attrs[i]->attnum, t->desc, &isnull); if (!isnull) luaP_pushdatum(L, v, t->desc->attrs[i]->atttypid); else lua_pushnil(L); } else { if (!t->null[i]) luaP_pushdatum(L, t->value[i], t->desc->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_pushinteger(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->desc->attrs[i]->atttypid, t->desc->attrs[i]->atttypmod, &isnull); 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 (lua_State *L, TupleDesc desc, HeapTuple tuple, Oid relid, int readonly) { luaP_Tuple *t; int i, n = desc->natts; if (readonly) { t = lua_newuserdata(L, sizeof(luaP_Tuple)); t->changed = -1; t->value = NULL; t->null = NULL; } else { t = lua_newuserdata(L, sizeof(luaP_Tuple) + n * (sizeof(Datum) + sizeof(bool))); t->changed = 0; t->value = (Datum *) (t + 1); t->null = (bool *) (t->value + n); 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->desc = desc; t->relid = relid; t->tuple = tuple; luaP_getfield(L, PLLUA_TUPLEMT); lua_setmetatable(L, -2); } /* adapted from SPI_modifytuple */ static HeapTuple luaP_copytuple (luaP_Tuple *t) { HeapTuple tuple = heap_form_tuple(t->desc, 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->desc->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_pushinteger(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->desc->attrs[j]->attnum, t->desc, 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; 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)); 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; if (cursor == NULL || (cursor != NULL && t->cursor != cursor)) { lua_pushinteger(L, (int) InvalidOid); luaP_pushdesctable(L, t->tuptable->tupdesc); lua_rawset(L, LUA_REGISTRYINDEX); t->cursor = cursor; } /* reset tuptable env */ lua_newtable(L); /* env */ lua_setuservalue(L, -2); } 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_pushinteger(L, (int) InvalidOid); lua_rawget(L, LUA_REGISTRYINDEX); } 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(L, t->tuptable->tupdesc, t->tuptable->vals[k - 1], InvalidOid, 1); lua_pushvalue(L, -1); lua_rawseti(L, -3, k); } } 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) { luaP_Tuptable *t = (luaP_Tuptable *) lua_touserdata(L, 1); 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; 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); SPI_cursor_fetch(c->cursor, 1, luaL_optlong(L, 2, FETCH_ALL)); 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); SPI_cursor_move(c->cursor, 1, luaL_optlong(L, 2, 0)); 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; SPI_scroll_cursor_fetch(c->cursor, fd, luaL_optlong(L, 2, FETCH_ALL)); 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; SPI_scroll_cursor_move(c->cursor, fd, luaL_optlong(L, 2, 0)); return 0; } #endif static int luaP_cursorclose (lua_State *L) { luaP_Cursor *c = (luaP_Cursor *) luaP_checkudata(L, 1, PLLUA_CURSORMT); 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); long c = luaL_optlong(L, 4, 0); int result; 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); result = SPI_execute_plan(p->plan, b->value, b->null, ro, c); } else result = SPI_execute_plan(p->plan, NULL, NULL, ro, c); 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; } static int luaP_saveplan (lua_State *L) { luaP_Plan *p = (luaP_Plan *) luaP_checkudata(L, 1, PLLUA_PLANMT); 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; 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); cursor = SPI_cursor_open(name, p->plan, b->value, b->null, ro); } else cursor = SPI_cursor_open(name, p->plan, NULL, NULL, 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; 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); cursor = SPI_cursor_open(NULL, p->plan, b->value, b->null, 1); } else cursor = SPI_cursor_open(NULL, p->plan, NULL, NULL, 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 *typename) { #if PG_VERSION_NUM < 80300 List *namelist = stringToQualifiedNameList(typename, NULL); HeapTuple typetup = typenameType(NULL, makeTypeNameFromNameList(namelist)); #else List *namelist = stringToQualifiedNameList(typename); 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) { const char *q = luaL_checkstring(L, 1); int nargs, cursoropt; 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); 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); } } 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 = SPI_execute(luaL_checkstring(L, 1), (bool) lua_toboolean(L, 2), luaL_optlong(L, 3, 0)); 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; 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); 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}, {NULL, NULL} }; static const luaL_Reg luaP_Tuptable_mt[] = { {"__index", luaP_tuptableindex}, {"__len", luaP_tuptablelen}, {"__gc", luaP_tuptablegc}, {"__tostring", luaP_tuptabletostring}, {NULL, NULL} }; static const luaL_Reg luaP_Cursor_mt[] = { {"__tostring", luaP_cursortostring}, {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); /* 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); }