pax_global_header00006660000000000000000000000064134704775420014527gustar00rootroot0000000000000052 comment=62e66bf8d174c257021bb95718ebae792f8a535a pllua-ng-REL_2_0_4/000077500000000000000000000000001347047754200141155ustar00rootroot00000000000000pllua-ng-REL_2_0_4/.dir-locals.el000066400000000000000000000037611347047754200165550ustar00rootroot00000000000000;; see also src/tools/editors/emacs.samples for more complete settings ((c-mode . ((c-basic-offset . 4) (c-file-style . "bsd") (fill-column . 78) (indent-tabs-mode . t) (tab-width . 4) (c-file-offsets (case-label . +) (label . -) (statement-case-open . +)) (eval add-hook 'before-save-hook 'delete-trailing-whitespace nil t))) (lua-mode . ((indent-tabs-mode . t) (tab-width . 4) (eval add-hook 'before-save-hook 'delete-trailing-whitespace nil t))) (sh-mode . ((indent-tabs-mode . t) (tab-width . 4))) (css-mode . ((tab-width . 4) (eval add-hook 'before-save-hook 'delete-trailing-whitespace nil t))) (dsssl-mode . ((indent-tabs-mode . nil))) (nxml-mode . ((indent-tabs-mode . nil) (eval add-hook 'before-save-hook 'delete-trailing-whitespace nil t))) (perl-mode . ((perl-indent-level . 4) (perl-continued-statement-offset . 4) (perl-continued-brace-offset . 4) (perl-brace-offset . 0) (perl-brace-imaginary-offset . 0) (perl-label-offset . -2) (indent-tabs-mode . t) (tab-width . 4) (eval add-hook 'before-save-hook 'delete-trailing-whitespace nil t))) (sgml-mode . ((fill-column . 78) (indent-tabs-mode . nil)))) ;; c-file-offsets is not marked safe by default, but you can either ;; accept the specific value given as safe always, or do something ;; like this in your .emacs to accept only the simplest offset lists ;; automatically: ;; (defun my-safe-c-file-offsets-p (alist) ;; (catch 'break ;; (and (listp alist) ;; (dolist (elt alist t) ;; (pcase elt ;; (`(,(pred symbolp) . ,(or `+ `- `++ `-- `* `/)) t) ;; (`(,(pred symbolp) . ,(or (pred null) (pred integerp))) t) ;; (`(,(pred symbolp) . [ ,(pred integerp) ]) t) ;; (_ (throw 'break nil))))))) ;; (put 'c-file-offsets 'safe-local-variable 'my-safe-c-file-offsets-p) pllua-ng-REL_2_0_4/.editorconfig000066400000000000000000000000551347047754200165720ustar00rootroot00000000000000 [*] tab_width = 4 [Makefile] tab_width = 8 pllua-ng-REL_2_0_4/.gitattributes000066400000000000000000000005341347047754200170120ustar00rootroot00000000000000# .gitattributes * whitespace=space-before-tab,trailing-space *.[chly] whitespace=space-before-tab,trailing-space,indent-with-non-tab,tabwidth=4 *.[ch] linguist-language=C # Test output files that contain extra whitespace *.out -whitespace # not to be counted as code .dir-locals.el linguist-documentation .editorconfig linguist-documentation pllua-ng-REL_2_0_4/.gitignore000066400000000000000000000005231347047754200161050ustar00rootroot00000000000000*.o *.obj *.bc *.so *.so.[0-9] *.so.[0-9].[0-9] *.sl *.sl.[0-9] *.sl.[0-9].[0-9] *.dylib *.dll *.a *.mo objfiles.txt .deps/ *.gcno *.gcda *.gcov *.gcov.out *.luac lcov.info coverage/ /results/ /hstore/results/ /pllua_functable.h /plerrcodes.h /src/.dir-locals.el /pllua.html /icon.ico /icon-*.png /icon.meta /logo.css /tmpdoc.html build*/ pllua-ng-REL_2_0_4/.travis.yml000066400000000000000000000046761347047754200162430ustar00rootroot00000000000000sudo: required dist: trusty language: c compiler: - gcc git: depth: 5 env: matrix: - PG=9.5 LUAV=lua-5.3.5 - PG=9.6 LUAV=lua-5.3.5 - PG=10 LUAV=lua-5.3.5 - PG=10 LUAV=LuaJIT-b025b01 - PG=11 LUAV=lua-5.3.5 - PG=12 PGREPO=trusty-pgdg-testing LUAV=lua-5.3.5 addons: apt: sources: - sourceline: "deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main 12" key_url: "https://www.postgresql.org/media/keys/ACCC4CF8.asc" - sourceline: "deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg-testing main 12" key_url: "https://www.postgresql.org/media/keys/ACCC4CF8.asc" before_install: - sudo /etc/init.d/postgresql stop - sudo apt-get -y --purge remove postgresql libpq-dev libpq5 postgresql-client-common postgresql-common - sudo apt-get update - sudo rm -rf /var/lib/postgresql - sudo apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::="--force-confnew" ${PGREPO:+-t "$PGREPO"} install postgresql-${PG:?} postgresql-contrib-${PG:?} postgresql-server-dev-${PG:?} - | mkdir "$LUAV" if [[ "$LUAV" == lua-* ]]; then LUA_URL="http://www.lua.org/ftp/" if [[ "$LUAV" == *-work* ]]; then LUA_URL="http://www.lua.org/work/" fi wget -O- "${LUA_URL}${LUAV}".tar.gz \ | tar zx --strip-components=1 -C "$LUAV" make -C "$LUAV"/src \ SYSCFLAGS="-DLUA_USE_POSIX -DLUA_USE_DLOPEN -ULUA_COMPAT_5_2" \ SYSLIBS="-Wl,-E -ldl" \ MYCFLAGS="-fPIC" liblua.a lua luac export LUAC="$PWD/$LUAV/src/luac" export LUA="$PWD/$LUAV/src/lua" elif [[ "$LUAV" == LuaJIT-* ]]; then wget -O- https://github.com/LuaJIT/LuaJIT/archive/"${LUAV#LuaJIT-}".tar.gz \ | tar zx --strip-components=1 -C "$LUAV" make -C "$LUAV"/src \ XCFLAGS="-DLUAJIT_ENABLE_GC64 -DLUAJIT_ENABLE_LUA52COMPAT" \ CFLAGS="-fPIC -O3" libluajit.a luajit export LUAJIT="$PWD/$LUAV/src/luajit" export LUAJITC="cp" fi export LUA_INCDIR="$PWD/$LUAV/src" mv $LUAV/src/liblua*.a ./libcurrent_lua_build.a export LUALIB="-L$PWD -lcurrent_lua_build" before_script: - sudo -u postgres createuser -s "$USER" script: - | make && sudo make install && time make installcheck && if [ -z "$NO_HSTORE" ]; then make -C hstore && sudo make -C hstore install && time make -C hstore installcheck fi after_script: - | cat regression.diffs || true cat hstore/regression.diffs || true pllua-ng-REL_2_0_4/LICENSE000066400000000000000000000020561347047754200151250ustar00rootroot00000000000000MIT License Copyright (c) 2017 Andrew Gierth 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-ng-REL_2_0_4/Makefile000066400000000000000000000140021347047754200155520ustar00rootroot00000000000000# Makefile for PL/Lua # -*- mode: makefile-gmake -*- PG_CONFIG ?= pg_config # Lua specific # two values can be set here, which only apply to luajit: # -DNO_LUAJIT disables all use of luajit features # -DUSE_INT8_CDATA convert sql bigints to cdata int64_t # The latter is off by default because it has some possibly # undesirable effects on bigint handling. PLLUA_CONFIG_OPTS ?= # General LUA_INCDIR ?= /usr/local/include/lua53 LUALIB ?= -L/usr/local/lib -llua-5.3 LUAC ?= luac53 LUA ?= lua53 # LuaJIT #LUA_INCDIR = /usr/local/include/luajit-2.1 #LUALIB = -L/usr/local/lib -lluajit-5.1 #LUAJIT = luajit ifdef LUAJIT LUAJITC ?= $(LUAJIT) -b -g -t raw LUA = $(LUAJIT) LUAC = $(REORDER_O) $(LUAJITC) endif # if no OBJCOPY or not needed, this can be set to true (or false) OBJCOPY ?= objcopy # We expect $(BIN_LD) -o foo.o foo.luac to create a foo.o with the # content of foo.luac as a data section (plus appropriate symbols). # GNU LD and compatible linkers (including recent clang lld) should be # fine with -r -b binary, but this does break on some ports. BIN_LD ?= $(LD) -r -b binary # If BIN_ARCH and BIN_FMT are defined, we assume LD_BINARY is broken # and do this instead. This is apparently needed for linux-mips64el, # for which BIN_ARCH=mips:isa64r2 BIN_FMT=elf64-tradlittlemips seems # to work. ifdef BIN_ARCH ifdef BIN_FMT BIN_LD = $(REORDER_O) $(OBJCOPY) -B $(BIN_ARCH) -I binary -O $(BIN_FMT) endif endif # should be no need to edit below here MODULE_big = pllua EXTENSION = pllua plluau SQL_SRC = pllua--2.0.sql pllua--1.0--2.0.sql \ plluau--2.0.sql plluau--1.0--2.0.sql DATA = $(addprefix scripts/, $(SQL_SRC)) DOC_HTML = pllua.html ifdef BUILD_DOCS DOCS = $(DOC_HTML) ifdef BUILD_ICON ICON = icon.meta endif endif objdir := src # variables like $(srcdir) and $(MAJORVERSION) are not yet set, but # are safe to use in recursively-expanded variables since they will be # set before most values are needed. Can't use them in conditionals # until after pgxs is loaded though. # version-dependent regression tests REGRESS_10 := triggers_10 REGRESS_11 := $(REGRESS_10) procedures REGRESS_12 := $(REGRESS_11) EXTRA_REGRESS = $(REGRESS_$(MAJORVERSION)) REGRESS = --schedule=$(srcdir)/serial_schedule $(EXTRA_REGRESS) REGRESS_PARALLEL = --schedule=$(srcdir)/parallel_schedule $(EXTRA_REGRESS) REORDER_O = $(srcdir)/tools/reorder-o.sh DOC_MD = css.css script.js intro.md pllua.md building.md endnote.md DOC_SRCS = logo.css $(ICON) $(addprefix $(srcdir)/doc/, $(DOC_MD)) INCS= pllua.h pllua_pgver.h pllua_luaver.h pllua_luajit.h HEADERS= $(addprefix src/, $(INCS)) OBJS_C= compile.o datum.o elog.o error.o exec.o globals.o init.o \ jsonb.o numeric.o objects.o paths.o pllua.o preload.o spi.o \ time.o trigger.o trusted.o SRCS_C = $(addprefix $(srcdir)/src/, $(OBJS_C:.o=.c)) OBJS_LUA = compat.o SRCS_LUA = $(addprefix $(srcdir)/src/, $(OBJS_LUA:.o=.lua)) OBJS = $(addprefix src/, $(OBJS_C)) EXTRA_OBJS = $(addprefix src/, $(OBJS_LUA)) EXTRA_CLEAN = pllua_functable.h plerrcodes.h \ $(addprefix src/,$(OBJS_LUA:.o=.luac)) $(EXTRA_OBJS) \ logo.css tmpdoc.html icon-16.png icon.meta $(DOC_HTML) PG_CPPFLAGS = -I$(LUA_INCDIR) $(PLLUA_CONFIG_OPTS) SHLIB_LINK = $(EXTRA_OBJS) $(LUALIB) # if VPATH is not already set, but the makefile is not in the current # dir, then assume a vpath build using the makefile's directory as # source. PGXS will set $(srcdir) accordingly. ifndef VPATH ifneq ($(realpath $(CURDIR)),$(realpath $(dir $(firstword $(MAKEFILE_LIST))))) VPATH := $(dir $(firstword $(MAKEFILE_LIST))) endif endif # actually load pgxs PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) # definitions that must follow pgxs ifeq ($(filter-out 7.% 8.% 9.0 9.1 9.2 9.3 9.4, $(MAJORVERSION)),) $(error unsupported PostgreSQL version) endif $(OBJS): $(addprefix $(srcdir)/src/, $(INCS)) # for a vpath build, we need src/ to exist in the build dir before # building any objects. ifdef VPATH all: vpath-mkdirs .PHONY: vpath-mkdirs $(OBJS) $(EXTRA_OBJS): | vpath-mkdirs vpath-mkdirs: $(MKDIR_P) $(objdir) endif # VPATH all: $(DOCS) # explicit deps on generated includes src/init.o: pllua_functable.h src/error.o: plerrcodes.h $(shlib): $(EXTRA_OBJS) %.luac: %.lua $(LUAC) -o $@ $< ifeq ($(PORTNAME),darwin) # Apple of course has to do its own thing when it comes to object file # format and linker options. _stub.c: touch $@ %.o: %.luac _stub.o $(LD) -r -sectcreate binary $(subst .,_,$($@ ifneq ($(filter-out 9.% 10, $(MAJORVERSION)),) #in pg 11+, we can get the server's errcodes.txt. plerrcodes.h: $(datadir)/errcodes.txt $(srcdir)/tools/errcodes.lua $(LUA) $(srcdir)/tools/errcodes.lua $(datadir)/errcodes.txt >plerrcodes.h else plerrcodes.h: $(srcdir)/src/plerrcodes_old.h cp $(srcdir)/src/plerrcodes_old.h plerrcodes.h endif installcheck-parallel: submake $(REGRESS_PREP) $(pg_regress_installcheck) $(REGRESS_OPTS) $(REGRESS_PARALLEL) logo.css: $(srcdir)/doc/logo.svg $(srcdir)/tools/logo.lua $(LUA) $(srcdir)/tools/logo.lua -text -logo $(srcdir)/doc/logo.svg >$@ # Stripped PNGs are quite a bit smaller than .ico #icon.ico: $(srcdir)/doc/logo.svg # convert -size 256x256 -background transparent $(srcdir)/doc/logo.svg \ # -format ico -define icon:auto-resize=32,16 icon.ico icon-16.png: $(srcdir)/doc/logo.svg convert -size 16x16 -background transparent $(srcdir)/doc/logo.svg \ -format png -define png:format=png32 \ -define png:exclude-chunk=all icon-16.png icon.meta: icon-16.png $(LUA) $(srcdir)/tools/logo.lua -binary -icon="16x16" icon-16.png >$@ $(DOC_HTML): $(DOC_SRCS) $(srcdir)/doc/template.xsl $(srcdir)/tools/doc.sh $(srcdir)/tools/doc.sh $(DOC_SRCS) >tmpdoc.html xsltproc --encoding utf-8 $(srcdir)/doc/template.xsl tmpdoc.html >$@ rm -- tmpdoc.html pllua-ng-REL_2_0_4/README.md000066400000000000000000000427721347047754200154100ustar00rootroot00000000000000 pllua [![Build Status](https://travis-ci.org/pllua/pllua-ng.svg)](https://travis-ci.org/pllua/pllua-ng) ===== Embeds Lua into PostgreSQL as a procedural language module. Please report any remaining bugs or missing functionality on github. Currently it should build against (recent point releases of) pg versions 9.5, 9.6, 10, and 11. It is known that this module will never work on pg versions before 9.5 (we rely critically on memory context callbacks, which were introduced in that version). Lua 5.3 and LuaJIT 2.1beta (with COMPAT52) are fully supported at this time. Documentation is being migrated from this README to a more comprehensive document: [Documentation](https://pllua.github.io/pllua-ng/) COMPATIBILITY WITH 0.x (previous pllua-ng releases) --------------------------------------------------- The server.* namespace is removed, to facilitate the compatibility option described below. server.error, server.debug, etc. are now available via spi.error, spi.debug, or can be obtained from the 'pllua.elog' package via require(). If you want, you can do: pllua.on_common_init='server = require "pllua.elog"' to restore the previous behavior. COMPATIBILITY WITH 1.x (old pllua) ---------------------------------- This version permits a degree of compatibility with pllua 1.x via the following setting: pllua.on_common_init='require "pllua.compat"' This creates the server.* namespace with functions that match the 1.x calling conventions (e.g. passing args as tables). It also creates global functions fromstring(), subtransaction(), debug(), log(), info(), notice(), warning(), and setshared(). The following incompatibilities remain: + coroutine.yield() with no result values in 1.x ended execution of the SRF; in this version it returns a NULL row and continues. + The global error() is not modified, so using it to throw a table has the same effect it would have in plain Lua. In 1.x, a metatable was added to the thrown value in this case. + The pgfunc library is not supported at all. + The "readonly" parameter to server.execute and friends is ignored. All queries in a stable function are readonly, and all queries in a volatile function are read-write. + Returning multi-dimensional arrays by doing a simple return of a Lua table is no longer supported. The pllua.compat module is implemented in pure Lua (inside the sandbox in trusted mode), see src/compat.lua for the implementation. Please report any incompatibilities discovered. CHANGES ------- *NOTE:* the name of the module is now just "pllua", and its extension packaging is split into two according to usual practice for pl modules (in this case "pllua" for the trusted language and "plluau" for the untrusted language). (Compared to the old pllua project:) Some names and locations have been changed. The old pllua.init table is gone. Instead we support three init strings (superuser-only): pllua.on_init, pllua.on_trusted_init, pllua.on_untrusted_init. Note that the on_init string can be run in the postmaster process, by including pllua in shared_preload_libraries. Accordingly, on_init cannot do any database access, and the only function directly available from this module is print(), which in this environment will output to the server log as LOG: messages. The on_init string can now access the trusted.allow() functionality, but only by doing an explicit require 'pllua.trusted'. e.g. local t = require 'pllua.trusted' t.allow{ "lpeg", "re" } SPI functionality is now in global table spi and has different calling conventions: spi.execute("query text", arg, arg, ...) spi.execute_count("query text", maxrows, arg, arg, ...) spi.prepare("query text", {argtypes}, [{options}]) - returns a statement object: s:execute(arg, arg, ...) - returns a result table s:execute_count(maxrows, arg, arg, ...) - returns a result table s:rows(arg, arg, ...) - returns iterator s:numargs() - returns integer s:argtype(argnum) - returns typeinfo spi.rows("query text", args...) - returns iterator Execution now returns a table with no number keys (#t == 0) in the event of no matching rows, whereas the old version returned nil. The result is also currently a plain table, not an object. spi.prepare takes an options table with these possible values: scroll = true or false no_scroll = true fast_start = true custom_plan = true generic_plan = true fetch_count = integer Note that "scroll" and "no_scroll" are independent options to the planner, but we treat { scroll = false } as if it were { no_scroll = true } because not doing so would be too confusing. The fetch_count value is used only by the :rows iterator, to determine how much prefetch to use; the default is 50. (Smaller values might be desirable for fetching very large rows, or a value of 1 disables prefetch entirely.) Cursors work: spi.findcursor("name") - find already-open portal by name spi.newcursor(["name"]) - find existing cursor or create new one s:getcursor(args) - get cursor from statement (can't specify name) c:open(stmt,args) - open a cursor c:open(query,args) - open a cursor c:isopen() - is it open c:name() c:fetch([n, [dir]]) - fetch n rows in dir (default: forward 1) c:move([n, [dir]]) There can only be one cursor object for a given open portal - doing a findcursor on an existing cursor will always return the same object. (But note that this matching is by portal, not name - if a cursor was closed and reopened with the same name, findcursor will return a different object for the new cursor.) If a cursor is closed by external code (or transaction end), then the :isopen() state will be automatically updated (this happens when the portal is actually dropped). Cursor options are set on the statement object. Refcursor parameters and results are transparently converted to and from SPI cursor objects. :save on a statement is now a no-op - all statements seen by lua code have been passed through SPI_keepplan and are managed by Lua garbage collection. (It was never safe to do otherwise.) (SPI interface is particularly subject to change - in particular to something more compatible with client-side database APIs) print() is still a global function to print an informational message, but other error levels such as debug, notice are installed as spi.debug(), spi.warning() etc. spi.elog('error', ...) is equivalent to spi.error(...) and so on. spi.error() and friends can take optional args: spi.error('message') spi.error('sqlstate', 'message') spi.error('sqlstate', 'message', 'detail') spi.error('sqlstate', 'message', 'detail', 'hint') spi.error({ sqlstate = ?, message = ?, detail = ?, hint = ?, table = ?, column = ?, ...}) Sqlstates can be given either as 5-character strings or as the string names used in plpgsql: spi.error('invalid_argument', 'message') Subtransactions are implemented via pcall() and xpcall(), which now run the called function in a subtransaction. In the case of xpcall, the subtransaction is ended *before* running the error function, which therefore runs in the outer subtransaction. This does mean that while Lua errors in the error function cause recursion and are eventually caught by the xpcall, if an error function causes a PG error then the xpcall will eventually rethrow that to its own caller. (This is subject to change if I decide it was a bad idea.) e.g. local ok,err = pcall(function() --[[ do stuff in subxact ]] end) if not ok then print("subxact failed with error",err) end Currently there's also an lpcall function which does NOT create subtransactions, but which will catch only Lua errors and not PG errors (which are immediately rethrown). It's not clear yet how useful this is; it saves the (possibly significant) subxact overhead, but it can be quite unpredictable whether any given error will manifest as a Lua error or a PG error. The readonly-global-table and setshared() hacks are omitted. As the trusted language now creates an entirely separate lua_State for each calling userid, anything the user does in the global environment can only affect themselves. Type handling is all different. The global fromstring() is replaced by the pgtype package/function: pgtype(d) -- if d is a pg datum value, returns an object representing its type pgtype(d,n) -- if d is a datum, as above; if not, returns the object describing the type of argument N of the function, or the return type of the function if N==0 pgtype['typename'] pgtype.typename pgtype(nil, 'typename') -- parse 'typename' as an SQL type and return the object for it pgtype.array.typename pgtype.array['typename'] -- return the type for "typename[]" if it exists The object representing a type can then be called as a constructor for datum objects of that type: pgtype['mytablename'](col1,col2,col3) pgtype['mytablename']({ col1 = val1, col2 = val2, col3 = val3}) pgtype.numeric(1234) pgtype.date('2017-12-01') pgtype.array.integer(1,2,3,4) pgtype.array.integer({1,2,3,4}, 4) -- dimension mandatory pgtype.array.integer({{1,2},{3,4}},2,2) -- dimensions mandatory pgtype.numrange(1,2) -- range type constructor pgtype.numrange(1,2,'[]') -- range type constructor or the :fromstring method can be used: pgtype.date:fromstring('string') In turn, datum objects of composite type can be indexed by column number or name: row.foo -- value of column "foo" row[3] -- note this is attnum=3, which might not be the third column if columns have been dropped Arrays can be indexed normally as a[1] or a[3][6] etc. By default array indexes in PG start at 1, but values starting at other indexes can be constructed. One-dimensional arrays (but not higher dimensions) can be extended by adding elements with indexes outside the current bounds; ranges of unassigned elements between assigned ones contain NULL. tostring() works on any datum and returns its string representation. pairs() works on a composite datum (and actually returns the attnum as a third result): for colname,value,attnum in pairs(row) do ... The result is always in column order. ipairs() should NOT be used on a composite datum since it will stop at a null value or dropped column. Arrays, composite types, and jsonb values support a mapping operation controlled by a configuration table: rowval{ map = function(colname,value,attno,row) ... return value end, null = (any value, default nil), discard = (boolean, default false) } arrayval{ map = function(elem,array,i,j,k...) ... return elem end, null = (any value, default nil), discard = (boolean, default false) } jsonbval{ map = function(key,val,...) ... return key,val end, null = (any value, default nil), discard = (boolean, default false), pg_numeric = (boolean, default false) } The result in all cases is returned as a Lua table, not a datum, unless the "discard" option was given as true, in which case no result at all is returned. The map function for arrays is passed as many indexes as the original array dimension. The map function for jsonb values is passed the path leading up to the current key (not including the key) as separate additional parameters. The key is an integer if the current container is an array, a string if the container is an object, and nil if this is a single top-level scalar value (which I believe is not strictly allowed in the json spec, but pg allows it). The key/val returned by the function are used to store the result, but do not affect the path values passed to any other function call. If discard is not specified, then the function is also called for completed containers (in which case val will be a table). If pg_numeric is not true, then numeric values are converted to Lua numbers, otherwise they remain as Datum values of numeric type (for which see below). Substitution of null values happens BEFORE the mapping function is called; if that's not what you want, then do the substitution yourself before returning the result. (If the mapping function itself returns a Lua nil, then the entry will be omitted from the result.) As a convenience shorthand, these work: d(nvl) -> d{null = nvl} d(func) -> d{map = func} d() -> d{} Jsonb supports an inverse mapping operation for construction of json values from lua data: pgtype.jsonb(value, { map = function(val) ... return val end, null = (any value, default nil), empty_object = (boolean, default false) array_thresh = (integer, default 1000) array_frac = (integer, default 1000) } "value" can be composed of any combination of (where "collection" means a value which is either a table or possesses a __pairs metamethod): + Empty collections, which will convert to empty json arrays unless empty_object=true in which case they become empty objects + Collections with only integer keys >= 1, which will convert to json arrays (with lua index 1 becoming json index 0) unless either more than array_thresh initial null values would have to be inserted, or the total size of the array would be more than array_frac times the number of table keys. + Collections with keys which can be stringified: strings or numbers, or tables or userdata with __tostring methods, will convert to json objects. + Values which compare raw-equal to the "null" parameter are converted to json nulls + Values of type nil, boolean, number, string are converted to corresponding json values + Datum values of type pgtype.numeric convert to json numbers + Datum values of other types convert to json in the same way as they do in SQL; in particular, jsonb and json values are included directly, and values with casts to jsonb have those casts respected + Values of other types that possess a __tostring metamethod are converted to strings Unlike the other mapping functions, the map function for this operation is called only for values (including collections), not keys, and is not passed any path information. Range types support the following pseudo-columns (immutable): r.lower r.upper r.lower_inc r.upper_inc r.lower_inf r.upper_inf r.isempty Function arguments are converted to simple Lua values in the case of: + integers, floats -- passed as Lua numbers + text, varchar, char, json (not jsonb), xml, cstring, name -- all passed as strings (with the padding preserved in the case of char(n)) + enums -- passed as the text label + bytea -- passed as a string without any escaping or conversion + boolean -- passed as boolean + nulls of any type -- passed as nil + refcursor values are converted to or from SPI cursor objects (whether or not they correspond to open portals) + domains over any of the above are treated as the base types Other values are kept as datum objects. The trusted language is implemented differently - rather than removing functions and packages, the trusted language evaluates all user-supplied code (everything but the init strings) in a separate environment table which contains only whitelisted content. A mini version of the package library is installed in the sandbox environment, allowing package.preload and package.searchers to work (the user can install their own function into package.searchers to load modules from database queries if they so wish). See the main documentation for details on making additional modules available to the trusted language. A set-returning function isn't considered to end until it either returns or throws an error; yielding with no results is considered the same as yielding with explicit nils. (Old version killed the thread in that scenario.) A set-returning function that returns on the first call with no result is treated as returning 0 rows, but if the first call returns values, those are treated as the (only) result row. Trigger functions no longer have a global "trigger" object, but rather are compiled with the following definition: function(trigger,old,new,...) --[[ body here ]] end "trigger" is now a userdata, not a table, but can be indexed as before. Trigger functions may assign a row to trigger.row, or modify fields of trigger.row or trigger.new, or may return a row or table; if they do none of these and return nothing, they're treated as returning trigger.row unchanged. Note that returning nil or assigning row=nil to suppress the triggered operation is in general a bad idea; if you need to prevent an action, then throw an error instead. An interface to pg's "numeric" type (decimal arithmetic) is provided; see the main documentation for details. Polymorphic and variadic functions are fully supported, including VARIADIC "any". VARIADIC of non-"any" type is passed as an array as usual. Interpreters are shut down on backend exit, meaning that finalizers will be run for all objects at this time (including user-defined ones). Currently, SPI functionality is disabled during exit. AUTHOR ------ Andrew Gierth, aka RhodiumToad The author acknowledges the work of Luis Carvalho and other contributors to the original pllua project (of which this is a ground-up redesign). License: MIT license pllua-ng-REL_2_0_4/TODO000066400000000000000000000000701347047754200146020ustar00rootroot00000000000000 Stuff to do. ============ * Improve error reporting pllua-ng-REL_2_0_4/doc/000077500000000000000000000000001347047754200146625ustar00rootroot00000000000000pllua-ng-REL_2_0_4/doc/building.md000066400000000000000000000120471347047754200170050ustar00rootroot00000000000000Building PL/Lua =============== GNU Make is required to build, as usual for PostgreSQL extensions. This module assumes you have already built Lua itself, either as a shared library or as an archive library with `-fPIC` (on most platforms a non-PIC archive library will not work). A shared library is recommended. Building the `pllua` module --------------------------- Lua unfortunately does not provide much in the way of infrastructure for determining build locations; accordingly, those have to be specified explicitly to build this module. The following values must be defined on the `make` command line or in the environment: + `LUA_INCDIR`\ directory containing `lua.h`, `luaconf.h`, `lualib.h` + `LUALIB`\ linker options needed to link, typically `-Lsomedir -llua-5.3` And if building with standard Lua: + `LUAC`\ name or full path of the luac binary (bytecode compiler) + `LUA`\ name or full path of the lua binary Or if building with Luajit: + `LUAJIT`\ name or full path of the luajit binary In addition, as for all PGXS modules, `PG_CONFIG` must be set to the name or full path of the `pg_config` binary corresponding to the PostgreSQL server version being compiled against, unless the correct `pg_config` is already findable via `$PATH` (which is usually not the case). Example: make PG_CONFIG=/usr/lib/postgresql/10/bin/pg_config \ LUA_INCDIR="/usr/include/lua5.3" \ LUALIB="-llua5.3" \ LUAC="luac5.3" LUA="lua5.3" install Building the `hstore_pllua` module ---------------------------------- Currently, the `hstore_pllua` module does not need `LUALIB` on most platforms (since it will reference lua functions either exported by `pllua.so` or by a library loaded by `pllua.so`). You should specify `LUALIB` if you're using a shared lua library and your platform isn't exposing symbols from one module's loaded dependencies to other modules. If you're using a shared library then specifying `LIBLUA` unnecessarily is harmless. Example: make -C hstore \ PG_CONFIG=/usr/lib/postgresql/10/bin/pg_config \ LUA_INCDIR="/usr/include/lua5.3" \ LUAC="luac5.3" LUA="lua5.3" install Building the documentation -------------------------- Specifying `BUILD_DOCS=1` will build the HTML documentation from the Markdown doc sources; this requires `cmark` and `xsltproc`. Additionally specifying `BUILD_ICON=1` will include the favicon in the HTML documentation; this requires ImageMagick's `convert` program. `VPATH` builds -------------- Both modules support building with `VPATH`, which can either be explicitly set or, if `make -f /path/to/Makefile` is used to specify a makefile outside the current directory and `VPATH` is not explicitly set, then `VPATH` will be set to the directory containing the Makefile. Luajit options -------------- `PLLUA_CONFIG_OPTS` can be used to control certain aspects of pllua's behavior when built with Luajit. + `-DNO_LUAJIT`\ disables all use of luajit features + `-DUSE_INT8_CDATA`\ convert sql bigints to cdata int64_t The latter is off by default because it has some possibly undesirable effects on bigint handling, especially when serializing to JSON. However, as long as `NO_LUAJIT` was not specified, cdata integers can be freely returned from functions or passed to SQL type constructors. Actual JIT compilation of user-supplied lua code is not affected by any of these options. Porting options --------------- If you have problems building on an unusual platform, then these options might be useful. The values shown are the defaults if any. + `BIN_LD`\ `$(LD) -r -b binary` The command `$(BIN_LD) -o file.o dir/datafile.ext` is assumed to produce `file.o` containing a data section populated with the content of `datafile.ext`, with symbols `_binary_dir_datafile_ext_start` and `_binary_dir_datafile_ext_end` bracketing the data. The default is believed to work for most GNU ld and (recent) LLVM lld targets, but it is known to fail on some non-mainstream architecture distributions. The value of `BIN_LD` can be set to any suitable equivalent command. + `OBJCOPY`\ `objcopy` The output of `BIN_LD` is passed through `OBJCOPY` to make the data section read-only, but this is a non-critical operation. If no working objcopy is available, this can be set to 'false'. + `BIN_ARCH`\ unset + `BIN_FMT`\ unset If both of these are set, then `BIN_LD` is assumed not to work, and instead the command $(OBJCOPY) -B $(BIN_ARCH) -I binary -O $(BIN_FMT) datafile.ext file.o will be used in its place. The following values have been used on linux-mips64el to work around build failures with `ld -r`: BIN_ARCH=mips:isa64r2 BIN_FMT=elf64-tradlittlemips + `LUAJITC`\ `$(LUAJIT) -b -g -t raw` On Luajit, the bytecode compile option only works if luajit has been fully installed. In test environments where only the luajit build dir is otherwise needed, the bytecode compilation step can be skipped by setting `LUAJITC="cp"`. (The bytecode compile can also be skipped in non-luajit builds by setting `LUAC='$(REORDER_O) cp'` but this is not expected to be useful.) pllua-ng-REL_2_0_4/doc/css.css000066400000000000000000000046661347047754200162000ustar00rootroot00000000000000 body { margin: 0; padding: 0; font-family: verdana, sans-serif; color: #000; background-color: #fff; } .maincolumn { margin: 0 auto; width: 95%; } @media (min-width:60em) { .maincolumn { width: 85%; } } #logo { width: 30vw; height: 30vw; max-width: 30em; max-height: 30em; float: right; margin-top: 2.5vw; margin-right: 0; margin-bottom: 1em; margin-left: 1em; background-color: inherit; background-repeat: no-repeat; background-attachment: scroll; background-size: 100% 100% } @media (min-width:35em) { #logo { margin-top: 1.75em; } } #logo a { display: inline-block; width: 100%; height: 100%; } #topContainer { border-bottom: 1px solid #666; clear: both; } #bodycontent { clear: both; } #footerContainer { margin: 3em 0 0 0; padding: 0.5em 0; border-top: 2px solid #666; background-color: #ddd; color: #000; } dd p { margin-top: 0.5em; } p, ol, ul, dl, pre { line-height: 140%; } li.tocentry-1 { margin-top: 0.5em; } dd { margin-bottom: 0.5em; } h1 { font-size: 175%; } h2 { font-size: 150%; } h3 { font-size: 120%; } h1, h2, h3 { margin-top: 1em; margin-bottom: 0.5em; } @supports ( display: flex ) { .bodycontent h1, .bodycontent h2 { display: flex; flex-direction: row; flex: none; } .bodycontent h1::after, .bodycontent h2::after { margin-left: 1em; flex: auto; align-self: center; content: "\a0"; height: 0px; } .bodycontent h1::after { border-top: 2px #ddd solid; } .bodycontent h2::after { border-top: 1px #ddd solid; } } code { font-size: 120%; } pre code { padding: 0; line-height: 150%; } .bodycontent .shortcode, .bodycontent .longcode { padding: 2px 3px; color: #000; background-color: #eee; } .footer .shortcode, .footer .longcode { padding: 2px 3px; color: #000; background-color: #ccc; } /* prevent line-breaks in short code spans */ .shortcode { white-space: pre; } .longcode { white-space: pre-wrap; } .bodycontent pre.codeblock { margin: 1em 2em; padding: 1em; border-radius: 8px; border-width: 1px; border-style: solid; color: #000; background-color: #eee; box-shadow: 3px 3px 5px #ddd; border-color: #ccc; overflow: auto; } a[rel~="external"] { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAW0lEQVQYlXWQgQ0AIQgD3cmd2Mmd2Kn/RPtCfZs0JngBSmsqA36d1YfDXqsOkB27wBM0RzWgTT8wFwjFy84FVIghYucCxmcUN+RgwLX8Hk1oTnBJauc5ruDVSQ92Teh+W2MqsAAAAABJRU5ErkJggg==) center right no-repeat; padding-right: 13px; } /* eof */ pllua-ng-REL_2_0_4/doc/endnote.md000066400000000000000000000011751347047754200166440ustar00rootroot00000000000000 pllua-ng-REL_2_0_4/doc/intro.md000066400000000000000000000115241347047754200163420ustar00rootroot00000000000000PL/Lua Introduction =================== PL/Lua is a procedural language module for the PostgreSQL database that allows server-side functions to be written in Lua. Quick start ----------- create extension pllua; create function hello(person text) returns text language pllua as $$ return "Hello, " .. person .. ", from Lua!" $$; select hello('Fred'); hello ------------------------ Hello, Fred, from Lua! (1 row) Basic Examples -------------- ### Using `print()` for interactive diagnostics Anything passed to the `print()` function will be raised as a notification at `INFO` level, causing `psql` to display it interactively; program clients will usually just ignore non-error notices. create function print_lua_ver() returns void language pllua as $$ print(_VERSION) $$; select print_lua_ver(); INFO: Lua 5.3 print_lua_ver --------------- (1 row) ### Simple arguments and results Simple scalar types (integers, floats, text, bytea, boolean) are converted to the matching Lua type, and conversely for results. create function add2(a integer, b integer) returns integer language pllua as $$ return a + b $$; Other data types are passed as userdata objects that can be converted to strings with `tostring()` or accessed via provided methods and metamethods. In particular, arrays and records are accessible in most ways as though they were Lua tables, though they're actually not. create type myrow as (a integer, b text[]); create function foo(rec myrow) returns myrow language pllua as $$ print("a is", rec.a) print("b[1] is", rec.b[1]) print("b[2] is", rec.b[2]) return { a = 123, b = {"fred","jim"} } $$; select * from foo(row(1,array['foo','bar'])::myrow); INFO: a is 1 INFO: b[1] is foo INFO: b[2] is bar a | b -----+------------ 123 | {fred,jim} (1 row) ### Sum of an array create function array_sum(a integer[]) returns integer language pllua as $$ local total = 0 for k,v in pairs(a) do total = total + v end return total $$; The above assume single-dimension arrays with no NULLs. A more generic method uses the array mapping function provided by the array userdata: create function array_sum(a integer[]) returns integer language pllua as $$ local total = 0 a{ null = 0, map = function(v,...) total = total + v end, discard = true } return total $$; ### Simple database queries The local environment created for each function is a good place to cache prepared queries: create table objects (id integer primary key, value text); create function get_value(id integer) returns text language pllua stable as $$ local r = q:execute(id) return r and r[1] and r[1].value or 'value not found' end do -- the part below will be executed once before the first call q = spi.prepare("select value from objects where id=$1") $$; The result of executing a query is a table containing rows (if any) for select queries, or an integer rowcount for queries that do not return rows. ### Triggers create function mytrigger() returns trigger language pllua as $$ -- trigger functions are implicitly declared f(trigger,old,new,...) new.total_cost = new.price * new.qty; return new $$; ### JSON handling Values of type `json` are passed to Lua simply as strings. But the `jsonb` data type is supported in a more direct fashion. `jsonb` values can be mapped to Lua tables in a configurable way, and Lua tables converted back to `jsonb` values: create function add_stuff(val jsonb) returns jsonb language pllua as $$ local t = val{} -- convert jsonb to table with default settings t.newkey = { { foo = 1 }, { bar = 2 } } return t $$; select add_stuff('{"oldkey":123}'); add_stuff ----------------------------------------------------- {"newkey": [{"foo": 1}, {"bar": 2}], "oldkey": 123} (1 row) The above simplistic approach will tend to drop json null values (since Lua does not store nulls in tables), and loses precision on numeric values not representable as floats; this can be avoided as follows: create function add_stuff(val jsonb) returns jsonb language pllua as $$ local nullval = {} -- use some unique object to mark nulls local t = val{ null = nullval, pg_numeric = true } t.newkey = { { foo = 1 }, { bar = 2 } } return t, { null = nullval } $$; select add_stuff('{"oldkey":[147573952589676412928,null]}'); add_stuff ------------------------------------------------------------------------------- {"newkey": [{"foo": 1}, {"bar": 2}], "oldkey": [147573952589676412928, null]} (1 row) Tables that originated from JSON are tagged as to whether they were originally objects or arrays, so as long as you provide a unique null value, this form of round-trip conversion should not change anything. (See the `pllua.jsonb` module documentation for more detail.) pllua-ng-REL_2_0_4/doc/logo.svg000066400000000000000000000107741347047754200163540ustar00rootroot00000000000000 pllua-ng-REL_2_0_4/doc/pllua.md000066400000000000000000001264551347047754200163360ustar00rootroot00000000000000PL/Lua Reference ================ PostgreSQL environment ---------------------- PL/Lua provides two extensions: create extension pllua; -- installs the trusted language create extension plluau; -- installs the untrusted language Two optional transform modules exist which are useful if the optional "hstore" extension is loaded: create extension hstore_pllua; -- for hstore type in pllua create extension hstore_plluau; -- for hstore type in plluau These allow direct conversions between hstore values and Lua tables. The following optional configuration settings apply to PL/Lua. Most of them require superuser privileges to set. + `shared_preload_libraries='pllua'` If set, `pllua.so` will be loaded in the postmaster process and the `pllua.on_init` string run there. Be careful with this, since errors in the init string will prevent PostgreSQL from starting. The benefit of this is that additional modules can be require'd into the interpreter and inherited by child processes via `fork()`. Most applications will likely not need this. By default, `pllua.so` is loaded and the init strings run on the first use within each database session. + `pllua.check_for_interrupts=boolean` (default: `true`) If set, a hook function checks for a query cancel interrupt at intervals while running Lua code. + `pllua.on_init='lua code chunk'` If set, this string is loaded and run early in the interpreter setup process. If `shared_preload_libraries` is used (see below), this string is run in the postmaster process (which is useful for preloading code to be inherited via `fork()`). No database access is possible. The `print()` function will output to the server log. + `pllua.on_trusted_init='lua code chunk'` + `pllua.on_untrusted_init='lua code chunk'` This string is run late in initialization of a trusted or untrusted interpreter, as applicable. It can do database access. The trusted init string is run outside the trusted environment, so it has full access to the system; if it wishes to expose loaded modules to the trusted environment, this must be done explicitly with the `trusted.allow()` or `trusted.require()` functions described below. + `pllua.on_common_init='lua code chunk'` This string is run late (after the previous init strings) in initialization of any interpreter. It can do database access. For trusted interpreter, the string is run inside the sandbox. + `pllua.install_globals=boolean` (default: `true`) If true, the `spi` and `pgtype` modules are stored as global tables, as if by: _G.spi = require 'pllua.spi' _G.pgtype = require 'pllua.pgtype' If false, this is not done, and functions wanting to access these modules will need to require them explicitly. + `pllua.prebuilt_interpreters=integer` (default: 1) If `pllua.so` was loaded in `shared_preload_libraries`, this specifies how many Lua states (interpreters) to prebuild. The `on_init` string is run independently in each one. The sole benefit of prebuilding more than one interpreter is if you expect most database sessions to use both trusted and untrusted language functions, or trusted language functions called from `SECURITY DEFINER` functions under more than one user. New states are always created on demand as needed within each session if the prebuilt ones are used up. The default is to create 1 prebuilt state if loaded from `shared_preload_libraries`. + `pllua.interpreter_reload_ident='arbitrary string'` (default: unset) If `pllua.so` is loaded in the postmaster, then altering this setting will cause any prebuilt interpreters to be destroyed and recreated. Also, if this value is set to a nonempty string, altering the value of `pllua.on_init` will also cause prebuilt interpreters to be rebuilt. The value of `pllua.interpreter_reload_ident` is stored in the created interpreters (as `_G._PL_IDENT`) for verification purposes. If this value is unset or empty then prebuilt interpreters are not reloaded except by postmaster restart. Additionally, altering the value causes `_G._PL_IDENT_NEW` to be set to the new value in existing active interpreters before their next use after the value changes. + `pllua.extra_gc_multiplier=real` (min 0, default 0, max 1000000) + `pllua.extra_gc_threshold=real` (min 0, default 0) These options do not require superuser privilege. If `multiplier` is 0 (the default), then no additional garbage collection is done. If `multiplier` is set to a value greater than 0 but less than 1000000, then the amount of non-Lua memory newly allocated by the module is estimated, and before each return to the user, if that amount is at least `threshold` kbytes, then a `LUA_GCSTEP` call is made with a parameter of `(allocated_kbytes * multiplier)`. If `multiplier` is set to 1000000, then a `LUA_GCCOLLECT` call is made instead. Lua environment --------------- The Lua interpreters are initialized as follows. The standard Lua libraries are installed and a number of global functions are replaced: + `print()` replaced with a version that outputs `INFO:` messages to the client (except in the init strings, where it outputs `LOG:` to the server log) + `pcall()` + `xpcall()` replaced with versions that provide subtransaction support + `lpcall()` "light" pcall with no subtransactions, but which doesn't catch all errors + `coroutine.resume()` replaced with a version that propagates PG errors, like lpcall Then the `pllua.trusted` module is loaded and initialized, but not stored into any global variable (it can be accessed with `require`). Then the `on_init` string is run if it is set. Then the equivalent of the following is done: require 'pllua.elog' require 'pllua.funcmgr' if install_globals then _G.pgtype = require 'pllua.pgtype' _G.spi = require 'pllua.spi' else require 'pllua.pgtype' require 'pllua.spi' end require 'pllua.trigger' require 'pllua.numeric' require 'pllua.jsonb' and in trusted interpreters only, the `pllua.trusted` module is assigned to the global `_G.trusted` (outside the sandbox). Then the `on_trusted_init` or `on_untrusted_init` string is run if set. Then the `on_common_init` string is run if set. Each module is described below. PL/Lua code is invoked in two ways. Inline code blocks are invoked as: DO LANGUAGE pllua $$ string... $$; This is processed as if by the following Lua code: function inline(str) local env = setmetatable({}, { __index = _G }) local chunk = assert(load(str,"DO-block","t",env)) chunk(env) end SQL-callable function or procedure (PostgreSQL 11+ only) definitions are created as: CREATE FUNCTION name(args...) RETURNS ... LANGUAGE pllua AS $$ body $$; CREATE PROCEDURE name(args...) LANGUAGE pllua AS $$ body $$; These are handled as follows. When the function is first called in a session, the body is processed as if by the following Lua function: function compile(name,argdef,body) local env = setmetatable({}, { __index = _G }) local fmt = "local self = (...) local function %s(%s) %s end return %s" local chunk = assert(load(string.format(fmt,name,argdef,body,name), name,"t",env)) return chunk(env) end For non-trigger functions, the `argdef` string lists the names of named arguments (if any) followed by a `...` varargs definition if not all arguments have names (named arguments must not follow unnamed ones). For trigger functions, the `argdef` string is always `"trigger,old,new,..."` (where the additional arguments come from the `CREATE TRIGGER` definition). For event triggers, it is simply `"trigger"`. The intended effect is that functions and do-blocks run in their own `self` environment which inherits the global one. They can still set global variables, but must do so explicitly. Functions can do their own first-call initialization by ending the function block early: create function name(args)... as $$ --[[ code here to execute on normal call]] end do --[[ code here is executed only before first call]] $$; `pllua.elog` ------------ The pllua.elog module is a table of simple functions: elog(severity, message) elog(severity, sqlstate, message) elog(severity, sqlstate, message, detail) elog(severity, sqlstate, message, detail, hint) elog(severity, { sqlstate = ?, message = ?, detail = ?, hint = ?, table = ?, column = ?, datatype = ? constraint = ? schema = ? }) debug(...) = elog('debug',...) log(...) = elog('log',...) info(...) = elog('info',...) notice(...) = elog('notice',...) warning(...) = elog('warning',...) error(...) = elog('error',...) This is just the obvious wrapper around pg's ereport() call. `sqlstate` parameters may be either 5-character codes or the error names from the appendix to the PostgreSQL manual. By default these functions are also available via the `spi` module. `pllua.funcmgr` ------------- This module exposes nothing to Lua. `pllua.pgtype` ------------ The pgtype object provides the following functionality: + `pgtype(value)`\ if value is a Datum type, returns its typeinfo, else nil + `pgtype(value,0)`\ if value is a Datum type, returns its typeinfo, otherwise returns the typeinfo of the result type of the current function (if any) + `pgtype(value,argno)`\ if value is a Datum type, returns its typeinfo, otherwise returns the typeinfo of argument `"argno"` (`1..n`) of the current function (if any). This is the recommended way to get the type of a function parameter which might have been transparently converted to a Lua value. + `pgtype['typename']`\ `pgtype.typename`\ parse `'typename'` as an SQL type string and return the typeinfo (or nil if no such type exists) + `pgtype.array['typename']`\ `pgtype.array.typename`\ parse `'typename'` as an SQL type string and return the typeinfo of its array type (or nil if no such type exists) The typeinfo object returned from any of the above has the following functionality: + `typeinfo(datum)`\ Construct a new `Datum` object by copying from the specified value, which must already be of a compatible type + `typeinfo(...)`\ Construct a new `Datum` object of the specified type from the arguments given. The nature of the arguments varies according to the category of type being constructed. + `typeinfo:fromstring(str)`\ Construct a new `Datum` object given its standard text representation in `str`. For some types the distinction between `typeinfo:fromstring(str)` and `typeinfo(str)` is significant. + `typeinfo:frombinary(str)`\ Construct a new `Datum` object given its wire-protocol binary representation in `str`. This is less useful than it might seem because for many data types, the interpretation of the binary representation is dependent on the client_encoding setting. + `typeinfo:name([typmod])`\ Returns the name of the type as SQL syntax (same as the `format_type` function in SQL, or `::regtype` output) + `typeinfo:element()`\ For array or range types, returns the typeinfo of the element type + `typeinfo:element(str)`\ For row types, returns the typeinfo of the named column The type constructor call has the following forms according to the type category (scalar, row, array, range) + `scalartype(nondatum...)` In order, stopping on the first success: 1. If the input value is not a single string, and a transform function exists for this type, then the transform function is called to try and convert the value. Certain types (for example `jsonb` and, in version 2.0.3+ of this module, date/time types), have built-in special transform functions which are documented below. 0. If there is more than one input value, an error is raised. 0. The built-in simple transforms from Lua values to SQL types are tried, including checking for domains over known types. Note: in some cases, especially `bytea`, this gives a different result for string input than `:fromstring` would. 0. If the input is a single string, it is processed as if by `scalartype:fromstring(str)` 0. Otherwise an error is raised. + `rowtype(table)` If passed a single Lua table or userdata (other than a `Datum`), this is assumed to be indexable by column names, and a row is constructed by applying the typeinfo operation of each column type to the indexed value. + `rowtype(...)` otherwise, the number of arguments must equal the arity of the row (i.e. the number of undropped columns). Each argument is matched positionally to its column, converted to the column's type, and then has typmod coercion applied if necessary (e.g. length checks for `varchar(n)`, padding for `char(n)` etc.) + `arraytype()` constructs an empty array. + `arraytype(val,val,val,...)` constructs a one-dimensional array of the specified values. _(currently, the ambiguous case where one single Datum is passed is resolved as the generic typeinfo(datum) call, NOT this one)_ + `arraytype(table, dim...)` One integer value must be given for each dimension of the array. The table is indexed accordingly to populate the new array. + `arraytype(table)` Constructs a one-dimensional array assuming the largest integer index in the table as the array size. (Use the above form for multi-dimensional arrays or for precise control over the size when trailing nulls are allowed.) + `rangetype()` constructs an empty range + `rangetype(string)` as for `rangetype:fromstring(string)` + `rangetype(lo,hi[,bounds])` Constructs a range from specified bounds, with nil values treated as infinities, and the "bounds" string interpreted in the usual way (i.e. `"[]"`, `"[)"`, `"(]"`, `"()"`). Some specific types have additional functions: see the `pllua.jsonb`, `pllua.numeric` and `pllua.time` modules. `Datum` values themselves provide the following: + `tostring(datum)` returns the datum's standard text representation (inverse of `typeinfo:fromstring()`) (tobinary function/syntax TBD) `Datum` values of row types allow indexing by name or number: rowval.column_name rowval['column_name'] rowval[attno] Indexed column values can be assigned to. Note that the `attno` does not correspond to the positional index of the column if columns have been dropped. Row types can be iterated with `pairs()` (but do NOT use ipairs): for colname,value,attno in pairs(rowval) do ... This iteration is always in column order. `Datum` values of array type allow indexing by number, including multiple dimensions, and assignments to individual elements: arrayval[i] arrayval[i][j] etc. Arrays can be iterated with `pairs()` and, in some Lua versions only, `ipairs()`: for i,val in ipairs(arrayval) do ... `Datum` values of range type provide the following immutable pseudo-columns: r.lower r.upper r.lower_inc r.upper_inc r.lower_inf r.upper_inf r.isempty `Datum` values of row, array or `jsonb` type provide a mapping/deserialization operation: rowval{ map = function(colname,value,attno,row) ... return value end, null = (any value, default nil), discard = (boolean, default false) } arrayval{ map = function(elem,array,i,j,k...) ... return elem end, null = (any value, default nil), discard = (boolean, default false) } jsonbval{ map = function(key,val,...) ... return key,val end, null = (any value, default nil), discard = (boolean, default false), pg_numeric = (boolean, default false) } The result in all cases is returned as a Lua table, not a datum, unless the "discard" option was given as true, in which case no result at all is returned. The map function for arrays is passed as many indexes as the original array dimension. The map function for `jsonb` values is passed the path leading up to the current key (not including the key) as separate additional parameters. The key is an integer if the current container is an array, a string if the container is an object, and nil if this is a single top-level scalar value (which I believe is not strictly allowed in the JSON spec, but PostgreSQL allows it). The `key`/`val` returned by the function are used to store the result, but do not affect the path values passed to any other function call. If `discard` is not specified, then the function is also called for completed containers (in which case `val` will be a table). If `pg_numeric` is not true, then numeric values are converted to Lua numbers, otherwise they remain as `Datum` values of `numeric` type (for which see below). All tables returned from a `jsonb` mapping will be tagged with metatables that record whether they were originally arrays or objects; see the `pllua.jsonb` module for details. Substitution of null values happens BEFORE the mapping function is called; if that's not what you want, then do the substitution yourself before returning the result. (If the mapping function itself returns a Lua nil, then the entry will be omitted from the result.) The built-in simple type transformations from PG to Lua are as follows: text, varchar(n), char(n), xml, json, name, cstring -> string bytea -> string, WITHOUT any escaping or conversions enum -> string boolean -> boolean float4, float8 -> number oid, smallint, integer -> number bigint -> number IF the underlying Lua has 64-bit integers refcursor -> SPI cursor object NULL of any type -> nil If a transform function is defined for a given type, then it behaves as if added to the list of simple transformations. Otherwise, values received from PG remain as Datum objects. The built-in simple transforms from Lua to PG are: nil -> any type boolean -> boolean string -> text, varchar, cstring, refcursor string -> bytea, WITHOUT conversion or escaping string -> boolean (accepts only "true","t","1","false","f","0") number -> smallint, integer, bigint, oid (error unless exact integer) number -> float4, float8 number -> boolean (accepts only 0 or 1) number -> numeric SPI cursor object -> refcursor Conversions not listed as "simple transforms" are done with either a builtin special transform, an SQL transform function if defined, or the type constructor as detailed above. Notice that for `bytea`, the simple transform just copies the bytes (Lua strings are byte strings, not character strings). This makes the simple conversion quite different to the `fromstring`/`tostring` conversion, which uses the PG text representation. `pllua.spi` --------- The spi module provides the following functionality (as a table of functions): + `spi.execute("query text", arg, arg, ...)` + `spi.execute_count("query text", maxrows, arg, arg, ...)` execute the given query text as SQL with the given arguments. Returns a table containing a sequence (possibly empty) of rows for queries that return rows, otherwise returns an integer count. For all query execution methods, if called from a nonvolatile function, the query will be run in "readonly" mode using the caller's snapshot. Otherwise a new snapshot is taken. + `spi.prepare("query text", {argtypes}, [{options}])` returns a statement object. `{argtypes}` is a table containing type names or typeinfo objects. Allowed options are: * `scroll = true` or `false` * `no_scroll = true` * `hold = true` * `fast_start = true` * `custom_plan = true` * `generic_plan = true` * `fetch_count = integer` The `fetch_count` option is used only by `rows()` iterators. + `spi.rows("query text", args...)` returns an iterator: for r in spi.rows("query") do ... + `spi.findcursor("name")` if "name" is the name of an open portal (i.e. cursor), then returns a cursor object to access this portal. Otherwise returns nil. The cursor is marked as unowned (it will not be closed by garbage collection). + `spi.newcursor(["name"])` if "name" is the name of an open portal (i.e. cursor), then returns a cursor object (unowned) to access this portal. Otherwise creates a new cursor object with no portal, recording the name given for use with a later open() call. + `spi.is_atomic()` returns true if the call context is atomic with respect to (top-level) transactions; this is always true in pg versions before PostgreSQL 11; it is only false when code is being executed in PostgreSQL 11+ in a `CALL` or `DO` statement which is outside any explicit transaction. (These are the only contexts in which `spi.commit` and `spi.rollback` are allowed.) + `spi.commit()` + `spi.rollback()` (Not defined in pg versions before 11). If in a non-atomic context, these commit or abort the current transaction, and immediately start a new one. An error is raised if they are attempted in an atomic context or inside a subtransaction. + `spi.elog(...)` + `spi.error(...), .warning(...), .notice(...), .info(...), .debug(...), .log(...)` These functions from pllua.elog are accessible via spi.* for convenience. SPI statement objects have the following functionality: + `stmt(arg, arg, ...)` + `stmt:execute(arg, arg, ...)` + `stmt:execute_count(maxrows, arg, arg, ...)` execute the statement, with the same result as spi.execute + `stmt:getcursor(arg, arg, ...)` return an open cursor (with an arbitrarily assigned name) for the statement. The cursor is marked as owned. + `stmt:rows(arg, arg, ...)` return an iterator for the statement execution, as `spi.rows()` + `stmt:numargs()` returns an integer giving the expected number of arguments (including any unused numbered params) expected + `stmt:argtype(argnum)` returns the typeinfo for the expected type of the specified arg SPI cursor objects have the following functionality: + `cur:open(stmt,arg,arg...)` + `cur:open(query_string,arg,arg...)` The cursor object must not be already open. The specified statement or query string is executed in a new portal whose name is given by the cursor name (if one has been assigned). The original cursor object is returned. Cursors returned by an `open()` call are marked as owned. + `cur:isopen()` returns true if the cursor is open + `cur:close()` close the portal (whether or not we created it or own it) + `cur:isowned()` returns true if the cursor is marked as owned. An "owned" cursor has its portal closed (if it's still open) if the cursor object is garbage-collected; this is intended for cursors opened by Lua functions and not returned to callers. An unowned cursor's portal is not affected by the collection of the cursor object. + `cur:own()` + `cur:disown()` mark the cursor as owned or not. Returns the cursor. Typical intended use is `return c:disown()` when returning a cursor opened by a function to its caller. + `cur:name()` returns the open portal name (if the cursor is open) or the assigned name (if not). + `cur:fetch([n, [dir]])` Fetch according to the specified number and direction parameters. `"dir"` can be: * `"forward" / "next"`\ fetch N rows forward * `"backward" / "prior"`\ fetch N rows backward * `"absolute"`\ fetch row at absolute position `n` * `"relative"`\ fetch row at relative position `n` By default, fetch one row in the forward direction. + `cur:move([n, [dir]])` Move the cursor without fetching. Note that the cursor is left at the same position it would be _after_ executing the same operation as a fetch. So to position the cursor such that the next forward fetch will return the first row, use `cur:move(0, 'absolute')` There can only be one cursor object for a given open portal - doing a findcursor on an existing cursor will always return the same object. (But note that this matching is by portal, not name - if a cursor was closed and reopened with the same name, findcursor will return a different object for the new cursor.) If a cursor is closed by external code (or transaction end), then the `:isopen()` state will be automatically updated (this happens when the portal is actually dropped). Cursor options are set on the statement object. `refcursor` parameters and results are transparently converted to and from SPI cursor objects. But note that when returning a cursor from a function, it should be explicitly disowned to ensure that garbage collection won't close it from under the caller's use of it. `pllua.trusted` ------------- The trusted interpreter is implemented using a sandbox system; trusted-language code is run in an environment into which only safe functions have been copied (or proxied). However, in order to allow administrators to provide access to additional modules inside the sandbox in a controlled manner, the initialization strings on_init and on_trusted_init are run outside the sandbox and the functions in pllua.trusted can be used by those strings to make additional modules accessible. For example, setting pllua.on_trusted_init='trusted.allow{"lpeg","re"}' would load the `lpeg` and `re` modules and make them accessible inside the sandbox via `require "lpeg"` etc. **THE ADMINISTRATOR IS RESPONSIBLE FOR ASSESSING THE SECURITY AND SAFETY OF MODULES.** It must be stressed that many modules, whether implemented in Lua or C, perform operations that will either violate security or risk crashing the server. A non-exhausive list of things that are dangerous in modules would include:
* any assumption that the caller's `_G` or `_ENV` is the same as the module's, or any exposure of the module's `_G` to the caller * any i/o or networking functionality exposed by the module to the caller * any use of `lua_pcall` or `lua_resume` from C to call code that might throw an SQL error
The available functions are: + `trusted.allow(module, newname, mode, global, preload)` This makes the module `module` accessible via `require 'newname'` (`newname` is defaulted to `module` if nil or omitted) inside the sandbox using the adapter specified by `mode` (default `"proxy"`). The module is not actually loaded until the first `require` unless either `global` or `preload` is a true value. Then, if `global` is true or a string, it executes the equivalent of: _G[ (type(global)=="string" and global) or newname or module ] = require(newname or module) inside the sandbox. Mode can be `"direct"` (exposes the module to the sandbox directly), `"copy"` (makes a recursive copy of it and any contained tables, without copying metatables, otherwise as `"direct"`), and `"proxy"` which returns a proxy table having the module in the metatable index slot (and any table members in the module proxied likewise; `"sproxy"` omits this step). All modes behave like `"direct"` if the module's value is not a table. **PROXY MODE IS NOT INTENDED TO BE A FULLY SECURE WRAPPER FOR ARBITRARY MODULES.** It's intended to make it _possible_ for simple and well-behaved modules or adapters to be used easily while protecting the "outside" copy from direct modification from inside. If the module returns any table from a function, that table might be modified from inside the sandbox. **NEITHER PROXY MODE NOR COPY MODE ARE GUARANTEED TO WORK ON ALL MODULES.** The following constructs (for example) will typically defeat usage of either mode:
* use of empty tables as unique identifiers * use of table values as keys * metatables on the module table or its contents with anything other than `__call` methods
If you find yourself wanting to use this on a module more complex than (for example) "lpeg" or "re", then consider whether you ought to be using the untrusted language instead. If the `module` parameter is actually a table, it is treated as a sequence, each element of which is either a module name or a table `{ 'module', newname, mode, global, preload }` with missing values defaulted to the original arguments. This enables the common case usage to be just\: trusted.allow{"foo", "bar", "baz"} + `trusted.require(module, newname, mode)` equiv. to `trusted.allow(module, newname, mode, true, true)` + `trusted.remove('newname','global')` undoes either of the above (probably not very useful, but you could do trusted.remove('os') or whatever) To use these functions from the on_init string, you must `require 'pllua.trusted'` explicitly, and use the return value of that to access the functions. Passing a true value for the `preload` argument of `trusted.allow` allows for preloading of modules before forking when using prebuilt interpreters. The trusted environment's version of `load` overrides the text/binary mode field (loading binary functions is unsafe) and overrides the environment to be the trusted sandbox if the caller didn't provide one itself (but the caller can still give an explicit environment of nil or anything else). `pllua.trigger` ------------- This module provides nothing directly to Lua, but a `trigger` parameter is passed as the first parameter to trigger functions (and a different trigger parameter to event-trigger functions). The `trigger` object for DDL triggers ("event triggers") provides the following values when indexed: + `trigger.event`\ Event for which the trigger was fired + `trigger.tag`\ Command tag See the PostgreSQL documentation for details. The trigger object for DML triggers provides the following values when indexed: + `trigger.new`\ the "new" row for the operation (or nil) + `trigger.old`\ the "old" row for the operation (or nil) + `trigger.row`\ an alias for whichever of `old` or `new` the operation is expected to return; i.e. `new` for insert or update operations, `old` for deletes + `trigger.name`\ name used in `CREATE TRIGGER` + `trigger.when`\ `"before"`, `"after"` or `"instead"` + `trigger.operation`\ `trigger.op`\ `"insert"`, `"update"`, `"delete"`, `"truncate"` + `trigger.level`\ `"row"` or `"statement"` + `trigger.relation`\ a table The `trigger.relation` table has this form: { ["namespace"] = "public", ["attributes"] = { ["test_column"] = 1, }, ["name"] = "table_name", ["oid"] = 59059 } The fields of the trigger object are immutable with the exception of `trigger.row`, which can be assigned a new row wholesale in order to alter the result of the operation in a before trigger. This immutability does not extend to contained fields: a trigger can instead assign to individual `new.*` fields and the result will reflect this. The result of any trigger function which is not called `BEFORE` or `INSTEAD`, or is not called `FOR EACH ROW`, is ignored (as are any changes it makes to the trigger object). Trigger functions which are called `BEFORE` or `INSTEAD` and `FOR EACH ROW` can do one of three things: 1. To complete the operation normally, with no changes to the data, either return no value at all (not even `nil`), or return `trigger.row` without having assigned to `trigger.row` or any field of `old` or `new`. 0. To complete the operation normally with modified data: 1. A non-nil return value will be converted to the table's row type using the type constructor, and this will be the new tuple, overriding any previous tuple and superseding any changes made to `trigger.row` or `new`/`old`. 0. Returning no value at all (not even `nil`) having modified the content of `trigger.row` (directly or via whichever of `new` or `old` is appropriate for the triggered operation) will result in the value of `trigger.row` being used as the new tuple. 0. To suppress the operation, return the value `nil`, or assign `nil` to `trigger.row`. `pllua.numeric` ------------- PostgreSQL values of `numeric` type (henceforth Numeric values) are converted to `Datum` objects as normal, but this module provides substantial additional functionality for such types. The methods and metamethods for Numeric values are accessible by default; code can `require 'pllua.numeric'` in order to obtain access to the additional non-method functions, e.g.: num = require 'pllua.numeric' if num.equal(x,y) then ... Equality comparison is restricted by Lua semantics; a Numeric value will never compare equal (`==`) to a Lua number, however `==` between two Numerics compares for numerical equality. A plain function `num.equal(x,y)` is provided for comparing equality. Note that Numerics used as table keys will likely not work in any useful way since two equal values are unlikely to compare as raw-equal. Other operations allow mixed types, and will return Numeric if any input value is. Arithmetic operations on Numeric use PG semantics. In particular, the `//` division operation truncates towards zero, not to `-inf`, and the `%` modulus operator returns a result with the sign of the dividend, not the sign of the divisor. These functions are available directly or as methods on a Numeric datum. (As direct calls they allow input of any Lua number.) + `abs`\ `ceil`\ `equal`\ `exp`\ `floor`\ `isnan`\ `sign`\ `sqrt`\ (as expected) + `log`\ (optional base parameter defaults to natural log) + `tointeger`\ returns nil if not exactly representable as a Lua integer + `tonumber`\ returns a Lua number, not exact + `trunc`\ `round`\ take an optional number of digits parameter The function `num.new(x)` will construct a new Numeric datum, as will `pgtype.numeric(x)`. `pllua.jsonb` ----------- `jsonb` supports an inverse mapping operation for construction of JSON values from Lua data: pgtype.jsonb(value, { map = function(val) ... return val end, null = (any value, default nil), empty_object = (boolean, default false) array_thresh = (integer, default 1000) array_frac = (integer, default 1000) }) `value` can be composed of any combination of the following (where "collection" means a value which is either a table or possesses a `__pairs` metamethod):
* Tables which have had the is_object or is_array metatable set (see below), which will convert to objects or arrays respectively (for arrays, any non-integer keys will be ignored) * Empty collections, which will convert to empty json arrays unless `empty_object=true` in which case they become empty objects * Collections with only integer keys not less than 1, which will convert to json arrays (with lua index 1 becoming json index 0) unless either more than `array_thresh` initial null values would have to be inserted, or the total size of the array would be more than `array_frac` times the number of table keys. * Collections with keys which can be stringified (i.e. strings or numbers, or tables or userdata with `__tostring` methods) will convert to json objects. * Values which compare raw-equal to the `null` parameter are converted to json nulls * Values of type `nil`, `boolean`, `number`, `string` are converted to corresponding json values * `Datum` values of type `numeric` convert to json numbers * `Datum` values of other types convert to json in the same way as they do in SQL; in particular, `jsonb` and `json` values are included directly, and values with casts to `jsonb` have those casts respected * Values of other types that possess a `__tostring` metamethod are converted to strings
Unlike the other mapping functions, the map function for this operation is called only for values (including collections), not keys, and is not passed any path information. The use of metatables to distinguish JSON objects and arrays means that the transformation from `jsonb` to Lua tables and back preserves the original content **as long as** a unique `null` value is provided. However, for more complex manipulations, the following functions are available via `require "pllua.jsonb"`: + `jsonb.is_object(table)` Returns true if `table` corresponds to a JSON object, false if it corresponds to an array, and no value if neither + `jsonb.is_array(table)` Returns true if `table` corresponds to a JSON array, false if it corresponds to an object, and no value if neither + `jsonb.set_as_array(table)` Mark the table as corresponding to a JSON array, and return it. The table must not already have a metatable, unless it's the one set by this function or by `set_as_object`. + `jsonb.set_as_object(table)` Mark the table as corresponding to a JSON object, and return it. The table must not already have a metatable, unless it's the one set by this function or by `set_as_array`. + `jsonb.set_as_unknown(table)` Mark the table as not corresponding to either a JSON object or array, and return it. This is the only way to remove the metatable that marks the JSON type, so you may need it if you want to apply some other metatable instead. `pllua.paths` ----------- This module was added in version 2.0.2. This module (not available in trusted mode) provides functions which return the locations of server directories: + `bin()`\ the directory containing server executables + `lib()`\ directory for object libraries + `pkglib()`\ `libdir()`\ this is the directory used for `$libdir` expansion in module paths + `share()`\ directory for ancillary read-only data files + `locale`\ directory for locale-dependent data, if any The following paths are also available, though they may not exist (the returned value only indicates where they are expected to be based on the compile-time options and the location of the server binary): + `doc`\ directory for documentation + `etc`\ directory for system-wide config files + `html`\ directory for html documentation + `include`\ directory for include files + `includeserver`\ directory for server-specific include files + `man`\ directory for manual pages + `pkginclude`\ I have no idea what this is supposed to be for `pllua.time` ----------- This module was added in version 2.0.3. SQL types `timestamp with time zone` (aka `timestamptz`), `timestamp`, `date`, `time`, `timetz`, and `interval` (collectively referred to here as "datetime types") have additional functionality provided by this module. All of this functionality is currently available by default; the module does not need to be explicitly loaded. Datetime types allow the following type constructor:
pgtype.typename({ args... })
where the parameter is a table similar to that used by `os.time` with many extensions, detailed below. Datum values of datetime types also support the following method call: d:as_table() which returns the value broken down into a table of calendar values. For `timestamp with time zone` only, d:as_table(timezone) performs the same breakdown but returns the result relative to the specified timezone name (abbreviations not permitted) or offset, following the same rules as for the `timezone` field. (If the input value is infinite, the table will contain only an infinite-valued `epoch` field; otherwise no `epoch` field will be present.) Datum values of datetime types also support field accesses such as: d.week d.epoch d.epoch_msec d.isoyear etc. The available field names are those supported by the SQL `extract()` function as documented in the [PostgreSQL manual](https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT). The following extra fields are also supported: * `isoweek`\ alias for `week` (both forms use the ISO week number) * `epoch_msec`\ `epoch` value scaled to milliseconds * `epoch_usec`\ `epoch` value scaled to (integer) microseconds The following entries are recognized in tables representing datetime values: * `year`\ Calendar year, e.g. 2019 * `month`\ Month number 1..12 * `day`\ Day of month * `hour`\ Hour (24-hour clock) * `min`\ Minute * `sec`\ Second (with fractional values allowed) * `msec`\ Milliseconds (with fractional values allowed) * `usec`\ Microseconds * `isdst`\ If the specified time is within a DST boundary interval, then this specifies whether it is interpreted according to the DST or non-DST time. Otherwise it is ignored. * `epoch`\ Specifies a Unix epoch time in seconds (fractions are allowed) * `epoch_msec`\ Specifies a Unix epoch time in milliseconds (fractions are allowed) * `epoch_usec`\ Specifies a Unix epoch time in microseconds * `timezone`\ If a string, this may be a timezone name (**not** an abbreviation), or a UTC offset in the form "+0100". If a number, then it is taken to be a UTC offset in seconds. A boolean value of `true` means to use the current session timezone, for use in contexts where that is not the default. * `timezone_abbrev`\ Ignored on input, this is set to the timezone abbreviation (e.g. `EST`) for tables generated from `timestamp with time zone` values. Epoch values and calendar values may not both be specified, but the timezone and `msec` and `usec` fractional values may be specified alongside either. Calendar values may be specified outside their normal ranges, and (except for the `interval` type) will be normalized before any conversion. (In particular, any DST boundaries are not taken into account when normalizing.) Millisecond or microsecond values specified may exceed one second, in which case they are applied **after** other conversions (and thus will take DST boundaries into account where appropriate). The values are interpreted according to the requested data type as follows: + `date` If calendar values `year`, `month`, `day` are specified they are used as-is. A timezone must not be specified in this case. If an epoch time is specified, then the result is the calendar day in the specified timezone (or `UTC` if not set) which contains the specified epoch time. + `timestamp with time zone` If an epoch time is specified then no timezone may be specified, and the result corresponds to the specified epoch time. If a calendar date and time is specified, then it is interpreted according to the specified timezone (defaulting to the session timezone if not specified). The time fields are optional and default to 0. + `timestamp` If an epoch time is specified with a timezone, then the result is the corresponding calendar time in the specified timezone at that epoch. If an epoch time is specified with no timezone, then it is interpreted as a seconds offset from 1970-01-01 00:00:00, with no DST transitions. If a calendar date and time is specified, then no timezone may be specified, and the result is the calendar time. The time fields are optional and default to 0. + `time` If an epoch is specified, it is assumed to be an offset since midnight. The result is taken modulo 1 day. If a calendar time is specified, it is used as-is. Date and timezone fields are ignored. + `timetz` **WARNING**: this type should not be used for anything. A timezone value specified must be an integer or a string offset, not a timezone name. Other input is used as for `time`, and the offset field of the result is set to the specified timezone. + `interval` An interval is constructed from any combination of the specified fields, which are not normalized first. pllua-ng-REL_2_0_4/doc/script.js000066400000000000000000000023331347047754200165250ustar00rootroot00000000000000(function(){ "use strict"; /* adjust scroll positions */ var shiftWindow5 = function() { scrollBy(0, -0.05 * window.innerHeight) }; window.addEventListener("DOMContentLoaded",(function(){ if (location.hash) shiftWindow5(); })); window.addEventListener("hashchange", shiftWindow5); /* render the logo into a high-res favicon */ window.addEventListener("DOMContentLoaded",(function(){ var logosrc = (window.getComputedStyle(document.getElementById("logo")) .getPropertyValue("background-image") .match(/data:[^"")]*/)[0]); if (logosrc.length) { var i = new Image(960,960); var render1 = function(id,s) { var c = document.createElement("canvas"); c.width = s; c.height = s; var cxt = c.getContext("2d"); cxt.drawImage(i, 0, 0, s, s); var link = document.createElement("link"); link.id = id; link.rel = "icon"; link.type = "image/png"; link.sizes = s+"x"+s; link.href = c.toDataURL(); return link; }; var render = function() { document.head.appendChild(render1("dyn-icon-192.png", 192)); }; i.src = logosrc; if (i.complete) { render(); } else { i.onload = (function(){ if (i.complete) { render(); } }); } } })); })(); pllua-ng-REL_2_0_4/doc/template.xsl000066400000000000000000000356001347047754200172310ustar00rootroot00000000000000 https://pllua.github.io/pllua-ng/ https://github.com/pllua/pllua-ng/
shortcode longcode S . . . .
  • PL/Lua Documentation

    Contents

    pllua-ng-REL_2_0_4/expected/000077500000000000000000000000001347047754200157165ustar00rootroot00000000000000pllua-ng-REL_2_0_4/expected/arrays.out000066400000000000000000000223121347047754200177500ustar00rootroot00000000000000-- \set VERBOSITY terse -- -- -- By having a real table with both short and long array values, -- we get to involve both short varlenas and external toast. -- create temp table adata (id serial, a integer[], b text[], c numeric[], d date[]); insert into adata(a) values (array[1,2]), (array[10,20,30,40,50]), (array(select i from generate_series(1,100) i)), (array(select i from generate_series(1,100000) i)), ('{}'); insert into adata(b) values ('{}'), (array['foo','bar','baz']), (array(select 'val'||i from generate_series(1,100) i)), (array(select 'val'||i from generate_series(1,10000) i)); insert into adata(c) values ('{}'), (array[1.234,exp(1.0::numeric(32,30)),27!]), (array(select i from generate_series(1,100) i)), (array(select i from generate_series(1,10000) i)); insert into adata(d) values ('{}'), (array[date '2017-12-11', '1968-05-10', '1983-09-26', '1962-10-27']), (array(select date '2017-01-01' + i from generate_series(0,364,18) i)); do language pllua $$ package.preload['myutil'] = function() local expect_next = { string = function(s) return string.gsub(s, "%d+$", function(n) return string.format("%d", n + 1) end) end, number = function(n) return n + 1 end, [pgtype.numeric] = function(n) return n + 1 end, } local function map(a,f) local r = {} for i = 1,#a do r[#r+1] = f(a[i]) end return r end local function summarize(a) if a == nil then return nil end local expect,first_match,result = nil,nil,{} for i = 1,#a do if first_match == nil then expect,first_match = a[i],i elseif a[i] ~= expect then if first_match < i-1 then result[#result+1] = { a[first_match], a[i-1] } else result[#result+1] = a[i-1] end expect,first_match = a[i],i end --[[ update the "expected" next element ]] expect = (expect_next[pgtype(expect) or type(expect)] or function(x) return x end)(expect) end if first_match == #a then result[#result+1] = a[#a] elseif first_match ~= nil then result[#result+1] = { a[first_match], a[#a] } end return table.concat(map(result, function(e) if type(e)=='table' then return string.format("[%s..%s]", tostring(e[1]), tostring(e[2])) else return tostring(e) end end), ',') end return { map = map, summarize = summarize } end $$; do language pllua $$ local u = require 'myutil' for r in spi.rows([[ select a, b, array_append(a, -1) as xa, array_append(b, 'wombat') as xb from adata where a is not null or b is not null order by id ]]) do print(u.summarize(r.a),u.summarize(r.b)) print(u.summarize(r.xa),u.summarize(r.xb)) end for r in spi.rows([[ select c, array_append(c, -1.0) as xc from adata where c is not null order by id ]]) do print(u.summarize(r.c)) print(u.summarize(r.xc)) end for r in spi.rows([[ select d from adata where d is not null order by id ]]) do print(r.d) end $$; INFO: [1..2] nil INFO: [1..2],-1 wombat INFO: 10,20,30,40,50 nil INFO: 10,20,30,40,50,-1 wombat INFO: [1..100] nil INFO: [1..100],-1 wombat INFO: [1..100000] nil INFO: [1..100000],-1 wombat INFO: nil INFO: -1 wombat INFO: nil INFO: -1 wombat INFO: nil foo,bar,baz INFO: -1 foo,bar,baz,wombat INFO: nil [val1..val100] INFO: -1 [val1..val100],wombat INFO: nil [val1..val10000] INFO: -1 [val1..val10000],wombat INFO: INFO: -1.0 INFO: 1.234,2.718281828459045235360287471353,10888869450418352160768000000 INFO: 1.234,2.718281828459045235360287471353,10888869450418352160768000000,-1.0 INFO: [1..100] INFO: [1..100],-1.0 INFO: [1..10000] INFO: [1..10000],-1.0 INFO: {} INFO: {12-11-2017,05-10-1968,09-26-1983,10-27-1962} INFO: {01-01-2017,01-19-2017,02-06-2017,02-24-2017,03-14-2017,04-01-2017,04-19-2017,05-07-2017,05-25-2017,06-12-2017,06-30-2017,07-18-2017,08-05-2017,08-23-2017,09-10-2017,09-28-2017,10-16-2017,11-03-2017,11-21-2017,12-09-2017,12-27-2017} create function af1(a anyarray) returns text language pllua stable as $$ return tostring(u.summarize(a)) end u = require 'myutil' do $$; -- array_append returns its result as an expanded datum select af1(a) from adata where a is not null order by id; af1 ---------------- [1..2] 10,20,30,40,50 [1..100] [1..100000] (5 rows) with t as (select a from adata where a is not null order by id) select af1(array_append(a, -1)) from t; af1 ------------------- [1..2],-1 10,20,30,40,50,-1 [1..100],-1 [1..100000],-1 -1 (5 rows) select af1(b) from adata where b is not null order by id; af1 ------------------ foo,bar,baz [val1..val100] [val1..val10000] (4 rows) with t as (select b from adata where b is not null order by id) select af1(array_append(b, 'wombat')) from t; af1 ------------------------- wombat foo,bar,baz,wombat [val1..val100],wombat [val1..val10000],wombat (4 rows) select af1(c) from adata where c is not null order by id; af1 ---------------------------------------------------------------------- 1.234,2.718281828459045235360287471353,10888869450418352160768000000 [1..100] [1..10000] (4 rows) with t as (select c from adata where c is not null order by id) select af1(array_append(c, -1.0)) from t; af1 --------------------------------------------------------------------------- -1.0 1.234,2.718281828459045235360287471353,10888869450418352160768000000,-1.0 [1..100],-1.0 [1..10000],-1.0 (4 rows) select af1(d) from adata where d is not null order by id; af1 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 12-11-2017,05-10-1968,09-26-1983,10-27-1962 01-01-2017,01-19-2017,02-06-2017,02-24-2017,03-14-2017,04-01-2017,04-19-2017,05-07-2017,05-25-2017,06-12-2017,06-30-2017,07-18-2017,08-05-2017,08-23-2017,09-10-2017,09-28-2017,10-16-2017,11-03-2017,11-21-2017,12-09-2017,12-27-2017 (3 rows) with t as (select d from adata where d is not null order by id) select af1(array_append(d, date '1970-01-01')) from t; af1 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 01-01-1970 12-11-2017,05-10-1968,09-26-1983,10-27-1962,01-01-1970 01-01-2017,01-19-2017,02-06-2017,02-24-2017,03-14-2017,04-01-2017,04-19-2017,05-07-2017,05-25-2017,06-12-2017,06-30-2017,07-18-2017,08-05-2017,08-23-2017,09-10-2017,09-28-2017,10-16-2017,11-03-2017,11-21-2017,12-09-2017,12-27-2017,01-01-1970 (3 rows) -- conversion edge cases create function pg_temp.af2() returns integer[] language pllua as $$ return nil $$; select pg_temp.af2(); af2 ----- (1 row) create function pg_temp.af3() returns integer[] language pllua as $$ return $$; select pg_temp.af3(); af3 ----- (1 row) create function pg_temp.af4() returns integer[] language pllua as $$ return 1,2 $$; select pg_temp.af4(); af4 ------- {1,2} (1 row) create function pg_temp.af5() returns integer[] language pllua as $$ return pgtype(nil,0)() $$; select pg_temp.af5(); af5 ----- {} (1 row) create function pg_temp.af5b() returns integer[] language pllua as $$ return {} $$; select pg_temp.af5b(); af5b ------ {} (1 row) create function pg_temp.af6() returns integer[] language pllua as $$ return { 1, nil, 3 } $$; select pg_temp.af6(); af6 ------------ {1,NULL,3} (1 row) create function pg_temp.af7() returns integer[] language pllua as $$ return pgtype.integer(1) $$; select pg_temp.af7(); af7 ----- {1} (1 row) create function pg_temp.af8() returns integer[] language pllua as $$ return { pgtype.integer(1) } $$; select pg_temp.af8(); af8 ----- {1} (1 row) create type acomp1 as (foo integer, bar text); create function pg_temp.af9() returns acomp1[] language pllua as $$ return { { foo = 1, bar = "zot" } } $$; select pg_temp.af9(); af9 ------------- {"(1,zot)"} (1 row) create function pg_temp.af10() returns acomp1[] language pllua as $$ return { pgtype(nil,0):element()(1, "zot") } $$; select pg_temp.af10(); af10 ------------- {"(1,zot)"} (1 row) -- pllua-ng-REL_2_0_4/expected/event_triggers.out000066400000000000000000000021661347047754200215030ustar00rootroot00000000000000-- \set VERBOSITY terse \set QUIET 0 -- Test event triggers. create function evtrig() returns event_trigger language pllua as $$ print(trigger.event, trigger.tag) $$; CREATE FUNCTION create event trigger et1 on ddl_command_start execute procedure evtrig(); CREATE EVENT TRIGGER create event trigger et2 on ddl_command_end execute procedure evtrig(); CREATE EVENT TRIGGER create event trigger et3 on sql_drop execute procedure evtrig(); CREATE EVENT TRIGGER create event trigger et4 on table_rewrite execute procedure evtrig(); CREATE EVENT TRIGGER create table evt1 (a text); INFO: ddl_command_start CREATE TABLE INFO: ddl_command_end CREATE TABLE CREATE TABLE alter table evt1 alter column a type integer using null; INFO: ddl_command_start ALTER TABLE INFO: table_rewrite ALTER TABLE INFO: ddl_command_end ALTER TABLE ALTER TABLE drop table evt1; INFO: ddl_command_start DROP TABLE INFO: sql_drop DROP TABLE INFO: ddl_command_end DROP TABLE DROP TABLE drop event trigger et1; DROP EVENT TRIGGER drop event trigger et2; DROP EVENT TRIGGER drop event trigger et3; DROP EVENT TRIGGER drop event trigger et4; DROP EVENT TRIGGER --end pllua-ng-REL_2_0_4/expected/horology.out000066400000000000000000000530451347047754200203200ustar00rootroot00000000000000-- \set VERBOSITY terse -- -- 1. Input of datetime values from table form. set timezone = 'GMT'; set datestyle = 'ISO,YMD'; do language pllua $$ print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=2419, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=1919, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=1819, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=1019, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=-2019, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=-4713, month=12, day=1, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34, usec=123456 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, msec=1, usec=1 }); $$; INFO: 2019-04-22 12:23:34.1+00 INFO: 2419-04-22 12:23:34.1+00 INFO: 1919-04-22 12:23:34.1+00 INFO: 1819-04-22 12:23:34.1+00 INFO: 1019-04-22 12:23:34.1+00 INFO: 2020-04-22 12:23:34.1+00 BC INFO: 4714-12-01 12:23:34.1+00 BC INFO: 2019-04-22 12:23:34.123456+00 INFO: 2019-04-22 12:23:34.101001+00 do language pllua $$ print(pgtype.timestamptz{ epoch=0 }); print(pgtype.timestamptz{ epoch=1555891200 }); print(pgtype.timestamptz{ epoch=1555891200.000001 }); print(pgtype.timestamptz{ epoch_msec=1555891200001 }); print(pgtype.timestamptz{ epoch_usec=1555891200000001 }); print(pgtype.timestamptz{ epoch=1555891200, msec=1, usec=1 }); print(pgtype.timestamptz{ epoch=1555891200, msec=-1, usec=1 }); print(pgtype.timestamptz{ epoch=-1555891200 }); print(pgtype.timestamptz{ epoch=-1555891200, msec=1, usec=-1 }); print(pgtype.timestamptz{ epoch=-1555891200, msec=-1, usec=1 }); print(pgtype.timestamptz{ epoch_msec=-1555891200001 }); print(pgtype.timestamptz{ epoch_usec=-1555891200000001 }); $$; INFO: 1970-01-01 00:00:00+00 INFO: 2019-04-22 00:00:00+00 INFO: 2019-04-22 00:00:00.000001+00 INFO: 2019-04-22 00:00:00.001+00 INFO: 2019-04-22 00:00:00.000001+00 INFO: 2019-04-22 00:00:00.001001+00 INFO: 2019-04-21 23:59:59.999001+00 INFO: 1920-09-12 00:00:00+00 INFO: 1920-09-12 00:00:00.000999+00 INFO: 1920-09-11 23:59:59.999001+00 INFO: 1920-09-11 23:59:59.999+00 INFO: 1920-09-11 23:59:59.999999+00 do language pllua $$ print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone=10800 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone=-10800 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="+0300" }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="-0300" }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="+1400" }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="-1400" }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="America/Los_Angeles" }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="Pacific/Auckland" }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="Asia/Kathmandu" }); print(pgtype.timestamptz{ year=2018, month=10, day=28, hour=1, min=30, sec=0, timezone="Europe/London" }); print(pgtype.timestamptz{ year=2018, month=10, day=28, hour=1, min=30, sec=0, isdst=true, timezone="Europe/London" }); print(pgtype.timestamptz{ year=2018, month=10, day=28, hour=1, min=30, sec=0, isdst=false, timezone="Europe/London" }); $$; INFO: 2019-04-22 09:23:34.1+00 INFO: 2019-04-22 15:23:34.1+00 INFO: 2019-04-22 09:23:34.1+00 INFO: 2019-04-22 15:23:34.1+00 INFO: 2019-04-21 22:23:34.1+00 INFO: 2019-04-23 02:23:34.1+00 INFO: 2019-04-22 19:23:34.1+00 INFO: 2019-04-22 00:23:34.1+00 INFO: 2019-04-22 06:38:34.1+00 INFO: 2018-10-28 01:30:00+00 INFO: 2018-10-28 00:30:00+00 INFO: 2018-10-28 01:30:00+00 do language pllua $$ print(pgtype.timestamptz{ epoch=1/0 }); print(pgtype.timestamptz{ epoch=-1/0 }); $$; INFO: infinity INFO: -infinity do language pllua $$ print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=1, usec=59000000 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=-1 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=-60 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=-61 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=60 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=61 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=120 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=0, sec=3661 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=0, sec=43201 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=0, sec=-43201 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=0, sec=864000 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=720, sec=3661 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=24, min=0, sec=0 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=24, min=0, sec=1 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=240, min=0, sec=1 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=-1, min=0, sec=1 }); print(pgtype.timestamptz{ year=2019, month=-4, day=22, hour=12, min=23, sec=34 }); print(pgtype.timestamptz{ year=2019, month=16, day=22, hour=12, min=23, sec=34 }); $$; INFO: 2019-04-22 12:24:00+00 INFO: 2019-04-22 12:22:59+00 INFO: 2019-04-22 12:22:00+00 INFO: 2019-04-22 12:21:59+00 INFO: 2019-04-22 12:24:00+00 INFO: 2019-04-22 12:24:01+00 INFO: 2019-04-22 12:25:00+00 INFO: 2019-04-22 13:01:01+00 INFO: 2019-04-23 00:00:01+00 INFO: 2019-04-21 23:59:59+00 INFO: 2019-05-02 12:00:00+00 INFO: 2019-04-23 01:01:01+00 INFO: 2019-04-23 00:00:00+00 INFO: 2019-04-23 00:00:01+00 INFO: 2019-05-02 00:00:01+00 INFO: 2019-04-21 23:00:01+00 INFO: 2018-08-22 12:23:34+00 INFO: 2020-04-22 12:23:34+00 do language pllua $$ print(pgtype.timestamp{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=2419, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=1919, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=1819, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=1019, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=-2019, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=-4713, month=12, day=1, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=2019, month=4, day=22, hour=12, min=23, sec=34, usec=123456 }); print(pgtype.timestamp{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, msec=1, usec=1 }); $$; INFO: 2019-04-22 12:23:34.1 INFO: 2419-04-22 12:23:34.1 INFO: 1919-04-22 12:23:34.1 INFO: 1819-04-22 12:23:34.1 INFO: 1019-04-22 12:23:34.1 INFO: 2020-04-22 12:23:34.1 BC INFO: 4714-12-01 12:23:34.1 BC INFO: 2019-04-22 12:23:34.123456 INFO: 2019-04-22 12:23:34.101001 do language pllua $$ print(pgtype.timestamp{ epoch=0 }); print(pgtype.timestamp{ epoch=1555891200 }); print(pgtype.timestamp{ epoch=1555891200.000001 }); print(pgtype.timestamp{ epoch_msec=1555891200001 }); print(pgtype.timestamp{ epoch_usec=1555891200000001 }); print(pgtype.timestamp{ epoch=1555891200, msec=1, usec=1 }); print(pgtype.timestamp{ epoch=1555891200, msec=-1, usec=1 }); print(pgtype.timestamp{ epoch=-1555891200 }); print(pgtype.timestamp{ epoch=-1555891200, msec=1, usec=-1 }); print(pgtype.timestamp{ epoch=-1555891200, msec=-1, usec=1 }); print(pgtype.timestamp{ epoch_msec=-1555891200001 }); print(pgtype.timestamp{ epoch_usec=-1555891200000001 }); print(pgtype.timestamp{ epoch=1555891200, timezone="America/Los_Angeles" }); print(pgtype.timestamp{ epoch=1555891200, timezone="Pacific/Auckland" }); print(pgtype.timestamp{ epoch=1555891200, timezone="Asia/Kathmandu" }); $$; INFO: 1970-01-01 00:00:00 INFO: 2019-04-22 00:00:00 INFO: 2019-04-22 00:00:00.000001 INFO: 2019-04-22 00:00:00.001 INFO: 2019-04-22 00:00:00.000001 INFO: 2019-04-22 00:00:00.001001 INFO: 2019-04-21 23:59:59.999001 INFO: 1920-09-12 00:00:00 INFO: 1920-09-12 00:00:00.000999 INFO: 1920-09-11 23:59:59.999001 INFO: 1920-09-11 23:59:59.999 INFO: 1920-09-11 23:59:59.999999 INFO: 2019-04-21 17:00:00 INFO: 2019-04-22 12:00:00 INFO: 2019-04-22 05:45:00 do language pllua $$ print(pgtype.date{ year=2019, month=4, day=22 }); print(pgtype.date{ year=2419, month=2, day=22 }); print(pgtype.date{ year=1919, month=1, day=22 }); print(pgtype.date{ year=1819, month=3, day=22 }); print(pgtype.date{ year=1019, month=5, day=22 }); print(pgtype.date{ year=-2019, month=4, day=22 }); print(pgtype.date{ year=-4713, month=12, day=1 }); print(pgtype.date{ year=2019, month=4, day=22, hour=24 }); print(pgtype.date{ year=2019, month=4, day=22, hour=24, min=1 }); $$; INFO: 2019-04-22 INFO: 2419-02-22 INFO: 1919-01-22 INFO: 1819-03-22 INFO: 1019-05-22 INFO: 2020-04-22 BC INFO: 4714-12-01 BC INFO: 2019-04-22 INFO: 2019-04-23 do language pllua $$ print(pgtype.date{ epoch=0 }); print(pgtype.date{ epoch=1555891200 }); print(pgtype.date{ epoch=1555891200.000001 }); print(pgtype.date{ epoch_msec=1555891200001 }); print(pgtype.date{ epoch_usec=1555891200000001 }); print(pgtype.date{ epoch=1555891200 }); print(pgtype.date{ epoch=-1555891200 }); print(pgtype.date{ epoch_msec=-1555891200001 }); print(pgtype.date{ epoch_usec=-1555891200000001 }); print(pgtype.date{ epoch=1555891200, timezone="America/Los_Angeles" }); print(pgtype.date{ epoch=1555891200, timezone="Pacific/Auckland" }); print(pgtype.date{ epoch=1555891200, timezone="Asia/Kathmandu" }); $$; INFO: 1970-01-01 INFO: 2019-04-22 INFO: 2019-04-22 INFO: 2019-04-22 INFO: 2019-04-22 INFO: 2019-04-22 INFO: 1920-09-12 INFO: 1920-09-11 INFO: 1920-09-11 INFO: 2019-04-21 INFO: 2019-04-22 INFO: 2019-04-22 do language pllua $$ print(pgtype.time{ hour=12, min=23, sec=34.1 }); print(pgtype.time{ hour=12, min=120, sec=1 }); print(pgtype.time{ hour=24, min=23, sec=34.1 }); print(pgtype.time{ hour=25, min=23, sec=34.1 }); print(pgtype.time{ hour=12, min=23, sec=34, usec=123456 }); print(pgtype.time{ hour=12, min=23, sec=34.1, msec=1, usec=1 }); $$; INFO: 12:23:34.1 INFO: 14:00:01 INFO: 00:23:34.1 INFO: 01:23:34.1 INFO: 12:23:34.123456 INFO: 12:23:34.101001 do language pllua $$ print(pgtype.time{ epoch=0 }); print(pgtype.time{ epoch=3601 }); print(pgtype.time{ epoch=86400 }); print(pgtype.time{ epoch=1555891200 }); $$; INFO: 00:00:00 INFO: 01:00:01 INFO: 00:00:00 INFO: 00:00:00 do language pllua $$ print(pgtype.timetz{ hour=12, min=23, sec=34.1, timezone=7200 }); $$; INFO: 12:23:34.1+02 do language pllua $$ print(pgtype.timetz{ epoch=0, timezone=3600 }); print(pgtype.timetz{ epoch=3601, timezone=-3600 }); print(pgtype.timetz{ epoch=86400, timezone=-43200 }); print(pgtype.timetz{ epoch=1555891200, timezone=0 }); $$; INFO: 00:00:00+01 INFO: 01:00:01-01 INFO: 00:00:00-12 INFO: 00:00:00+00 do language pllua $$ print(pgtype.interval{ year=0, month=0, day=0, hour=0, min=0, sec=0, usec=0 }); print(pgtype.interval{ year=100 }); print(pgtype.interval{ month=100 }); print(pgtype.interval{ day=100 }); print(pgtype.interval{ hour=100 }); print(pgtype.interval{ min=100 }); print(pgtype.interval{ sec=100 }); print(pgtype.interval{ usec=1 }); print(pgtype.interval{ year=1, month=2, day=3, hour=4, min=5, sec=6, usec=7 }); $$; INFO: @ 0 INFO: @ 100 years INFO: @ 8 years 4 mons INFO: @ 100 days INFO: @ 100 hours INFO: @ 1 hour 40 mins INFO: @ 1 min 40 secs INFO: @ 0.000001 secs INFO: @ 1 year 2 mons 3 days 4 hours 5 mins 6.000007 secs do language pllua $$ print(pgtype.interval{ epoch=0 }); print(pgtype.interval{ epoch=120 }); print(pgtype.interval{ epoch=43200 }); print(pgtype.interval{ epoch=86400 }); $$; INFO: @ 0 INFO: @ 2 mins INFO: @ 12 hours INFO: @ 24 hours -- input error cases. do language pllua $$ print(pgtype.interval{ hour="foo" }); $$; ERROR: pllua: invalid value in field 'hour' do language pllua $$ print(pgtype.interval{ hour=1.2 }); $$; ERROR: pllua: invalid value in field 'hour' do language pllua $$ print(pgtype.interval{ hour=0/0 }); $$; ERROR: pllua: invalid value in field 'hour' do language pllua $$ print(pgtype.interval{ hour=1/0 }); $$; ERROR: pllua: infinite values not permitted for this type do language pllua $$ print(pgtype.interval{ sec=1/0 }); $$; ERROR: pllua: infinite values not permitted for this type do language pllua $$ print(pgtype.interval{ usec=1/0 }); $$; ERROR: pllua: infinite values not permitted for this type do language pllua $$ print(pgtype.timestamptz{ year=1/0, month=1, day=-1/0 }); $$; ERROR: pllua: invalid value in field 'day' do language pllua $$ print(pgtype.timestamp{ epoch=0, timezone="1234" }); $$; ERROR: invalid timezone specified do language pllua $$ print(pgtype.timestamp{ epoch=0, timezone=1234.5 }); $$; ERROR: pllua: invalid value in field 'timezone' do language pllua $$ print(pgtype.timestamp{ epoch=0, timezone=function() end }); $$; ERROR: pllua: invalid value in field 'timezone' do language pllua $$ print(pgtype.timestamptz{ epoch=0, epoch_msec=0 }); $$; ERROR: pllua: cannot specify multiple epoch fields do language pllua $$ print(pgtype.timestamptz{ epoch=0, year=2019 }); $$; ERROR: pllua: cannot specify both epoch and date fields do language pllua $$ print(pgtype.timestamptz{ epoch=0, hour=20 }); $$; ERROR: pllua: cannot specify both epoch and time fields do language pllua $$ print(pgtype.timestamptz{ year=2019 }); $$; ERROR: pllua: missing datetime field 'mon' do language pllua $$ print(pgtype.timestamptz{ year=2019, month=4 }); $$; ERROR: pllua: missing datetime field 'day' do language pllua $$ print(pgtype.time{ min=10 }); $$; ERROR: pllua: missing datetime field 'hour' do language pllua $$ print(pgtype.time{ hour=20, sec=10 }); $$; ERROR: pllua: missing datetime field 'min' do language pllua $$ print(pgtype.timetz{ hour=20, timezone="America/Los_Angeles" }); $$; ERROR: pllua: non-numeric timezones not supported for 'timetz' do language pllua $$ print(pgtype.time{ hour=20, timezone="America/Los_Angeles" }); $$; ERROR: pllua: cannot specify timezone for this type do language pllua $$ print(pgtype.date{ year=2019, month=4, day=22, timezone="America/Los_Angeles" }); $$; ERROR: pllua: cannot specify timezone for this type do language pllua $$ print(pgtype.timestamp{ year=2019, month=4, day=22, hour=20, timezone="America/Los_Angeles" }); $$; ERROR: pllua: cannot specify timezone for this type do language pllua $$ print(pgtype.timestamptz{ epoch=-300000000000 }); $$; ERROR: timestamp out of range do language pllua $$ print(pgtype.timestamptz{ year=-5000, month=1, day=1 }); $$; ERROR: could not convert to timestamp do language pllua $$ print(pgtype.date{ year=-5000, month=1, day=1 }); $$; INFO: 4760-06-20 BC -- 2. output of values in table form. do language pllua $$ local function prt(t,z) print(t) local rt = t:as_table(z) local o = {} for k,_ in pairs(rt) do o[1+#o] = k end table.sort(o) for _,k in ipairs(o) do print(k,rt[k]) end end prt(pgtype.timestamptz('2019-04-22 10:20:30+00')) prt(pgtype.timestamptz('2019-04-22 10:20:30+00'),'America/Los_Angeles') prt(pgtype.timestamptz('2019-04-22 10:20:30+00'),'Asia/Kathmandu') prt(pgtype.timestamp('2019-04-22 10:20:30+00')) prt(pgtype.date('2019-04-22')) prt(pgtype.time('10:20:30')) prt(pgtype.timetz('10:20:30+04')) prt(pgtype.interval('P1Y2M3DT4H5M6S')) $$; INFO: 2019-04-22 10:20:30+00 INFO: day 22 INFO: hour 10 INFO: isdst false INFO: min 20 INFO: month 4 INFO: sec 30 INFO: timezone 0 INFO: timezone_abbrev GMT INFO: usec 0 INFO: year 2019 INFO: 2019-04-22 10:20:30+00 INFO: day 22 INFO: hour 3 INFO: isdst true INFO: min 20 INFO: month 4 INFO: sec 30 INFO: timezone -25200 INFO: timezone_abbrev PDT INFO: usec 0 INFO: year 2019 INFO: 2019-04-22 10:20:30+00 INFO: day 22 INFO: hour 16 INFO: isdst false INFO: min 5 INFO: month 4 INFO: sec 30 INFO: timezone 20700 INFO: timezone_abbrev +0545 INFO: usec 0 INFO: year 2019 INFO: 2019-04-22 10:20:30 INFO: day 22 INFO: hour 10 INFO: min 20 INFO: month 4 INFO: sec 30 INFO: usec 0 INFO: year 2019 INFO: 2019-04-22 INFO: day 22 INFO: month 4 INFO: year 2019 INFO: 10:20:30 INFO: hour 10 INFO: min 20 INFO: sec 30 INFO: usec 0 INFO: 10:20:30+04 INFO: hour 10 INFO: min 20 INFO: sec 30 INFO: timezone 14400 INFO: usec 0 INFO: @ 1 year 2 mons 3 days 4 hours 5 mins 6 secs INFO: day 3 INFO: hour 4 INFO: min 5 INFO: month 2 INFO: sec 6 INFO: usec 0 INFO: year 1 -- error do language pllua $$ print(pgtype.timestamp('2019-04-22 10:20:30+00'):as_table('Europe/London')) $$; ERROR: pllua: [string "DO-block"]:1: cannot specify timezone parameter for this type -- 3. Field access -- note, fields come out in session timezone, so set that: set timezone = 'Europe/London'; do language pllua $$ local t = pgtype.timestamptz('1968-05-10 03:45:01.234567+01') for _,k in ipairs{ 'century', 'day', 'decade', 'dow', 'doy', 'epoch', 'epoch_msec', 'epoch_usec', 'hour', 'isodow', 'isoweek', 'isoyear', 'julian', 'microseconds', 'millennium', 'milliseconds', 'minute', 'month', 'quarter', 'second', 'timezone', 'timezone_hour', 'timezone_minute', 'week', 'year' } do print(k, t[k]) end $$; INFO: century 20 INFO: day 10 INFO: decade 196 INFO: dow 5 INFO: doy 131 INFO: epoch -51916498.765433 INFO: epoch_msec -51916498765.433 INFO: epoch_usec -51916498765433 INFO: hour 3 INFO: isodow 5 INFO: isoweek 19 INFO: isoyear 1968 INFO: julian 2439987 INFO: microseconds 1234567 INFO: millennium 2 INFO: milliseconds 1235 INFO: minute 45 INFO: month 5 INFO: quarter 2 INFO: second 1.234567 INFO: timezone 3600 INFO: timezone_hour 1 INFO: timezone_minute 0 INFO: week 19 INFO: year 1968 set timezone = 'UTC'; do language pllua $$ local t = pgtype.timestamp('1968-05-10 03:45:01.234567') for _,k in ipairs{ 'century', 'day', 'decade', 'dow', 'doy', 'epoch', 'epoch_msec', 'epoch_usec', 'hour', 'isodow', 'isoweek', 'isoyear', 'julian', 'microseconds', 'millennium', 'milliseconds', 'minute', 'month', 'quarter', 'second', 'week', 'year' } do print(k, t[k]) end $$; INFO: century 20 INFO: day 10 INFO: decade 196 INFO: dow 5 INFO: doy 131 INFO: epoch -51912898.765433 INFO: epoch_msec -51912898765.433 INFO: epoch_usec -51912898765433 INFO: hour 3 INFO: isodow 5 INFO: isoweek 19 INFO: isoyear 1968 INFO: julian 2439987 INFO: microseconds 1234567 INFO: millennium 2 INFO: milliseconds 1235 INFO: minute 45 INFO: month 5 INFO: quarter 2 INFO: second 1.234567 INFO: week 19 INFO: year 1968 do language pllua $$ local t = pgtype.date('1968-05-10') for _,k in ipairs{ 'century', 'day', 'decade', 'dow', 'doy', 'epoch', 'epoch_msec', 'epoch_usec', 'hour', 'isodow', 'isoweek', 'isoyear', 'julian', 'microseconds', 'millennium', 'milliseconds', 'minute', 'month', 'quarter', 'second', 'week', 'year' } do print(k, string.format("%.18g",t[k])) end $$; INFO: century 20 INFO: day 10 INFO: decade 196 INFO: dow 5 INFO: doy 131 INFO: epoch -51926400 INFO: epoch_msec -51926400000 INFO: epoch_usec -51926400000000 INFO: hour 0 INFO: isodow 5 INFO: isoweek 19 INFO: isoyear 1968 INFO: julian 2439987 INFO: microseconds 0 INFO: millennium 2 INFO: milliseconds 0 INFO: minute 0 INFO: month 5 INFO: quarter 2 INFO: second 0 INFO: week 19 INFO: year 1968 do language pllua $$ local t = pgtype.time('03:45:01.234567') for _,k in ipairs{ 'epoch', 'epoch_msec', 'epoch_usec', 'hour', 'microseconds', 'milliseconds', 'minute', 'second' } do print(k, t[k]) end $$; INFO: epoch 13501.234567 INFO: epoch_msec 13501234.567 INFO: epoch_usec 13501234567 INFO: hour 3 INFO: microseconds 1234567 INFO: milliseconds 1235 INFO: minute 45 INFO: second 1.234567 do language pllua $$ local t = pgtype.timetz('03:45:01.234567+01') for _,k in ipairs{ 'epoch', 'epoch_msec', 'epoch_usec', 'hour', 'microseconds', 'milliseconds', 'minute', 'second', 'timezone', 'timezone_hour', 'timezone_minute', } do print(k, t[k]) end $$; INFO: epoch 9901.234567 INFO: epoch_msec 9901234.567 INFO: epoch_usec 9901234567 INFO: hour 3 INFO: microseconds 1234567 INFO: milliseconds 1235 INFO: minute 45 INFO: second 1.234567 INFO: timezone 3600 INFO: timezone_hour 1 INFO: timezone_minute 0 do language pllua $$ local t = pgtype.interval('P1Y2M3DT4H5M6.789001S') for _,k in ipairs{ 'century', 'day', 'decade', 'epoch', 'epoch_msec', 'epoch_usec', 'hour', 'microseconds', 'millennium', 'milliseconds', 'minute', 'month', 'quarter', 'second', 'year' } do print(k, t[k]) end $$; INFO: century 0 INFO: day 3 INFO: decade 0 INFO: epoch 37015506.789001 INFO: epoch_msec 37015506789.001 INFO: epoch_usec 37015506789001 INFO: hour 4 INFO: microseconds 6789001 INFO: millennium 0 INFO: milliseconds 6789 INFO: minute 5 INFO: month 2 INFO: quarter 1 INFO: second 6.789001 INFO: year 1 -- errors (not worth testing many combinations, they all share a code path) do language pllua $$ print(pgtype.time('03:45:01.234567').dow) $$; ERROR: "time" units "dow" not recognized --end pllua-ng-REL_2_0_4/expected/jsonb.out000066400000000000000000000236461347047754200175750ustar00rootroot00000000000000-- \set VERBOSITY terse -- do language pllua $$ a = { json_test = "This is only a test.", foo = "If this were real data, it would make more sense.", piem = [[ Now I, even I, would celebrate In rhymes unapt the great Immortal Syracusan rivaled nevermore, Who in his wondrous lore, Passed on before, Left men his guidance How to circles mensurate. ]], names = { "Dougal", "Florence", "Ermintrude", "Zebedee", "Brian", "Dylan" }, mixed = { nil, nil, 123, "foo", nil, true, false, [23] = "fred" }, empty = {}, empty2 = {{},{}}, nested = { "arrayelem", { ["object key"] = "object val", subobject = { subarray = { { { 123 } } } } } } } b = pgtype.jsonb(a) print(b) c = pgtype.jsonb(a, { null = false }) print(c) d = pgtype.jsonb(a, { map = function(v) if type(v) == "boolean" then v = tostring(v) end return v end }) print(d) e = pgtype.jsonb(a, { array_thresh = 1 }) print(e) f = pgtype.jsonb(a, { empty_object = true }) print(f) spi.execute([[ create temp table jt1 as select $1 as a ]], b) $$; INFO: {"foo": "If this were real data, it would make more sense.", "piem": "Now I, even I, would celebrate\nIn rhymes unapt the great\nImmortal Syracusan rivaled nevermore,\nWho in his wondrous lore,\nPassed on before,\nLeft men his guidance\nHow to circles mensurate.\n", "empty": [], "mixed": [null, null, 123, "foo", null, true, false, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "fred"], "names": ["Dougal", "Florence", "Ermintrude", "Zebedee", "Brian", "Dylan"], "empty2": [[], []], "nested": ["arrayelem", {"subobject": {"subarray": [[[123]]]}, "object key": "object val"}], "json_test": "This is only a test."} INFO: {"foo": "If this were real data, it would make more sense.", "piem": "Now I, even I, would celebrate\nIn rhymes unapt the great\nImmortal Syracusan rivaled nevermore,\nWho in his wondrous lore,\nPassed on before,\nLeft men his guidance\nHow to circles mensurate.\n", "empty": [], "mixed": [null, null, 123, "foo", null, true, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "fred"], "names": ["Dougal", "Florence", "Ermintrude", "Zebedee", "Brian", "Dylan"], "empty2": [[], []], "nested": ["arrayelem", {"subobject": {"subarray": [[[123]]]}, "object key": "object val"}], "json_test": "This is only a test."} INFO: {"foo": "If this were real data, it would make more sense.", "piem": "Now I, even I, would celebrate\nIn rhymes unapt the great\nImmortal Syracusan rivaled nevermore,\nWho in his wondrous lore,\nPassed on before,\nLeft men his guidance\nHow to circles mensurate.\n", "empty": [], "mixed": [null, null, 123, "foo", null, "true", "false", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "fred"], "names": ["Dougal", "Florence", "Ermintrude", "Zebedee", "Brian", "Dylan"], "empty2": [[], []], "nested": ["arrayelem", {"subobject": {"subarray": [[[123]]]}, "object key": "object val"}], "json_test": "This is only a test."} INFO: {"foo": "If this were real data, it would make more sense.", "piem": "Now I, even I, would celebrate\nIn rhymes unapt the great\nImmortal Syracusan rivaled nevermore,\nWho in his wondrous lore,\nPassed on before,\nLeft men his guidance\nHow to circles mensurate.\n", "empty": [], "mixed": {"3": null, "4": null, "6": null, "7": null, "23": null}, "names": ["Dougal", "Florence", "Ermintrude", "Zebedee", "Brian", "Dylan"], "empty2": [[], []], "nested": ["arrayelem", {"subobject": {"subarray": [[[123]]]}, "object key": "object val"}], "json_test": "This is only a test."} INFO: {"foo": "If this were real data, it would make more sense.", "piem": "Now I, even I, would celebrate\nIn rhymes unapt the great\nImmortal Syracusan rivaled nevermore,\nWho in his wondrous lore,\nPassed on before,\nLeft men his guidance\nHow to circles mensurate.\n", "empty": {}, "mixed": [null, null, 123, "foo", null, true, false, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "fred"], "names": ["Dougal", "Florence", "Ermintrude", "Zebedee", "Brian", "Dylan"], "empty2": [{}, {}], "nested": ["arrayelem", {"subobject": {"subarray": [[[123]]]}, "object key": "object val"}], "json_test": "This is only a test."} select a, pg_typeof(a) from jt1; a | pg_typeof -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------- {"foo": "If this were real data, it would make more sense.", "piem": "Now I, even I, would celebrate\nIn rhymes unapt the great\nImmortal Syracusan rivaled nevermore,\nWho in his wondrous lore,\nPassed on before,\nLeft men his guidance\nHow to circles mensurate.\n", "empty": [], "mixed": [null, null, 123, "foo", null, true, false, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "fred"], "names": ["Dougal", "Florence", "Ermintrude", "Zebedee", "Brian", "Dylan"], "empty2": [[], []], "nested": ["arrayelem", {"subobject": {"subarray": [[[123]]]}, "object key": "object val"}], "json_test": "This is only a test."} | jsonb (1 row) create temp table jt2(id serial, a jsonb); insert into jt2(a) values ('1'); insert into jt2(a) values ('"foo"'); insert into jt2(a) values ('true'); insert into jt2(a) values ('null'); insert into jt2(a) values ('{"foo":123}'); insert into jt2(a) values ('{"foo":null}'); insert into jt2(a) values ('[10,20,30]'); insert into jt2(a) values ('{"foo":[2,4,6]}'); insert into jt2(a) values ('[{"foo":"bar"},{"baz":"foo"},123,null]'); -- check objects with keys that look like numbers insert into jt2(a) values ('{"1":"foo", "2":[false,true], "foo":{}}'); insert into jt2(a) values ('{"1":"foo", "2":[false,true]}'); do language pllua $$ s = spi.prepare([[ select a from jt2 order by id ]]) for r in s:rows() do print(r.a) b = r.a(function(k,v,...) if type(v)~="table" then print("mapfunc",type(k),k,v,...) else print("mapfunc",type(k),k,type(v),...) end return k,v end) print(type(b)) end $$; INFO: 1 INFO: mapfunc nil nil 1 INFO: number INFO: "foo" INFO: mapfunc nil nil foo INFO: string INFO: true INFO: mapfunc nil nil true INFO: boolean INFO: null INFO: mapfunc nil nil nil INFO: nil INFO: {"foo": 123} INFO: mapfunc string foo 123 INFO: mapfunc nil nil table INFO: table INFO: {"foo": null} INFO: mapfunc string foo nil INFO: mapfunc nil nil table INFO: table INFO: [10, 20, 30] INFO: mapfunc number 0 10 INFO: mapfunc number 1 20 INFO: mapfunc number 2 30 INFO: mapfunc nil nil table INFO: table INFO: {"foo": [2, 4, 6]} INFO: mapfunc number 0 2 foo INFO: mapfunc number 1 4 foo INFO: mapfunc number 2 6 foo INFO: mapfunc string foo table INFO: mapfunc nil nil table INFO: table INFO: [{"foo": "bar"}, {"baz": "foo"}, 123, null] INFO: mapfunc string foo bar 0 INFO: mapfunc number 0 table INFO: mapfunc string baz foo 1 INFO: mapfunc number 1 table INFO: mapfunc number 2 123 INFO: mapfunc number 3 nil INFO: mapfunc nil nil table INFO: table INFO: {"1": "foo", "2": [false, true], "foo": {}} INFO: mapfunc string 1 foo INFO: mapfunc number 0 false 2 INFO: mapfunc number 1 true 2 INFO: mapfunc string 2 table INFO: mapfunc string foo table INFO: mapfunc nil nil table INFO: table INFO: {"1": "foo", "2": [false, true]} INFO: mapfunc string 1 foo INFO: mapfunc number 0 false 2 INFO: mapfunc number 1 true 2 INFO: mapfunc string 2 table INFO: mapfunc nil nil table INFO: table create temp table jt3(id integer, a jsonb); -- first row should be plain, then a couple with compressed values, -- then a couple with external toast insert into jt3 select i, ('[' || repeat('"foo",',10*(10^i)::integer) || i || ']')::jsonb from generate_series(1,5) i; do language pllua $$ s = spi.prepare([[ select a from jt3 where id = $1 ]]) for i = 1,5 do local r = (s:execute(i))[1] local a = r.a() print(#a,a[#a]) end $$; INFO: 101 1 INFO: 1001 2 INFO: 10001 3 INFO: 100001 4 INFO: 1000001 5 -- test jsonb in jsonb and similar paths do language pllua $$ local jtst1 = pgtype.jsonb('"foo"') -- json scalar local jtst2 = pgtype.jsonb('{"foo":true,"bar":[1,2,false]}') -- json container local ts1 = pgtype.timestamp('2017-12-19 12:00:00') print(pgtype.jsonb({ v1 = jtst1, v2 = jtst2, v3 = ts1 })) $$; INFO: {"v1": "foo", "v2": {"bar": [1, 2, false], "foo": true}, "v3": "2017-12-19T12:00:00"} -- test round-trip conversions do language pllua $$ local j_in = pgtype.jsonb('{"foo":[1,null,false,{"a":null,"b":[]},{},[]]}') local nvl = {} local val = j_in{ null = nvl } local j_out = pgtype.jsonb(val, { null = nvl }) print(j_in) print(j_out) $$; INFO: {"foo": [1, null, false, {"a": null, "b": []}, {}, []]} INFO: {"foo": [1, null, false, {"a": null, "b": []}, {}, []]} --end pllua-ng-REL_2_0_4/expected/numerics.out000066400000000000000000000150251347047754200202770ustar00rootroot00000000000000-- \set VERBOSITY terse -- test numerics create function lua_numexec(code text, n1 numeric, n2 numeric) returns text language pllua as $$ local f,e = load("return function(n1,n2) return "..code.." end", code, "t", self) assert(f,e) f = f() assert(f) return tostring(f(n1,n2)) end do num = require "pllua.numeric" $$; create function pg_numexec(code text, n1 numeric, n2 numeric) returns text language plpgsql as $$ declare r text; begin execute format('select (%s)::text', regexp_replace(regexp_replace(code, '\mnum\.', '', 'g'), '\mn([0-9])', '$\1', 'g')) into r using n1,n2; return r; end; $$; with t as (select code, lua_numexec(code, 5439.123456, -1.9) as lua, pg_numexec(code, 5439.123456, -1.9) as pg from unnest(array[ $$ n1 + n2 $$, $$ n1 - n2 $$, $$ n1 * n2 $$, $$ n1 / n2 $$, $$ n1 % n2 $$, $$ n1 ^ n2 $$, $$ (-n1) + n2 $$, $$ (-n1) - n2 $$, $$ (-n1) * n2 $$, $$ (-n1) / n2 $$, $$ (-n1) % n2 $$, $$ (-n1) ^ 3 $$, $$ (-n1) + (-n2) $$, $$ (-n1) - (-n2) $$, $$ (-n1) * (-n2) $$, $$ (-n1) / (-n2) $$, $$ (-n1) % (-n2) $$, $$ (-n1) ^ (-3) $$, $$ (n1) > (n2) $$, $$ (n1) < (n2) $$, $$ (n1) >= (n2) $$, $$ (n1) <= (n2) $$, $$ (n1) > (n2*10000) $$, $$ (n1) < (n2*10000) $$, $$ (n1) >= (n2 * -10000) $$, $$ (n1) <= (n2 * -10000) $$, $$ num.round(n1) $$, $$ num.round(n2) $$, $$ num.round(n1,4) $$, $$ num.round(n1,-1) $$, $$ num.trunc(n1) $$, $$ num.trunc(n2) $$, $$ num.trunc(n1,4) $$, $$ num.trunc(n1,-1) $$, $$ num.floor(n1) $$, $$ num.floor(n2) $$, $$ num.ceil(n1) $$, $$ num.ceil(n2) $$, $$ num.abs(n1) $$, $$ num.abs(n2) $$, $$ num.sign(n1) $$, $$ num.sign(n2) $$, $$ num.sqrt(n1) $$, $$ num.exp(12.3) $$, $$ num.exp(n2) $$ ]) as u(code)) select (lua = pg) as ok, * from t; ok | code | lua | pg ----+-------------------------+--------------------------------+-------------------------------- t | n1 + n2 | 5437.223456 | 5437.223456 t | n1 - n2 | 5441.023456 | 5441.023456 t | n1 * n2 | -10334.3345664 | -10334.3345664 t | n1 / n2 | -2862.6965557894736842 | -2862.6965557894736842 t | n1 % n2 | 1.323456 | 1.323456 t | n1 ^ n2 | 0.00000007989048519637487 | 0.00000007989048519637487 t | (-n1) + n2 | -5441.023456 | -5441.023456 t | (-n1) - n2 | -5437.223456 | -5437.223456 t | (-n1) * n2 | 10334.3345664 | 10334.3345664 t | (-n1) / n2 | 2862.6965557894736842 | 2862.6965557894736842 t | (-n1) % n2 | -1.323456 | -1.323456 t | (-n1) ^ 3 | -160911376260.9068713240072028 | -160911376260.9068713240072028 t | (-n1) + (-n2) | -5437.223456 | -5437.223456 t | (-n1) - (-n2) | -5441.023456 | -5441.023456 t | (-n1) * (-n2) | -10334.3345664 | -10334.3345664 t | (-n1) / (-n2) | -2862.6965557894736842 | -2862.6965557894736842 t | (-n1) % (-n2) | -1.323456 | -1.323456 t | (-n1) ^ (-3) | -0.0000000000062146 | -0.0000000000062146 t | (n1) > (n2) | true | true t | (n1) < (n2) | false | false t | (n1) >= (n2) | true | true t | (n1) <= (n2) | false | false t | (n1) > (n2*10000) | true | true t | (n1) < (n2*10000) | false | false t | (n1) >= (n2 * -10000) | false | false t | (n1) <= (n2 * -10000) | true | true t | num.round(n1) | 5439 | 5439 t | num.round(n2) | -2 | -2 t | num.round(n1,4) | 5439.1235 | 5439.1235 t | num.round(n1,-1) | 5440 | 5440 t | num.trunc(n1) | 5439 | 5439 t | num.trunc(n2) | -1 | -1 t | num.trunc(n1,4) | 5439.1234 | 5439.1234 t | num.trunc(n1,-1) | 5430 | 5430 t | num.floor(n1) | 5439 | 5439 t | num.floor(n2) | -2 | -2 t | num.ceil(n1) | 5440 | 5440 t | num.ceil(n2) | -1 | -1 t | num.abs(n1) | 5439.123456 | 5439.123456 t | num.abs(n2) | 1.9 | 1.9 t | num.sign(n1) | 1 | 1 t | num.sign(n2) | -1 | -1 t | num.sqrt(n1) | 73.750413259859093 | 73.750413259859093 t | num.exp(12.3) | 219695.98867213773 | 219695.98867213773 t | num.exp(n2) | 0.1495686192226351 | 0.1495686192226351 (45 rows) -- calculate pi to 40 places do language pllua $$ -- Chudnovsky formula; ~14 digits per round, we use 4 rounds local num = require 'pllua.numeric' local prec = 100 -- precision of intermediate values local function fact(n) local r = pgtype.numeric(1):round(prec) for i = 2,n do r = r * i end return r:round(prec) end local c640320 = pgtype.numeric(640320):round(prec) local c13591409 = pgtype.numeric(13591409):round(prec) local c545140134 = pgtype.numeric(545140134):round(prec) local function chn(k) return (fact(6*k) * (c13591409 + (c545140134 * k))) / (fact(3*k) * fact(k)^3 * (-c640320)^(3*k)) end local function pi() return (1 / ((chn(0) + chn(1) + chn(2) + chn(3))*12 / num.sqrt(c640320^3))):round(40) end print(pi()) $$; INFO: 3.1415926535897932384626433832795028841972 -- check sanity of maxinteger/mininteger do language pllua $$ local num = require 'pllua.numeric' local maxi = num.maxinteger local mini = num.mininteger print(type(num.tointeger(maxi)), type(num.tointeger(maxi+1))) print(type(num.tointeger(mini)), type(num.tointeger(mini-1))) $$ --end INFO: number nil INFO: number nil pllua-ng-REL_2_0_4/expected/paths.out000066400000000000000000000013621347047754200175700ustar00rootroot00000000000000-- \set VERBOSITY terse -- create function pg_temp.tmp1(n text) returns text language plluau immutable strict as $$ return (require "pllua.paths")[n]() $$; -- some of the dirs might not actually exist, so we test only the -- important ones. We can't actually test that the dir exists or what -- the contents are, since many pg versions reject pg_stat_file on -- absolute paths; so just check that we got some string that looks -- like a path. select u.n, f.path ~ '^(?:[[:alpha:]]:)?/' from unnest(array['bin','lib','libdir','pkglib','share']) with ordinality as u(n,ord), pg_temp.tmp1(u.n) f(path) order by u.ord; n | ?column? --------+---------- bin | t lib | t libdir | t pkglib | t share | t (5 rows) --end pllua-ng-REL_2_0_4/expected/pllua.out000066400000000000000000000210161347047754200175640ustar00rootroot00000000000000-- CREATE EXTENSION pllua; CREATE EXTENSION plluau; \set VERBOSITY terse -- smoke test do language pllua $$ print "hello world!" $$; INFO: hello world! do language plluau $$ print "hello world!" $$; INFO: hello world! create function pg_temp.f1() returns text language pllua as $$ return "hello world" $$; select pg_temp.f1(); f1 ------------- hello world (1 row) create function pg_temp.f2() returns text language plluau as $$ return "hello world" $$; select pg_temp.f2(); f2 ------------- hello world (1 row) -- Rest of this file concentrates on simple tests of code paths in -- compile, exec, and interpreter setup. Tests of other parts of the -- module are separate. -- validator create function pg_temp."bad name"() returns text language pllua as $$ $$; ERROR: PL/Lua function name "bad name" is not a valid Lua identifier create function pg_temp.f3("bad arg" integer) returns text language pllua as $$ $$; ERROR: PL/Lua argument name "bad arg" is not a valid Lua identifier -- simple params and results (see types.sql for detailed checks) create function pg_temp.f4(a integer) returns integer language pllua as $$ return a + 1 $$; select pg_temp.f4(1); f4 ---- 2 (1 row) create function pg_temp.f5(a text) returns text language pllua as $$ return a.."bar" $$; select pg_temp.f5('foo'); f5 -------- foobar (1 row) create function pg_temp.f6(a text, b integer) returns text language pllua as $$ return a..b $$; select pg_temp.f6('foo',1); f6 ------ foo1 (1 row) -- try some polymorphism too create function pg_temp.f7(a anyelement) returns anyelement language pllua as $$ return a $$; select pg_temp.f7(text 'foo'); f7 ----- foo (1 row) select pg_temp.f7(json '{"foo":1}'); f7 ----------- {"foo":1} (1 row) --select pg_temp.f7(xml 'bar'); -- don't bother with this, might be compiled out select pg_temp.f7(varchar 'foo'); f7 ----- foo (1 row) select 'x',pg_temp.f7('foo'::char(20)),'x'; ?column? | f7 | ?column? ----------+----------------------+---------- x | foo | x (1 row) select pg_temp.f7(cstring 'foo'); f7 ----- foo (1 row) select pg_temp.f7(name 'foo'); f7 ----- foo (1 row) select pg_temp.f7(bytea 'foo\000bar'); f7 ------------------ \x666f6f00626172 (1 row) select pg_temp.f7(smallint '2'); f7 ---- 2 (1 row) select pg_temp.f7(integer '2'); f7 ---- 2 (1 row) select pg_temp.f7(bigint '123456789012345'); f7 ----------------- 123456789012345 (1 row) select pg_temp.f7(oid '10'); f7 ---- 10 (1 row) select pg_temp.f7(oid '4294967295'); f7 ------------ 4294967295 (1 row) select pg_temp.f7(true); f7 ---- t (1 row) select pg_temp.f7(false); f7 ---- f (1 row) select pg_temp.f7(1.5::float8); f7 ----- 1.5 (1 row) select pg_temp.f7(1.5::float4); f7 ----- 1.5 (1 row) -- variadics create function pg_temp.f8(a text, variadic b integer[]) returns void language pllua as $$ print(a,type(b),b) $$; select pg_temp.f8('foo', 1, 2, 3); INFO: foo userdata {1,2,3} f8 ---- (1 row) create function pg_temp.f9(a integer, variadic b text[]) returns void language pllua as $$ print(a,type(b),b) $$; select pg_temp.f9(1, 'foo', 'bar', 'baz'); INFO: 1 userdata {foo,bar,baz} f9 ---- (1 row) create function pg_temp.f10(a integer, variadic "any") returns void language pllua as $$ print(a,...) $$; select pg_temp.f10(1, 'foo', 2, 'baz'); INFO: 1 foo 2 baz f10 ----- (1 row) -- SRF code paths create function pg_temp.f11(a integer) returns setof text language pllua as $$ return $$; -- 0 rows select * from pg_temp.f11(1); f11 ----- (0 rows) create function pg_temp.f11b(a integer) returns setof text language pllua as $$ return 'foo' $$; -- 1 row select * from pg_temp.f11b(1); f11b ------ foo (1 row) create function pg_temp.f12(a integer) returns setof text language pllua as $$ coroutine.yield() $$; -- 1 row, null select * from pg_temp.f12(1); f12 ----- (1 row) create function pg_temp.f13(a integer) returns setof text language pllua as $$ for i = 1,a do coroutine.yield("row "..i) end $$; select * from pg_temp.f13(4); f13 ------- row 1 row 2 row 3 row 4 (4 rows) create function pg_temp.f14(a integer, out x text, out y integer) returns setof record language pllua as $$ for i = 1,a do coroutine.yield("row "..i, i) end $$; select * from pg_temp.f14(4); x | y -------+--- row 1 | 1 row 2 | 2 row 3 | 3 row 4 | 4 (4 rows) create function pg_temp.f15(a integer) returns table(x text, y integer) language pllua as $$ for i = 1,a do coroutine.yield("row "..i, i) end $$; select * from pg_temp.f15(4); x | y -------+--- row 1 | 1 row 2 | 2 row 3 | 3 row 4 | 4 (4 rows) create function pg_temp.f16(a inout integer, x out text) returns setof record language pllua as $$ for i = 1,a do coroutine.yield(i, "row "..i) end $$; select * from pg_temp.f16(4); a | x ---+------- 1 | row 1 2 | row 2 3 | row 3 4 | row 4 (4 rows) -- SRF vs null returns create function pg_temp.f16b(a integer) returns table(x text, y integer) language pllua as $$ coroutine.yield() $$; -- 1 row, null select * from pg_temp.f16b(1); x | y ---+--- | (1 row) create function pg_temp.f16c(a integer) returns table(x text, y integer) language pllua as $$ coroutine.yield() for i = 1,a do coroutine.yield('foo',i) end $$; select * from pg_temp.f16c(3); x | y -----+--- | foo | 1 foo | 2 foo | 3 (4 rows) -- compiler and validator code paths do language pllua $$ _G.rdepth = 40 $$; -- global var hack -- This function will try and call itself at a point where it is visible -- but has no definition interned yet; the recursive call will likewise -- not see an interned definition and recurses again. without any limits -- this would hit a stack depth check somewhere; we eat about 3 levels of -- C function recursion inside lua each time, and that gets capped at 200. -- We don't expect this to be actually useful, the test is just that we -- don't crash. create function pg_temp.f17(a integer) returns integer language pllua as $$ return a end do if _G.rdepth > 0 then _G.rdepth = _G.rdepth - 1 u = spi.execute("select pg_temp.f17(1)") end $$; select pg_temp.f17(1); f17 ----- 1 (1 row) create type pg_temp.t1 as (a integer, b text); create function pg_temp.f18(a integer, b text) returns pg_temp.t1 language pllua as $$ return a,b $$; select * from pg_temp.f18(123,'foo'); a | b -----+----- 123 | foo (1 row) create function pg_temp.f19(a integer) returns text language pllua as $$ return 'foo '..a $$; select pg_temp.f19(2); f19 ------- foo 2 (1 row) create or replace function pg_temp.f19(a integer) returns text language pllua as $$ return 'bar '..a $$; select pg_temp.f19(3); f19 ------- bar 3 (1 row) -- trusted interpreter setup -- check we really do have different interpreters -- this is hard because we intentionally isolate trusted-language code -- from the normal global env of its interpreter, so we would only be -- able to verify isolation if we were able to break out of the -- sandbox, which would rather defeat the point. We have to take the -- outside view, by generating an interpreter-dependent value and -- checking that it differs. The stringification of a closure, such as -- server.error, suffices since this contains an interpreter-dependent -- address (whereas base C functions do not differ between -- interpreters in recent lua versions). create function pg_temp.f20() returns text language pllua as $$ return tostring(spi.error) $$; create function pg_temp.f21() returns text language plluau as $$ return tostring(spi.error) $$; select pg_temp.f20() as a intersect select pg_temp.f21(); -- should be empty a --- (0 rows) -- check the global table do language pllua $$ local gk = { "io", "dofile", "debug" } -- must not exist for i = 1,#gk do print(gk[i],type(_G[gk[i]])) end $$; INFO: io nil INFO: dofile nil INFO: debug nil do language plluau $$ local gk = { "io", "dofile" } -- probably exist for i = 1,#gk do print(gk[i],type(_G[gk[i]])) end $$; INFO: io table INFO: dofile function -- check that trusted gets only the restricted os module, even from -- require do language pllua $$ local os = require 'os' local gk = { "time", "difftime", "execute", "getenv", "exit" } for i = 1,#gk do print(gk[i],type(os[gk[i]])) end $$; INFO: time function INFO: difftime function INFO: execute nil INFO: getenv nil INFO: exit nil -- check that trusted can't require dangerous core modules do language pllua $$ print((lpcall(require,"debug"))) print((lpcall(require,"io"))) $$; INFO: false INFO: false --end pllua-ng-REL_2_0_4/expected/pllua_old.out000066400000000000000000000321301347047754200204210ustar00rootroot00000000000000-- \set VERBOSITY terse set pllua.on_common_init = 'require "pllua.compat"'; -- tests taken from old pllua -- 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) SET client_min_messages = warning; CREATE TABLE tree (id INT PRIMARY KEY, lchild INT, rchild INT); RESET client_min_messages; CREATE FUNCTION filltree (t text, n int) RETURNS void AS $$ local p = server.prepare("insert into " .. t .. " values($1, $2, $3)", {"int4", "int4", "int4"}) for i = 1, n do local lchild, rchild = 2 * i, 2 * i + 1 -- siblings p:execute{i, lchild, rchild} -- insert values end $$ LANGUAGE pllua; SELECT filltree('tree', 10); filltree ---------- (1 row) CREATE FUNCTION preorder (t text, s int) RETURNS SETOF int AS $$ coroutine.yield(s) local q = server.execute("select * from " .. t .. " where id=" .. s, true, 1) -- read-only, only 1 result if q ~= nil then local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query if lchild ~= nil then preorder(t, lchild) end if rchild ~= nil then preorder(t, rchild) end end $$ LANGUAGE pllua; SELECT * from preorder('tree', 1); preorder ---------- 1 2 4 8 16 17 9 18 19 5 10 20 21 11 3 6 12 13 7 14 15 (21 rows) CREATE FUNCTION postorder (t text, s int) RETURNS SETOF int AS $$ local p = _U[t] if p == nil then -- plan not cached? p = server.prepare("select * from " .. t .. " where id=$1", {"int4"}) _U[t] = p:save() end local c = p:getcursor({s}, true) -- read-only local q = c:fetch(1) -- one row if q ~= nil then local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query c:close() if lchild ~= nil then postorder(t, lchild) end if rchild ~= nil then postorder(t, rchild) end end coroutine.yield(s) end do _U = {} -- plan cache $$ LANGUAGE pllua; SELECT * FROM postorder('tree', 1); postorder ----------- 16 17 8 18 19 9 4 20 21 10 11 5 2 12 13 6 14 15 7 3 1 (21 rows) -- trigger CREATE FUNCTION treetrigger() RETURNS trigger AS $$ local row, operation = trigger.row, trigger.operation if operation == "update" then trigger.row = nil -- updates not allowed elseif operation == "insert" then local id, lchild, rchild = row.id, row.lchild, row.rchild if lchild == rchild or id == lchild or id == rchild -- avoid loops or (lchild ~= nil and _U.intree(lchild)) -- avoid cycles or (rchild ~= nil and _U.intree(rchild)) or (_U.nonemptytree() and not _U.isleaf(id)) -- not leaf? then trigger.row = nil -- skip operation end else -- operation == "delete" if not _U.isleafparent(row.id) then -- not both leaf parent? trigger.row = nil end end end do local getter = function(cmd, ...) local plan = server.prepare(cmd, {...}):save() return function(...) return plan:execute({...}, true) ~= nil end end _U = { -- plan closures nonemptytree = getter("select * from tree"), intree = getter("select node from (select id as node from tree " .. "union select lchild from tree union select rchild from tree) as q " .. "where node=$1", "int4"), isleaf = getter("select leaf from (select lchild as leaf from tree " .. "union select rchild from tree except select id from tree) as q " .. "where leaf=$1", "int4"), isleafparent = getter("select lp from (select id as lp from tree " .. "except select ti.id from tree ti join tree tl on ti.lchild=tl.id " .. "join tree tr on ti.rchild=tr.id) as q where lp=$1", "int4") } $$ LANGUAGE pllua; CREATE TRIGGER tree_trigger BEFORE INSERT OR UPDATE OR DELETE ON tree FOR EACH ROW EXECUTE PROCEDURE treetrigger(); SELECT * FROM tree WHERE id = 1; id | lchild | rchild ----+--------+-------- 1 | 2 | 3 (1 row) UPDATE tree SET rchild = 1 WHERE id = 1; SELECT * FROM tree WHERE id = 10; id | lchild | rchild ----+--------+-------- 10 | 20 | 21 (1 row) DELETE FROM tree where id = 10; DELETE FROM tree where id = 1; -- passthru types CREATE FUNCTION echo_int2(arg int2) RETURNS int2 AS $$ return arg $$ LANGUAGE pllua; SELECT echo_int2('12345'); echo_int2 ----------- 12345 (1 row) CREATE FUNCTION echo_int4(arg int4) RETURNS int4 AS $$ return arg $$ LANGUAGE pllua; SELECT echo_int4('1234567890'); echo_int4 ------------ 1234567890 (1 row) CREATE FUNCTION echo_int8(arg int8) RETURNS int8 AS $$ return arg $$ LANGUAGE pllua; SELECT echo_int8('1234567890'); echo_int8 ------------ 1234567890 (1 row) SELECT echo_int8('12345678901236789'); echo_int8 ------------------- 12345678901236789 (1 row) SELECT echo_int8('1234567890123456789'); echo_int8 --------------------- 1234567890123456789 (1 row) CREATE FUNCTION echo_text(arg text) RETURNS text AS $$ return arg $$ LANGUAGE pllua; SELECT echo_text('qwe''qwe'); echo_text ----------- qwe'qwe (1 row) CREATE FUNCTION echo_bytea(arg bytea) RETURNS bytea AS $$ return arg $$ LANGUAGE pllua; SELECT echo_bytea('qwe''qwe'); echo_bytea ------------------ \x71776527717765 (1 row) SELECT echo_bytea(E'q\\000w\\001e''q\\\\we'); echo_bytea ------------------------ \x710077016527715c7765 (1 row) CREATE FUNCTION echo_timestamptz(arg timestamptz) RETURNS timestamptz AS $$ return arg $$ LANGUAGE pllua; SELECT echo_timestamptz('2007-01-06 11:11 UTC') AT TIME ZONE 'UTC'; timezone -------------------------- Sat Jan 06 11:11:00 2007 (1 row) CREATE FUNCTION echo_timestamp(arg timestamp) RETURNS timestamp AS $$ return arg $$ LANGUAGE pllua; SELECT echo_timestamp('2007-01-06 11:11'); echo_timestamp -------------------------- Sat Jan 06 11:11:00 2007 (1 row) CREATE FUNCTION echo_date(arg date) RETURNS date AS $$ return arg $$ LANGUAGE pllua; SELECT echo_date('2007-01-06'); echo_date ------------ 01-06-2007 (1 row) CREATE FUNCTION echo_time(arg time) RETURNS time AS $$ return arg $$ LANGUAGE pllua; SELECT echo_time('11:11'); echo_time ----------- 11:11:00 (1 row) CREATE FUNCTION echo_arr(arg text[]) RETURNS text[] AS $$ return arg $$ LANGUAGE pllua; SELECT echo_arr(array['a', 'b', 'c']); echo_arr ---------- {a,b,c} (1 row) CREATE DOMAIN mynum AS numeric(6,3); CREATE FUNCTION echo_mynum(arg mynum) RETURNS mynum AS $$ return arg $$ LANGUAGE pllua; SELECT echo_mynum(666.777); echo_mynum ------------ 666.777 (1 row) CREATE TYPE mytype AS (id int2, val mynum, val_list numeric[]); CREATE FUNCTION echo_mytype(arg mytype) RETURNS mytype AS $$ return arg $$ LANGUAGE pllua; SELECT echo_mytype((1::int2, 666.777, array[1.0, 2.0]) ); echo_mytype ------------------------- (1,666.777,"{1.0,2.0}") (1 row) CREATE FUNCTION nested_server_rows () RETURNS SETOF text as $$ for left in server.rows('select generate_series as left from generate_series(3,4) ') do for right in server.rows('select generate_series as right from generate_series(5,6) ') do local s = left.left.." "..right.right coroutine.yield(s) end end $$ language pllua; select nested_server_rows(); nested_server_rows -------------------- 3 5 3 6 4 5 4 6 (4 rows) CREATE OR REPLACE FUNCTION pg_temp.srf() RETURNS SETOF integer AS $$ coroutine.yield(1) coroutine.yield(nil) coroutine.yield(2) $$ LANGUAGE pllua; select quote_nullable(pg_temp.srf()); quote_nullable ---------------- '1' NULL '2' (3 rows) CREATE OR REPLACE FUNCTION pg_temp.srf() RETURNS SETOF integer AS $$ coroutine.yield(1) coroutine.yield() coroutine.yield(2) $$ LANGUAGE pllua; select quote_nullable(pg_temp.srf()); quote_nullable ---------------- '1' NULL '2' (3 rows) CREATE or replace FUNCTION pg_temp.inoutf(a integer, INOUT b text, INOUT c text) AS $$ begin c = a||'c:'||c; b = 'b:'||b; end $$ LANGUAGE plpgsql; do $$ local a = server.execute("SELECT pg_temp.inoutf(5, 'ABC', 'd') as val "); local r = a[1].val print(r.b) print(r.c) $$ language pllua; INFO: b:ABC INFO: 5c:d -- body reload SELECT hello('PostgreSQL'); hello -------------------- Hello, PostgreSQL! (1 row) CREATE OR REPLACE FUNCTION hello(name text) RETURNS text AS $$ return string.format("Bye, %s!", name) $$ LANGUAGE pllua; SELECT hello('PostgreSQL'); hello ------------------ Bye, PostgreSQL! (1 row) pllua-ng-REL_2_0_4/expected/procedures.out000066400000000000000000000047031347047754200206260ustar00rootroot00000000000000-- \set VERBOSITY terse -- -- test procedures, non-atomic DO-blocks, and spi.commit/rollback -- (all pg11 new features) create table xatst2 (a integer); create procedure pg_temp.tp1(a text) language pllua as $$ print("hello world", a) print(spi.is_atomic() and "atomic context" or "non-atomic context") $$; call pg_temp.tp1('foo'); INFO: hello world foo INFO: non-atomic context begin; call pg_temp.tp1('foo'); commit; INFO: hello world foo INFO: atomic context do language pllua $$ print(spi.is_atomic() and "atomic context" or "non-atomic context") $$; INFO: non-atomic context begin; do language pllua $$ print(spi.is_atomic() and "atomic context" or "non-atomic context") $$; INFO: atomic context commit; create procedure pg_temp.tp2() language pllua as $$ local stmt = spi.prepare([[ insert into xatst2 values ($1) ]]); stmt:execute(1); spi.commit(); stmt:execute(2); spi.rollback(); stmt:execute(3); spi.commit(); stmt:execute(4); $$; call pg_temp.tp2(); -- should now be three different xids in xatst2, and 3 rows select count(*), count(distinct age(xmin)) from xatst2; count | count -------+------- 3 | 3 (1 row) -- proper handling of open cursors create procedure pg_temp.tp3() language pllua as $$ local stmt = spi.prepare([[ select i from generate_series(1,10) i ]]); for r in stmt:rows() do print(r.i) spi.commit(); end $$; call pg_temp.tp3(); INFO: 1 ERROR: pllua: [string "tp3"]:3: cannot iterate a closed cursor create procedure pg_temp.tp4() language pllua as $$ local stmt = spi.prepare([[ select i from generate_series(1,10) i ]], {}, { hold = true }); for r in stmt:rows() do print(r.i) spi.commit(); end $$; call pg_temp.tp4(); INFO: 1 INFO: 2 INFO: 3 INFO: 4 INFO: 5 INFO: 6 INFO: 7 INFO: 8 INFO: 9 INFO: 10 -- no commit inside subxact truncate table xatst2; do language pllua $$ local stmt = spi.prepare([[ insert into xatst2 values ($1) ]]); stmt:execute(1); spi.commit(); stmt:execute(2); print(pcall(function() stmt:execute(3) spi.commit() end)) -- the commit threw a lua error and the subxact was rolled back, -- so we should be in the same xact as row 2 stmt:execute(4); spi.commit(); $$; INFO: false [string "DO-block"]:6: cannot commit or rollback from inside a subtransaction -- should now be two different xids in xatst2, and 3 rows select count(*), count(distinct age(xmin)) from xatst2; count | count -------+------- 3 | 2 (1 row) --end pllua-ng-REL_2_0_4/expected/rowdatum.out000066400000000000000000000103231347047754200203100ustar00rootroot00000000000000-- \set VERBOSITY terse -- -- tests of operations on nested row values create type ntype1 as (fred integer, jim numeric); create type ntype2 as (thingy text[], wotsit ntype1); create type ntype3 as (foo text, bar ntype1, baz ntype2); create type ntype4 as (col0 text, col1 ntype1, col2 ntype2, col3 ntype3); -- do language pllua $$ local r = pgtype.ntype1(1,2) print(r.fred, r.jim) -- deform r.fred = 3 -- explode after deform print(r.fred, r.jim) r.jim = 4 -- modify pre-exploded value print(pgtype.ntype1(r), r.fred, r.jim) r = pgtype.ntype1(1,2) r.fred = 3 -- explode before deform print(r.fred, r.jim) r.jim = 4 -- modify pre-exploded value print(pgtype.ntype1(r), r.fred, r.jim) $$; INFO: 1 2 INFO: 3 2 INFO: (3,4) 3 4 INFO: 3 2 INFO: (3,4) 3 4 do language pllua $$ local r0 = pgtype.ntype4("zzz", { fred = 1, jim = 2 }, { thingy = {"a","b","c"}, wotsit = { fred = 3, jim = 4} }, { foo = "abcde", bar = { fred = 5, jim = 6 }, baz = { thingy = {"x","y","z"}, wotsit = { fred = 7, jim = 8 } } }) local r = pgtype.ntype4(r0) -- deform from inner to outer print(r.col3.baz.wotsit.fred) print(r.col3.foo) print(r.col1.fred) -- and from outer to inner print(r.col2.wotsit.jim) print(r.col3.bar.jim) -- start over with un-deformed datum r = pgtype.ntype4(r0) --deform from outer to inner print(r.col1, r.col2, r.col3) print(r.col1.jim, r.col2.thingy[3], r.col3.foo) print(r.col2.wotsit.jim, r.col3.bar.jim, r.col3.baz.thingy[3]) print(r.col3.baz.wotsit.jim) print(r) print(pgtype.ntype4(r)) -- start over with un-deformed datum r = pgtype.ntype4(r0) -- explode from inner r.col3.baz.wotsit.jim = 10 r.col2.thingy[2] = "k" print(r) print(pgtype.ntype4(r)) -- start over with un-deformed datum r = pgtype.ntype4(r0) -- explode from middle r.col3.foo = "edcba" r.col1.fred = -1 r.col3.baz.wotsit.jim = 80 r.col3.baz.wotsit.fred = 0 print(r) print(pgtype.ntype4(r)) -- start over with un-deformed datum r = pgtype.ntype4(r0) -- explode from top r.col0 = "yyy" r.col3.baz.thingy[1] = "@" r.col1.jim = 20 print(r) print(pgtype.ntype4(r)) -- start over with un-deformed datum r = pgtype.ntype4(r0) -- partially deform then explode from a deformed element print(r.col0, r.col2.wotsit.fred) r.col2.wotsit.jim = 40 r.col1.jim = 0 r.col3.bar.jim = 0 print(r) print(pgtype.ntype4(r)) -- start over with un-deformed datum r = pgtype.ntype4(r0) -- partially deform then explode from an undeformed element print(r.col0, r.col2.wotsit.fred) r.col3.bar.jim = 0 r.col3.bar.fred = -1 r.col2.wotsit.fred = 0 r.col0 = "yyy" r.col1 = { fred = 100, jim = 200 } print(r) print(pgtype.ntype4(r)) $$; INFO: 7 INFO: abcde INFO: 1 INFO: 4 INFO: 6 INFO: (1,2) ("{a,b,c}","(3,4)") (abcde,"(5,6)","(""{x,y,z}"",""(7,8)"")") INFO: 2 c abcde INFO: 4 6 z INFO: 8 INFO: (zzz,"(1,2)","(""{a,b,c}"",""(3,4)"")","(abcde,""(5,6)"",""(""""{x,y,z}"""",""""(7,8)"""")"")") INFO: (zzz,"(1,2)","(""{a,b,c}"",""(3,4)"")","(abcde,""(5,6)"",""(""""{x,y,z}"""",""""(7,8)"""")"")") INFO: (zzz,"(1,2)","(""{a,k,c}"",""(3,4)"")","(abcde,""(5,6)"",""(""""{x,y,z}"""",""""(7,10)"""")"")") INFO: (zzz,"(1,2)","(""{a,k,c}"",""(3,4)"")","(abcde,""(5,6)"",""(""""{x,y,z}"""",""""(7,10)"""")"")") INFO: (zzz,"(-1,2)","(""{a,b,c}"",""(3,4)"")","(edcba,""(5,6)"",""(""""{x,y,z}"""",""""(0,80)"""")"")") INFO: (zzz,"(-1,2)","(""{a,b,c}"",""(3,4)"")","(edcba,""(5,6)"",""(""""{x,y,z}"""",""""(0,80)"""")"")") INFO: (yyy,"(1,20)","(""{a,b,c}"",""(3,4)"")","(abcde,""(5,6)"",""(""""{@,y,z}"""",""""(7,8)"""")"")") INFO: (yyy,"(1,20)","(""{a,b,c}"",""(3,4)"")","(abcde,""(5,6)"",""(""""{@,y,z}"""",""""(7,8)"""")"")") INFO: zzz 3 INFO: (zzz,"(1,0)","(""{a,b,c}"",""(3,40)"")","(abcde,""(5,0)"",""(""""{x,y,z}"""",""""(7,8)"""")"")") INFO: (zzz,"(1,0)","(""{a,b,c}"",""(3,40)"")","(abcde,""(5,0)"",""(""""{x,y,z}"""",""""(7,8)"""")"")") INFO: zzz 3 INFO: (yyy,"(100,200)","(""{a,b,c}"",""(0,4)"")","(abcde,""(-1,0)"",""(""""{x,y,z}"""",""""(7,8)"""")"")") INFO: (yyy,"(100,200)","(""{a,b,c}"",""(0,4)"")","(abcde,""(-1,0)"",""(""""{x,y,z}"""",""""(7,8)"""")"")") --end pllua-ng-REL_2_0_4/expected/spi.out000066400000000000000000000201571347047754200172470ustar00rootroot00000000000000-- \set VERBOSITY terse -- -- Test of SPI-related functionality. create temp table tsttab ( id integer primary key, a integer, b text, c numeric, d date ); insert into tsttab(id, a,b,c,d) values (1, 1,'foo',2.34,'2017-01-01'), (2, 2,'bar',2.34,'2017-02-01'), (3, 3,'baz',2.34,'2017-03-01'), (4, 4, 'fred',2.34,'2017-04-01'), (5, 5,'jim',2.34,'2017-05-01'), (6, 6,'sheila',2.34,'2017-06-01'); -- basics do language pllua $$ local tbl tbl = spi.execute([[ select 1 as a, 'foo'::text as b ]]) print(#tbl,tbl[1],type(tbl[1])) print(tbl[1].a,tbl[1].b) tbl = spi.execute([[ select i, 'foo'::text as b from generate_series(1,10000) i ]]) print(#tbl,tbl[1],tbl[10000]) tbl = spi.execute([[ select * from tsttab order by id ]]) for i = 1,#tbl do print(tbl[i]) end $$; INFO: 1 (1,foo) userdata INFO: 1 foo INFO: 10000 (1,foo) (10000,foo) INFO: (1,1,foo,2.34,01-01-2017) INFO: (2,2,bar,2.34,02-01-2017) INFO: (3,3,baz,2.34,03-01-2017) INFO: (4,4,fred,2.34,04-01-2017) INFO: (5,5,jim,2.34,05-01-2017) INFO: (6,6,sheila,2.34,06-01-2017) -- statements do language pllua $$ local stmt,tbl stmt = spi.prepare([[ select * from tsttab where id=$1 ]], {"integer"}) tbl = stmt:execute(1) print(tbl[1]) -- __call metamethod tbl = stmt(6) print(tbl[1]) stmt = spi.prepare([[ select * from tsttab where id = ANY ($1) order by id ]], {pgtype.array.integer}) tbl = stmt:execute(pgtype.array.integer(1,nil,3)) print(#tbl,tbl[1],tbl[2]) -- type deduction: stmt = spi.prepare([[ select 1 + $1 as a, pg_typeof($1) ]]) tbl = stmt:execute(1) print(#tbl,tbl[1]) $$; INFO: (1,1,foo,2.34,01-01-2017) INFO: (6,6,sheila,2.34,06-01-2017) INFO: 2 (1,1,foo,2.34,01-01-2017) (3,3,baz,2.34,03-01-2017) INFO: 1 (2,integer) -- iterators do language pllua $$ for r in spi.rows([[ select * from tsttab order by id ]]) do print(r) end stmt = spi.prepare([[ select * from tsttab where id = ANY ($1) ]], {pgtype.array.integer}) for r in stmt:rows(pgtype.array.integer(1,nil,3)) do print(r) end $$; INFO: (1,1,foo,2.34,01-01-2017) INFO: (2,2,bar,2.34,02-01-2017) INFO: (3,3,baz,2.34,03-01-2017) INFO: (4,4,fred,2.34,04-01-2017) INFO: (5,5,jim,2.34,05-01-2017) INFO: (6,6,sheila,2.34,06-01-2017) INFO: (1,1,foo,2.34,01-01-2017) INFO: (3,3,baz,2.34,03-01-2017) do language pllua $$ local c = spi.newcursor('curs1') local stmt = spi.prepare([[ select * from tsttab order by id for update ]]) c:open(stmt) for r in c:rows() do print(r) if r.id == 3 then spi.execute([[ update tsttab set c = c + 10 where current of curs1 ]]) end end c:move(0, 'absolute') for r in c:rows() do print(r) end for r in spi.rows([[ select * from tsttab order by id ]]) do print(r) end spi.execute([[ update tsttab set c = c - 10 where id=3 ]]) c:close() $$; INFO: (1,1,foo,2.34,01-01-2017) INFO: (2,2,bar,2.34,02-01-2017) INFO: (3,3,baz,2.34,03-01-2017) INFO: (4,4,fred,2.34,04-01-2017) INFO: (5,5,jim,2.34,05-01-2017) INFO: (6,6,sheila,2.34,06-01-2017) INFO: (1,1,foo,2.34,01-01-2017) INFO: (2,2,bar,2.34,02-01-2017) INFO: (4,4,fred,2.34,04-01-2017) INFO: (5,5,jim,2.34,05-01-2017) INFO: (6,6,sheila,2.34,06-01-2017) INFO: (1,1,foo,2.34,01-01-2017) INFO: (2,2,bar,2.34,02-01-2017) INFO: (3,3,baz,12.34,03-01-2017) INFO: (4,4,fred,2.34,04-01-2017) INFO: (5,5,jim,2.34,05-01-2017) INFO: (6,6,sheila,2.34,06-01-2017) -- cursors begin; declare foo scroll cursor for select * from tsttab order by id; do language pllua $$ local c = spi.findcursor("foo") local tbl tbl = c:fetch(1,'next') print(#tbl,tbl[1]) tbl = c:fetch(2,'forward') -- same as 'next' print(#tbl,tbl[1],tbl[2]) tbl = c:fetch(1,'absolute') print(#tbl,tbl[1]) tbl = c:fetch(4,'relative') print(#tbl,tbl[1]) tbl = c:fetch(1,'prior') -- same as 'backward' print(#tbl,tbl[1]) tbl = c:fetch(1,'backward') print(#tbl,tbl[1]) print(c:isopen()) spi.execute("close foo") print(c:isopen()) $$; INFO: 1 (1,1,foo,2.34,01-01-2017) INFO: 2 (2,2,bar,2.34,02-01-2017) (3,3,baz,2.34,03-01-2017) INFO: 1 (1,1,foo,2.34,01-01-2017) INFO: 1 (5,5,jim,2.34,05-01-2017) INFO: 1 (4,4,fred,2.34,04-01-2017) INFO: 1 (3,3,baz,2.34,03-01-2017) INFO: true INFO: false commit; do language pllua $$ local c = spi.newcursor("bar") c:open([[ select * from tsttab where id >= $1 order by id ]], 3) local tbl tbl = c:fetch(1,'next') print(#tbl,tbl[1]) for k,v in ipairs(spi.execute([[ select name, statement from pg_cursors ]])) do print(v.name, v.statement) end c:close() for k,v in ipairs(spi.execute([[ select name, statement from pg_cursors ]])) do print(v.name, v.statement) end c:open([[ select * from tsttab where id < $1 order by id desc ]], 3) tbl = c:fetch(3,'next') print(#tbl,tbl[1],tbl[2]) for k,v in ipairs(spi.execute([[ select name, statement from pg_cursors ]])) do print(v.name, v.statement) end c:close() $$; INFO: 1 (3,3,baz,2.34,03-01-2017) INFO: bar select * from tsttab where id >= $1 order by id INFO: 2 (2,2,bar,2.34,02-01-2017) (1,1,foo,2.34,01-01-2017) INFO: bar select * from tsttab where id < $1 order by id desc -- cursor options on statement do language pllua $$ local stmt = spi.prepare([[ select * from tsttab where id >= $1 order by id ]], {"integer"}, { scroll = true, fast_start = true, generic_plan = true }) local stmt2 = spi.prepare([[ select * from tsttab where id >= $1 order by id ]], {"integer"}, { no_scroll = true }) local c = stmt:getcursor(4) local tbl tbl = c:fetch(3,'next') print(#tbl,tbl[1],tbl[2]) c:move(0,'absolute') tbl = c:fetch(3,'next') print(#tbl,tbl[1],tbl[2]) for k,v in ipairs(spi.execute([[ select name, statement, is_scrollable from pg_cursors ]])) do print(v.name, v.statement, v.is_scrollable) end c:close() c = stmt2:getcursor(4) local c2 = spi.findcursor(c:name()) print(c:name(), rawequal(c,c2)) for k,v in ipairs(spi.execute([[ select name, statement, is_scrollable from pg_cursors ]])) do print(v.name, v.statement, v.is_scrollable) end c:close() $$; INFO: 3 (4,4,fred,2.34,04-01-2017) (5,5,jim,2.34,05-01-2017) INFO: 3 (4,4,fred,2.34,04-01-2017) (5,5,jim,2.34,05-01-2017) INFO: select * from tsttab where id >= $1 order by id true INFO: true INFO: select * from tsttab where id >= $1 order by id false -- check missing params are OK do language pllua $$ local stmt = spi.prepare([[ select * from generate_series($1::integer, $3) i ]]); print(stmt:argtype(1):name()) print(type(stmt:argtype(2))) print(stmt:argtype(3):name()) $$; INFO: integer INFO: nil INFO: integer -- check execute_count do language pllua $$ local q = [[ select * from generate_series($1::integer,$2) i ]] local r1 = spi.execute_count(q, 2, 1, 5) print(#r1) local s = spi.prepare(q, {"integer","integer"}) r1 = s:execute_count(3,1,5) print(#r1) $$; INFO: 2 INFO: 3 -- cursors as parameters and return values create function do_fetch(c refcursor) returns void language pllua as $$ while true do local r = (c:fetch())[1] if r==nil then break end print(r) end $$; create function do_exec(q text, n text) returns refcursor language pllua as $$ local s = spi.prepare(q) local c = spi.newcursor(n) return c:open(s):disown() $$; begin; declare mycur cursor for select * from tsttab order by id; select do_fetch('mycur'); INFO: (1,1,foo,2.34,01-01-2017) INFO: (2,2,bar,2.34,02-01-2017) INFO: (3,3,baz,2.34,03-01-2017) INFO: (4,4,fred,2.34,04-01-2017) INFO: (5,5,jim,2.34,05-01-2017) INFO: (6,6,sheila,2.34,06-01-2017) do_fetch ---------- (1 row) commit; begin; select do_exec('select * from tsttab order by id desc', 'mycur2'); do_exec --------- mycur2 (1 row) do language pllua $$ collectgarbage() $$; -- check cursor stays open fetch all from mycur2; id | a | b | c | d ----+---+--------+------+------------ 6 | 6 | sheila | 2.34 | 06-01-2017 5 | 5 | jim | 2.34 | 05-01-2017 4 | 4 | fred | 2.34 | 04-01-2017 3 | 3 | baz | 2.34 | 03-01-2017 2 | 2 | bar | 2.34 | 02-01-2017 1 | 1 | foo | 2.34 | 01-01-2017 (6 rows) commit; --end pllua-ng-REL_2_0_4/expected/subxact.out000066400000000000000000000117431347047754200201260ustar00rootroot00000000000000-- \set VERBOSITY terse -- create table xatst (a integer); do language pllua $$ local stmt = spi.prepare([[ insert into xatst values ($1) ]]); stmt:execute(1); pcall(function() stmt:execute(2) end) stmt:execute(3); $$; -- should now be two different xids in xatst, and 3 rows select count(*), count(distinct age(xmin)) from xatst; count | count -------+------- 3 | 2 (1 row) truncate table xatst; do language pllua $$ local stmt = spi.prepare([[ insert into xatst values ($1) ]]); stmt:execute(1); print(pcall(function() stmt:execute(2) error("foo") end)) stmt:execute(3); $$; INFO: false [string "DO-block"]:4: foo -- should now be one xid in xatst, and 2 rows select count(*), count(distinct age(xmin)) from xatst; count | count -------+------- 2 | 1 (1 row) truncate table xatst; do language pllua $$ local stmt = spi.prepare([[ insert into xatst values ($1) ]]); stmt:execute(1); print(pcall(function() stmt:execute(2) spi.error("foo") end)) stmt:execute(3); $$; INFO: false ERROR: XX000 foo -- should now be one xid in xatst, and 2 rows select count(*), count(distinct age(xmin)) from xatst; count | count -------+------- 2 | 1 (1 row) do language pllua $$ local function f() for r in spi.rows([[ select * from xatst order by a ]]) do print(r) end end print(pcall(f)) $$; INFO: (1) INFO: (3) INFO: true do language pllua $$ local function f() for r in spi.rows([[ select * from xatst order by a ]]) do print(r) end end local function f2() error("foo") end print(pcall(f2)) f() $$; INFO: false [string "DO-block"]:3: foo INFO: (1) INFO: (3) do language pllua $$ local function f(e) print("error",e) for r in spi.rows([[ select * from xatst order by a ]]) do print(r) end end local function f2() error("foo") end print(xpcall(f2,f)) $$; INFO: error [string "DO-block"]:3: foo INFO: (1) INFO: (3) INFO: false nil truncate table xatst; do language pllua $$ local stmt = spi.prepare([[ insert into xatst values ($1) ]]); local function f(e) print("error",e) stmt:execute(3) end local function f2() stmt:execute(2) error("foo") end stmt:execute(1) print(xpcall(f2,f)) $$; INFO: error [string "DO-block"]:4: foo INFO: false nil -- should now be one xid in xatst, and 2 rows select count(*), count(distinct age(xmin)) from xatst; count | count -------+------- 2 | 1 (1 row) do language pllua $$ local function f(e) error("bar") end local function f2() error("foo") end print(xpcall(f2,f)) $$; INFO: false error in error handling -- tricky error-in-error cases: -- -- pg error inside xpcall handler func needs to abort out to the -- parent of the xpcall, not the xpcall itself. begin; -- we get (harmless) warnings with lua53 but not with luajit for this -- case. suppress them. set local client_min_messages = error; do language pllua $$ local function f(e) spi.error("nested") end local function f2() error("foo") end -- don't print the result because it differs with luajit, all that -- really matters here is that we don't crash and don't reach the -- last print pcall(function() print("entering xpcall"); print("inner xpcall", xpcall(f2,f)) print("should not be reached") end) $$; INFO: entering xpcall commit; do language pllua $$ local level = 0 local function f(e) level = level + 1 if level==1 then print("in error handler",level,e) spi.error("nested") end end local function f2() error("foo") end print("outer pcall", pcall(function() print("entering xpcall"); print("inner xpcall", xpcall(f2,f)) print("should not be reached") end)) $$; INFO: entering xpcall INFO: in error handler 1 [string "DO-block"]:4: foo INFO: outer pcall false ERROR: XX000 nested do language pllua $$ print(lpcall(function() error("caught") end)) $$; INFO: false [string "DO-block"]:2: caught do language pllua $$ print(lpcall(function() spi.error("not caught") end)) $$; ERROR: not caught -- make sure PG errors in coroutines are propagated (but not lua errors) do language pllua $$ local c = coroutine.create(function() coroutine.yield() error("caught") end) print(coroutine.resume(c)) print(coroutine.resume(c)) $$; INFO: true INFO: false [string "DO-block"]:2: caught do language pllua $$ local c = coroutine.create(function() coroutine.yield() spi.error("not caught") end) print(coroutine.resume(c)) print(coroutine.resume(c)) $$; INFO: true ERROR: not caught -- error object funcs do language pllua $$ local err = require 'pllua.error' local r,e = pcall(function() spi.error("22003", "foo", "bar", "baz") end) print(err.type(e), err.category(e), err.errcode(e)) print(e.severity, e.category, e.errcode, e.sqlstate, e.message, e.detail, e.hint) local r,e = pcall(function() error("foo") end) print(err.type(e), err.category(e), err.errcode(e), e) $$; INFO: error data_exception numeric_value_out_of_range INFO: error data_exception numeric_value_out_of_range 22003 foo bar baz INFO: nil nil nil [string "DO-block"]:6: foo --end pllua-ng-REL_2_0_4/expected/triggers.out000066400000000000000000000345511347047754200203050ustar00rootroot00000000000000-- \set VERBOSITY terse \set QUIET 0 -- Test triggers. -- -- Don't use pg10-specific stuff here; that goes in triggers_10.sql create table trigtst ( id integer primary key, name text, flag boolean, qty integer, weight numeric ); CREATE TABLE create function misctrig() returns trigger language pllua as $$ print(trigger.name,...) print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) if trigger.level == "row" then print(old,new) end $$; CREATE FUNCTION create trigger t1 before insert or update or delete on trigtst for each statement execute procedure misctrig('foo','bar'); CREATE TRIGGER create trigger t2 after insert or update or delete on trigtst for each statement execute procedure misctrig('foo','bar'); CREATE TRIGGER insert into trigtst values (1, 'fred', true, 23, 1.73), (2, 'jim', false, 11, 3.1), (3, 'sheila', false, 9, 1.3), (4, 'dougal', false, 1, 9.3), (5, 'brian', false, 31, 51.5), (6, 'ermintrude', true, 91, 52.7), (7, 'dylan', false, 35, 12.1), (8, 'florence', false, 23, 5.4), (9, 'zebedee', false, 199, 7.4); INFO: t1 foo bar INFO: before statement insert trigtst INFO: t2 foo bar INFO: after statement insert trigtst INSERT 0 9 update trigtst set qty = qty + 1; INFO: t1 foo bar INFO: before statement update trigtst INFO: t2 foo bar INFO: after statement update trigtst UPDATE 9 delete from trigtst where name = 'sheila'; INFO: t1 foo bar INFO: before statement delete trigtst INFO: t2 foo bar INFO: after statement delete trigtst DELETE 1 create trigger t3 before insert or update or delete on trigtst for each row execute procedure misctrig('wot'); CREATE TRIGGER create trigger t4 after insert or update or delete on trigtst for each row execute procedure misctrig('wot'); CREATE TRIGGER insert into trigtst values (3, 'sheila', false, 9, 1.3); INFO: t1 foo bar INFO: before statement insert trigtst INFO: t3 wot INFO: before row insert trigtst INFO: nil (3,sheila,f,9,1.3) INFO: t4 wot INFO: after row insert trigtst INFO: nil (3,sheila,f,9,1.3) INFO: t2 foo bar INFO: after statement insert trigtst INSERT 0 1 update trigtst set flag = true where name = 'dylan'; INFO: t1 foo bar INFO: before statement update trigtst INFO: t3 wot INFO: before row update trigtst INFO: (7,dylan,f,36,12.1) (7,dylan,t,36,12.1) INFO: t4 wot INFO: after row update trigtst INFO: (7,dylan,f,36,12.1) (7,dylan,t,36,12.1) INFO: t2 foo bar INFO: after statement update trigtst UPDATE 1 delete from trigtst where name = 'jim'; INFO: t1 foo bar INFO: before statement delete trigtst INFO: t3 wot INFO: before row delete trigtst INFO: (2,jim,f,12,3.1) nil INFO: t4 wot INFO: after row delete trigtst INFO: (2,jim,f,12,3.1) nil INFO: t2 foo bar INFO: after statement delete trigtst DELETE 1 -- check result is as expected select * from trigtst order by id; id | name | flag | qty | weight ----+------------+------+-----+-------- 1 | fred | t | 24 | 1.73 3 | sheila | f | 9 | 1.3 4 | dougal | f | 2 | 9.3 5 | brian | f | 32 | 51.5 6 | ermintrude | t | 92 | 52.7 7 | dylan | t | 36 | 12.1 8 | florence | f | 24 | 5.4 9 | zebedee | f | 200 | 7.4 (8 rows) drop trigger t1 on trigtst; DROP TRIGGER drop trigger t2 on trigtst; DROP TRIGGER drop trigger t3 on trigtst; DROP TRIGGER drop trigger t4 on trigtst; DROP TRIGGER -- compatible mode: assign to row fields create function modtrig1() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) trigger.row.weight = 10 * trigger.row.qty trigger.row.flag = false print(trigger.name,trigger.operation,old,new) $$; CREATE FUNCTION create trigger t1 before insert or update or delete on trigtst for each row execute procedure modtrig1(); CREATE TRIGGER insert into trigtst values (2, 'jim', true, 11, 3.1); INFO: t1 insert nil (2,jim,t,11,3.1) INFO: t1 insert nil (2,jim,f,11,110) INSERT 0 1 update trigtst set flag = true where name = 'ermintrude'; INFO: t1 update (6,ermintrude,t,92,52.7) (6,ermintrude,t,92,52.7) INFO: t1 update (6,ermintrude,t,92,52.7) (6,ermintrude,f,92,920) UPDATE 1 delete from trigtst where name = 'fred'; INFO: t1 delete (1,fred,t,24,1.73) nil INFO: t1 delete (1,fred,f,24,240) nil DELETE 1 select * from trigtst order by id; id | name | flag | qty | weight ----+------------+------+-----+-------- 2 | jim | f | 11 | 110 3 | sheila | f | 9 | 1.3 4 | dougal | f | 2 | 9.3 5 | brian | f | 32 | 51.5 6 | ermintrude | f | 92 | 920 7 | dylan | t | 36 | 12.1 8 | florence | f | 24 | 5.4 9 | zebedee | f | 200 | 7.4 (8 rows) drop trigger t1 on trigtst; DROP TRIGGER -- compatible mode: assign to row wholesale create function modtrig2() returns trigger language pllua as $$ print(trigger.name,trigger.op,old,new) local id,name,flag,qty,weight = new.id, new.name, new.flag, new.qty, new.weight qty = 2 + qty weight = weight * 2 flag = not flag trigger.row = { id = id, name = name, flag = flag, qty = qty, weight = weight } $$; CREATE FUNCTION create trigger t1 before insert or update on trigtst for each row execute procedure modtrig2(); CREATE TRIGGER insert into trigtst values (1, 'fred', true, 23, 1.73); INFO: t1 insert nil (1,fred,t,23,1.73) INSERT 0 1 update trigtst set flag = true where name = 'zebedee'; INFO: t1 update (9,zebedee,f,200,7.4) (9,zebedee,t,200,7.4) UPDATE 1 delete from trigtst where name = 'jim'; DELETE 1 select * from trigtst order by id; id | name | flag | qty | weight ----+------------+------+-----+-------- 1 | fred | f | 25 | 3.46 3 | sheila | f | 9 | 1.3 4 | dougal | f | 2 | 9.3 5 | brian | f | 32 | 51.5 6 | ermintrude | f | 92 | 920 7 | dylan | t | 36 | 12.1 8 | florence | f | 24 | 5.4 9 | zebedee | f | 202 | 14.8 (8 rows) drop trigger t1 on trigtst; DROP TRIGGER -- compatible mode: assign to row wholesale with new datum row create function modtrig3() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) local id,name,flag,qty,weight = new.id, new.name, new.flag, new.qty, new.weight qty = 2 + qty weight = weight * 2 flag = not flag trigger.row = pgtype(new)(id,name,flag,qty,weight) $$; CREATE FUNCTION create trigger t1 before insert or update on trigtst for each row execute procedure modtrig3(); CREATE TRIGGER insert into trigtst values (2, 'jim', false, 11, 3.1); INFO: t1 insert nil (2,jim,f,11,3.1) INSERT 0 1 update trigtst set flag = true where name = 'zebedee'; INFO: t1 update (9,zebedee,f,202,14.8) (9,zebedee,t,202,14.8) UPDATE 1 delete from trigtst where name = 'fred'; DELETE 1 select * from trigtst order by id; id | name | flag | qty | weight ----+------------+------+-----+-------- 2 | jim | t | 13 | 6.2 3 | sheila | f | 9 | 1.3 4 | dougal | f | 2 | 9.3 5 | brian | f | 32 | 51.5 6 | ermintrude | f | 92 | 920 7 | dylan | t | 36 | 12.1 8 | florence | f | 24 | 5.4 9 | zebedee | f | 204 | 29.6 (8 rows) drop trigger t1 on trigtst; DROP TRIGGER -- return value mode create function modtrig4() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) local id,name,flag,qty,weight = new.id, new.name, new.flag, new.qty, new.weight qty = 2 + qty weight = weight * 2 flag = not flag return { id = id, name = name, flag = flag, qty = qty, weight = weight } $$; CREATE FUNCTION create trigger t1 before insert or update on trigtst for each row execute procedure modtrig4(); CREATE TRIGGER insert into trigtst values (1, 'fred', true, 23, 1.73); INFO: t1 insert nil (1,fred,t,23,1.73) INSERT 0 1 update trigtst set flag = false where name = 'dylan'; INFO: t1 update (7,dylan,t,36,12.1) (7,dylan,f,36,12.1) UPDATE 1 delete from trigtst where name = 'jim'; DELETE 1 select * from trigtst order by id; id | name | flag | qty | weight ----+------------+------+-----+-------- 1 | fred | f | 25 | 3.46 3 | sheila | f | 9 | 1.3 4 | dougal | f | 2 | 9.3 5 | brian | f | 32 | 51.5 6 | ermintrude | f | 92 | 920 7 | dylan | t | 38 | 24.2 8 | florence | f | 24 | 5.4 9 | zebedee | f | 204 | 29.6 (8 rows) drop trigger t1 on trigtst; DROP TRIGGER -- return value mode create function modtrig5() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) local id,name,flag,qty,weight = new.id, new.name, new.flag, new.qty, new.weight qty = 2 + qty weight = weight * 2 flag = not flag return pgtype(new)(id,name,flag,qty,weight) $$; CREATE FUNCTION create trigger t1 before insert or update on trigtst for each row execute procedure modtrig5(); CREATE TRIGGER insert into trigtst values (2, 'jim', false, 11, 3.1); INFO: t1 insert nil (2,jim,f,11,3.1) INSERT 0 1 update trigtst set flag = false where name = 'dougal'; INFO: t1 update (4,dougal,f,2,9.3) (4,dougal,f,2,9.3) UPDATE 1 delete from trigtst where name = 'fred'; DELETE 1 select * from trigtst order by id; id | name | flag | qty | weight ----+------------+------+-----+-------- 2 | jim | t | 13 | 6.2 3 | sheila | f | 9 | 1.3 4 | dougal | t | 4 | 18.6 5 | brian | f | 32 | 51.5 6 | ermintrude | f | 92 | 920 7 | dylan | t | 38 | 24.2 8 | florence | f | 24 | 5.4 9 | zebedee | f | 204 | 29.6 (8 rows) drop trigger t1 on trigtst; DROP TRIGGER -- throw error from trigger create function modtrig6() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) if new.flag ~= old.flag then error("no changing flags") end $$; CREATE FUNCTION create trigger t1 before update on trigtst for each row execute procedure modtrig6(); CREATE TRIGGER update trigtst set flag = false where name = 'dougal'; INFO: t1 update (4,dougal,t,4,18.6) (4,dougal,f,4,18.6) ERROR: pllua: [string "modtrig6"]:3: no changing flags select * from trigtst order by id; id | name | flag | qty | weight ----+------------+------+-----+-------- 2 | jim | t | 13 | 6.2 3 | sheila | f | 9 | 1.3 4 | dougal | t | 4 | 18.6 5 | brian | f | 32 | 51.5 6 | ermintrude | f | 92 | 920 7 | dylan | t | 38 | 24.2 8 | florence | f | 24 | 5.4 9 | zebedee | f | 204 | 29.6 (8 rows) drop trigger t1 on trigtst; DROP TRIGGER -- throw error from trigger create function modtrig7() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) if new.flag ~= old.flag then error("no changing flags") end $$; CREATE FUNCTION create trigger t1 before update on trigtst for each row execute procedure modtrig7(); CREATE TRIGGER update trigtst set flag = true where name = 'florence'; INFO: t1 update (8,florence,f,24,5.4) (8,florence,t,24,5.4) ERROR: pllua: [string "modtrig7"]:3: no changing flags select * from trigtst order by id; id | name | flag | qty | weight ----+------------+------+-----+-------- 2 | jim | t | 13 | 6.2 3 | sheila | f | 9 | 1.3 4 | dougal | t | 4 | 18.6 5 | brian | f | 32 | 51.5 6 | ermintrude | f | 92 | 920 7 | dylan | t | 38 | 24.2 8 | florence | f | 24 | 5.4 9 | zebedee | f | 204 | 29.6 (8 rows) drop trigger t1 on trigtst; DROP TRIGGER -- suppress action 1 create function modtrig8() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) if new.flag ~= old.flag then return nil end $$; CREATE FUNCTION create trigger t1 before update on trigtst for each row execute procedure modtrig8(); CREATE TRIGGER update trigtst set flag = true where name = 'florence'; INFO: t1 update (8,florence,f,24,5.4) (8,florence,t,24,5.4) UPDATE 0 select * from trigtst order by id; id | name | flag | qty | weight ----+------------+------+-----+-------- 2 | jim | t | 13 | 6.2 3 | sheila | f | 9 | 1.3 4 | dougal | t | 4 | 18.6 5 | brian | f | 32 | 51.5 6 | ermintrude | f | 92 | 920 7 | dylan | t | 38 | 24.2 8 | florence | f | 24 | 5.4 9 | zebedee | f | 204 | 29.6 (8 rows) drop trigger t1 on trigtst; DROP TRIGGER -- suppress action 2 create function modtrig9() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) if new.flag ~= old.flag then trigger.row = nil end $$; CREATE FUNCTION create trigger t1 before update on trigtst for each row execute procedure modtrig9(); CREATE TRIGGER update trigtst set flag = true where name = 'florence'; INFO: t1 update (8,florence,f,24,5.4) (8,florence,t,24,5.4) UPDATE 0 select * from trigtst order by id; id | name | flag | qty | weight ----+------------+------+-----+-------- 2 | jim | t | 13 | 6.2 3 | sheila | f | 9 | 1.3 4 | dougal | t | 4 | 18.6 5 | brian | f | 32 | 51.5 6 | ermintrude | f | 92 | 920 7 | dylan | t | 38 | 24.2 8 | florence | f | 24 | 5.4 9 | zebedee | f | 204 | 29.6 (8 rows) drop trigger t1 on trigtst; DROP TRIGGER -- table with one column exercises several edge cases: create table trigtst1col (col integer); CREATE TABLE create function modtrig10() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) new.col = 123 $$; CREATE FUNCTION create trigger t1 before insert on trigtst1col for each row execute procedure modtrig10(); CREATE TRIGGER insert into trigtst1col values (1); INFO: t1 insert nil (1) INSERT 0 1 insert into trigtst1col values (2); INFO: t1 insert nil (2) INSERT 0 1 select * from trigtst1col; col ----- 123 123 (2 rows) create type t2col as (a integer, b text); CREATE TYPE create table trigtst1col2 (col t2col); CREATE TABLE create function modtrig11() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) new.col.a = 123 $$; CREATE FUNCTION create trigger t1 before insert on trigtst1col2 for each row execute procedure modtrig11(); CREATE TRIGGER insert into trigtst1col2 values (row(1,'foo')::t2col); INFO: t1 insert nil ("(1,foo)") INSERT 0 1 select * from trigtst1col2; col ----------- (123,foo) (1 row) -- pllua-ng-REL_2_0_4/expected/triggers_10.out000066400000000000000000000056161347047754200206050ustar00rootroot00000000000000-- \set VERBOSITY terse \set QUIET 0 -- Test pg10+ trigger functionality. create table trigtst2 ( id integer primary key, name text, flag boolean, qty integer, weight numeric ); CREATE TABLE create function ttrig1() returns trigger language pllua as $$ print(trigger.name,...) print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) for r in spi.rows([[ select * from newtab ]]) do print(r) end $$; CREATE FUNCTION create function ttrig2() returns trigger language pllua as $$ print(trigger.name,...) print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) for r in spi.rows([[ select 'old', * from oldtab union all select 'new', * from newtab ]]) do print(r) end $$; CREATE FUNCTION create function ttrig3() returns trigger language pllua as $$ print(trigger.name,...) print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) for r in spi.rows([[ select * from oldtab ]]) do print(r) end $$; CREATE FUNCTION create trigger t1 after insert on trigtst2 referencing new table as newtab for each statement execute procedure ttrig1('t1 insert'); CREATE TRIGGER create trigger t2 after update on trigtst2 referencing old table as oldtab new table as newtab for each statement execute procedure ttrig2('t2 update'); CREATE TRIGGER create trigger t3 after delete on trigtst2 referencing old table as oldtab for each statement execute procedure ttrig3('t3 delete'); CREATE TRIGGER insert into trigtst2 values (1, 'fred', true, 23, 1.73), (2, 'jim', false, 11, 3.1), (3, 'sheila', false, 9, 1.3), (4, 'dougal', false, 1, 9.3), (5, 'brian', false, 31, 51.5), (6, 'ermintrude', true, 91, 52.7), (7, 'dylan', false, 35, 12.1), (8, 'florence', false, 23, 5.4), (9, 'zebedee', false, 199, 7.4); INFO: t1 t1 insert INFO: after statement insert trigtst2 INFO: (1,fred,t,23,1.73) INFO: (2,jim,f,11,3.1) INFO: (3,sheila,f,9,1.3) INFO: (4,dougal,f,1,9.3) INFO: (5,brian,f,31,51.5) INFO: (6,ermintrude,t,91,52.7) INFO: (7,dylan,f,35,12.1) INFO: (8,florence,f,23,5.4) INFO: (9,zebedee,f,199,7.4) INSERT 0 9 update trigtst2 set qty = qty + 1; INFO: t2 t2 update INFO: after statement update trigtst2 INFO: (old,1,fred,t,23,1.73) INFO: (old,2,jim,f,11,3.1) INFO: (old,3,sheila,f,9,1.3) INFO: (old,4,dougal,f,1,9.3) INFO: (old,5,brian,f,31,51.5) INFO: (old,6,ermintrude,t,91,52.7) INFO: (old,7,dylan,f,35,12.1) INFO: (old,8,florence,f,23,5.4) INFO: (old,9,zebedee,f,199,7.4) INFO: (new,1,fred,t,24,1.73) INFO: (new,2,jim,f,12,3.1) INFO: (new,3,sheila,f,10,1.3) INFO: (new,4,dougal,f,2,9.3) INFO: (new,5,brian,f,32,51.5) INFO: (new,6,ermintrude,t,92,52.7) INFO: (new,7,dylan,f,36,12.1) INFO: (new,8,florence,f,24,5.4) INFO: (new,9,zebedee,f,200,7.4) UPDATE 9 delete from trigtst2 where name = 'sheila'; INFO: t3 t3 delete INFO: after statement delete trigtst2 INFO: (3,sheila,f,10,1.3) DELETE 1 -- pllua-ng-REL_2_0_4/expected/trusted.out000066400000000000000000000013711347047754200201430ustar00rootroot00000000000000-- \set VERBOSITY terse set pllua.on_trusted_init=$$ local e = require 'pllua.elog' package.preload['testmod1'] = function() e.info("testmod1 loaded") return { testfunc = function() print("testfunc1") end } end; package.preload['testmod2'] = function() e.info("testmod2 loaded") return { testfunc = function() print("testfunc2") end } end; trusted.allow('testmod1', nil, nil, nil, false); trusted.allow('testmod2', nil, nil, nil, true); $$; -- do language pllua $$ print("interpreter loaded") $$; INFO: testmod2 loaded INFO: interpreter loaded do language pllua $$ local m = require 'testmod1' m.testfunc() $$; INFO: testmod1 loaded INFO: testfunc1 do language pllua $$ local m = require 'testmod2' m.testfunc() $$; INFO: testfunc2 --end pllua-ng-REL_2_0_4/expected/types.out000066400000000000000000000211711347047754200176150ustar00rootroot00000000000000-- \set VERBOSITY terse -- create type ctype3 as (fred integer, jim numeric); do $$ begin if current_setting('server_version_num')::integer >= 110000 then execute 'create domain dtype as ctype3 check((VALUE).jim is not null)'; else execute 'create type dtype as (fred integer, jim numeric)'; end if; end; $$; create type ctype2 as (thingy text, wotsit integer); create type ctype as (foo text, bar ctype2, baz dtype); create table tdata ( intcol integer, textcol text, charcol char(32), varcharcol varchar(32), compcol ctype, dcompcol dtype ); insert into tdata values (1, 'row 1', 'padded with blanks', 'not padded', ('x',('y',1111),(111,11.1)), (11,1.1)), (2, 'row 2', 'padded with blanks', 'not padded', ('x',('y',2222),(222,22.2)), (22,2.2)), (3, 'row 3', 'padded with blanks', 'not padded', ('x',('y',3333),(333,33.3)), (33,3.3)); create function tf1() returns setof tdata language pllua as $f$ for i = 1,4 do coroutine.yield({ intcol = i, textcol = "row "..i, charcol = "padded with blanks", varcharcol = "not padded", compcol = { foo = "x", bar = { thingy = "y", wotsit = i*1111 }, baz = { fred = i*111, jim = i*11.1 } }, dcompcol = { fred = i*11, jim = i*1.1 } }) end $f$; select * from tf1(); intcol | textcol | charcol | varcharcol | compcol | dcompcol --------+---------+----------------------------------+------------+-----------------------------+---------- 1 | row 1 | padded with blanks | not padded | (x,"(y,1111)","(111,11.1)") | (11,1.1) 2 | row 2 | padded with blanks | not padded | (x,"(y,2222)","(222,22.2)") | (22,2.2) 3 | row 3 | padded with blanks | not padded | (x,"(y,3333)","(333,33.3)") | (33,3.3) 4 | row 4 | padded with blanks | not padded | (x,"(y,4444)","(444,44.4)") | (44,4.4) (4 rows) -- -- various checks of type handling -- do language pllua $$ print(pgtype(nil,'ctype3')(1,2)) $$; INFO: (1,2) do language pllua $$ print(pgtype(nil,'ctype3')({1,2})) $$; INFO: (,) do language pllua $$ print(pgtype(nil,'ctype3')(true,true)) $$; ERROR: pllua: incompatible value type do language pllua $$ print(pgtype(nil,'ctype3')("1","2")) $$; INFO: (1,2) do language pllua $$ print(pgtype(nil,'ctype3')({fred=1,jim=2})) $$; INFO: (1,2) do language pllua $$ print(pgtype(nil,'ctype3')({fred=1,jim={}})) $$; ERROR: pllua: incompatible value type do language pllua $$ print(pgtype(nil,'ctype3')({fred=1,jim=nil})) $$; INFO: (1,) --do language pllua $$ print(pgtype(nil,'dtype')({fred=1,jim=nil})) $$; create function tf2() returns setof tdata language pllua as $f$ local t = spi.execute("select * from tdata") for i,v in ipairs(t) do coroutine.yield(v) end $f$; select * from tf2(); intcol | textcol | charcol | varcharcol | compcol | dcompcol --------+---------+----------------------------------+------------+-----------------------------+---------- 1 | row 1 | padded with blanks | not padded | (x,"(y,1111)","(111,11.1)") | (11,1.1) 2 | row 2 | padded with blanks | not padded | (x,"(y,2222)","(222,22.2)") | (22,2.2) 3 | row 3 | padded with blanks | not padded | (x,"(y,3333)","(333,33.3)") | (33,3.3) (3 rows) do language pllua $$ print(pgtype.ctype3()) $$; INFO: (,) -- ensure detoasting of nested composites works right do language pllua $f$ for r in spi.rows("select * from tdata") do print(r.intcol, r.compcol.foo, r.compcol.bar.wotsit, r.dcompcol.jim) end $f$; INFO: 1 x 1111 1.1 INFO: 2 x 2222 2.2 INFO: 3 x 3333 3.3 do language pllua $$ a = pgtype.array.integer({{{1,2}},{{3,4}},{{5,6}}},3,1,2) print(a) print(#a,#(a[1]),#(a[1][1])) print(a[3][1][2],a[1][1][1]) $$; INFO: {{{1,2}},{{3,4}},{{5,6}}} INFO: 3 1 2 INFO: 6 1 do language pllua $$ print(pgtype.int4range(123,456)) $$; INFO: [123,456) do language pllua $$ print(pgtype.int4range()) $$; INFO: empty do language pllua $$ print(pgtype.int4range(123,456,'(]')) $$; INFO: [124,457) do language pllua $$ print(pgtype.int4range(nil,456,'(]')) $$; INFO: (,457) do language pllua $$ print(pgtype.int4range(nil,nil)) $$; INFO: (,) do language pllua $$ print(pgtype.int4range(123,nil)) $$; INFO: [123,) do language pllua $$ print(pgtype.int4range('[12,56]')) $$; INFO: [12,57) do language pllua $$ local r1,r2,r3 = pgtype.numrange('[12,56]'), pgtype.numrange('empty'), pgtype.numrange('(12,)') print(r1.lower,r1.upper,r1.lower_inc,r1.upper_inc,r1.lower_inf,r1.upper_inf,r1.isempty) print(r2.lower,r2.upper,r2.lower_inc,r2.upper_inc,r2.lower_inf,r2.upper_inf,r2.isempty) print(r3.lower,r3.upper,r3.lower_inc,r3.upper_inc,r3.lower_inf,r3.upper_inf,r3.isempty) $$; INFO: 12 56 true true false false false INFO: nil nil false false false false true INFO: 12 nil false false false true false create type myenum as enum ('TRUE', 'FALSE', 'FILE_NOT_FOUND'); create function pg_temp.f1(a myenum) returns text language pllua as $$ print(a,type(a)) return a $$; select pg_temp.f1(x) from unnest(enum_range(null::myenum)) x; INFO: TRUE string INFO: FALSE string INFO: FILE_NOT_FOUND string f1 ---------------- TRUE FALSE FILE_NOT_FOUND (3 rows) create function pg_temp.f2() returns myenum language pllua as $$ return 'FILE_NOT_FOUND' $$; select pg_temp.f2(); f2 ---------------- FILE_NOT_FOUND (1 row) -- domains create domain mydom1 as varchar(3); create domain mydom2 as varchar(3) check (value in ('foo','bar','baz')); create domain mydom3 as varchar(3) not null; create domain mydom4 as varchar(3) not null check (value in ('foo','bar','baz')); create function pg_temp.f3(a mydom1) returns void language pllua as $$ print(pgtype(nil,1):name(), type(a), #a) $$; select pg_temp.f3('foo') union all select pg_temp.f3('bar '); INFO: mydom1 string 3 INFO: mydom1 string 3 f3 ---- (2 rows) create function pg_temp.f4d1(a text) returns mydom1 language pllua as $$ return a $$; select pg_temp.f4d1('foo'); f4d1 ------ foo (1 row) select pg_temp.f4d1('bar '); f4d1 ------ bar (1 row) select pg_temp.f4d1('toolong'); ERROR: value too long for type character varying(3) select pg_temp.f4d1(null); f4d1 ------ (1 row) create function pg_temp.f4d2(a text) returns mydom2 language pllua as $$ return a $$; select pg_temp.f4d2('bar '); f4d2 ------ bar (1 row) select pg_temp.f4d2('bad'); ERROR: value for domain mydom2 violates check constraint "mydom2_check" select pg_temp.f4d2('toolong'); ERROR: value too long for type character varying(3) select pg_temp.f4d2(null); f4d2 ------ (1 row) create function pg_temp.f4d3(a text) returns mydom3 language pllua as $$ return a $$; select pg_temp.f4d3('bar '); f4d3 ------ bar (1 row) select pg_temp.f4d3('toolong'); ERROR: value too long for type character varying(3) select pg_temp.f4d3(null); ERROR: domain mydom3 does not allow null values create function pg_temp.f4d4(a text) returns mydom4 language pllua as $$ return a $$; select pg_temp.f4d4('bar '); f4d4 ------ bar (1 row) select pg_temp.f4d4('bad'); ERROR: value for domain mydom4 violates check constraint "mydom4_check" select pg_temp.f4d4('toolong'); ERROR: value too long for type character varying(3) select pg_temp.f4d4(null); ERROR: domain mydom4 does not allow null values -- array coercions -- relabeltype path do language pllua $$ local a = pgtype.array.varchar("foo","bar") local b = pgtype.array.text(a) print(b) $$; INFO: {foo,bar} -- cast function path do language pllua $$ local a = pgtype.array.boolean(false,true) local b = pgtype.array.text(a) print(b) $$; INFO: {false,true} -- IO path do language pllua $$ local a = pgtype.array.integer(10,20) local b = pgtype.array.text(a) print(b) $$; INFO: {10,20} -- array typmod coercions create temp table atc (a varchar(10)[], b char(10)[]); do language pllua $$ local a = pgtype.array.varchar('foo','bar','value_too_long_for_type') local b = pgtype.atc(a,nil) $$; ERROR: value too long for type character varying(10) do language pllua $$ local a = pgtype.array.bpchar('foo','bar','value ') local b = pgtype.atc(nil,a) print(b) $$; INFO: (,"{""foo "",""bar "",""value ""}") -- composite type construction edge cases do language pllua $$ print(pgtype.ctype3()) print(pgtype.ctype3(nil)) $$; INFO: (,) INFO: (,) do language pllua $$ print(pgtype.ctype3(1)) -- error $$; ERROR: pllua: incorrect number of arguments for type constructor (expected 2 got 1) do language pllua $$ print(pgtype.ctype3(1,2)) $$; INFO: (1,2) --end pllua-ng-REL_2_0_4/hstore/000077500000000000000000000000001347047754200154215ustar00rootroot00000000000000pllua-ng-REL_2_0_4/hstore/Makefile000066400000000000000000000031221347047754200170570ustar00rootroot00000000000000# Makefile for PL/Lua + hstore PG_CONFIG ?= pg_config # Lua specific # General LUA_INCDIR ?= /usr/local/include/lua53 LUALIB ?= -L/usr/local/lib -llua-5.3 # no need to edit below here MODULE_big = hstore_pllua EXTENSION = hstore_pllua hstore_plluau DATA = hstore_pllua--1.0.sql hstore_plluau--1.0.sql OBJS = hstore_pllua.o REGRESS = create_ext hstore # MAJORVERSION and includedir_server are not defined yet, but will be # defined before PG_CPPFLAGS is expanded # for pg11+, hstore.h will have been installed here as hstore/hstore.h, # but pllua.h might also have been installed as pllua/pllua.h and we don't # want to take that in preference to ../src/pllua.h. So we use # #include "hstore/hstore.h" but #include "pllua.h" EXT_INCDIR = $(includedir_server)/extension # for pg 9.5/9.6/10, we have a local copy of hstore.h since it happens # to be the same, barring non-semantic whitespace, between the three # versions EXT_INCDIR_OLD = $(srcdir)/old_inc PG_CPPFLAGS = -I$(LUA_INCDIR) -I$(srcdir)/../src PG_CPPFLAGS += -I$(EXT_INCDIR$(if $(filter 9.% 10,$(MAJORVERSION)),_OLD)) SHLIB_LINK = $(LUALIB) # if VPATH is not already set, but the makefile is not in the current # dir, then assume a vpath build using the makefile's directory as # source. PGXS will set $(srcdir) accordingly. ifndef VPATH ifneq ($(realpath $(CURDIR)),$(realpath $(dir $(firstword $(MAKEFILE_LIST))))) VPATH := $(dir $(firstword $(MAKEFILE_LIST))) endif endif PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) ifeq ($(filter-out 7.% 8.% 9.0 9.1 9.2 9.3 9.4, $(MAJORVERSION)),) $(error unsupported PostgreSQL version) endif pllua-ng-REL_2_0_4/hstore/expected/000077500000000000000000000000001347047754200172225ustar00rootroot00000000000000pllua-ng-REL_2_0_4/hstore/expected/create_ext.out000066400000000000000000000001311347047754200220710ustar00rootroot00000000000000-- create extension hstore; create extension pllua; create extension hstore_pllua; --end pllua-ng-REL_2_0_4/hstore/expected/hstore.out000066400000000000000000000023141347047754200212570ustar00rootroot00000000000000-- \set VERBOSITY terse -- smoke tests do language pllua $$ -- these should work even without the transform local hs = pgtype.hstore('"foo"=>"bar", "baz"=>"quux"') print(hs) local res = (spi.execute([[select pg_typeof($1) as type, $1::text as text, $1->'foo' as foo, $1->'baz' as bar]], hs))[1] print(res.type, res.text, res.foo, res.bar) -- but these use it: res = (spi.execute([[select $1 as hs]], hs))[1] print(type(res.hs)) do local ks = {} for k,v in pairs(res.hs) do ks[1+#ks] = k end table.sort(ks) for i,k in ipairs(ks) do print(k,res.hs[k]) end end local hs2 = pgtype.hstore({ foo = "bar", baz = "quux" }) res = (spi.execute([[select pg_typeof($1) as t2]], hs2))[1] print(res.t2) res = (spi.execute([[select $1 = $2 as eq]], hs, hs2))[1] print(res.eq) $$; INFO: "baz"=>"quux", "foo"=>"bar" INFO: hstore "baz"=>"quux", "foo"=>"bar" bar quux INFO: table INFO: baz quux INFO: foo bar INFO: hstore INFO: true -- make sure that non-table types don't crash the transform do language pllua $$ print(pgtype.hstore(123)) $$; ERROR: pllua: incompatible value type do language pllua $$ print(pgtype.hstore(function() end)) $$; ERROR: pllua: incompatible value type --end pllua-ng-REL_2_0_4/hstore/hstore_pllua--1.0.sql000066400000000000000000000014111347047754200212110ustar00rootroot00000000000000/* hstore_pllua--1.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION hstore_pllua" to load this file. \quit -- force module load; at runtime this doesn't matter because the transforms -- can't be legitimately called except from within pllua.so, but we want -- CREATE EXTENSION to work even in a cold session do language pllua $$ $$; CREATE FUNCTION hstore_to_pllua(val internal) RETURNS internal LANGUAGE C STRICT IMMUTABLE AS 'MODULE_PATHNAME'; CREATE FUNCTION pllua_to_hstore(val internal) RETURNS hstore LANGUAGE C STRICT IMMUTABLE AS 'MODULE_PATHNAME'; CREATE TRANSFORM FOR hstore LANGUAGE pllua ( FROM SQL WITH FUNCTION hstore_to_pllua(internal), TO SQL WITH FUNCTION pllua_to_hstore(internal) ); pllua-ng-REL_2_0_4/hstore/hstore_pllua.c000066400000000000000000000153351347047754200202750ustar00rootroot00000000000000/* hstore_pllua.c */ /* note, we do not support out-of-pllua-tree building */ #include "pllua.h" #include "hstore/hstore.h" #include "mb/pg_wchar.h" PG_MODULE_MAGIC; #ifndef PG_GETARG_HSTORE_P #define PG_GETARG_HSTORE_P(h_) PG_GETARG_HS(h_) #endif extern void _PG_init(void); /* Linkage to functions in hstore module */ typedef HStore *(*hstoreUpgrade_t) (Datum orig); static hstoreUpgrade_t hstoreUpgrade_p; typedef int (*hstoreUniquePairs_t) (Pairs *a, int32 l, int32 *buflen); static hstoreUniquePairs_t hstoreUniquePairs_p; typedef HStore *(*hstorePairs_t) (Pairs *pairs, int32 pcount, int32 buflen); static hstorePairs_t hstorePairs_p; typedef size_t (*hstoreCheckKeyLen_t) (size_t len); static hstoreCheckKeyLen_t hstoreCheckKeyLen_p; typedef size_t (*hstoreCheckValLen_t) (size_t len); static hstoreCheckValLen_t hstoreCheckValLen_p; /* Linkage to functions in pllua module */ typedef void (*pllua_pcall_t)(lua_State *L, int nargs, int nresults, int msgh); static pllua_pcall_t pllua_pcall_p; typedef lua_CFunction pllua_trampoline_t; static lua_CFunction pllua_trampoline_p; typedef bool (*pllua_pairs_start_t) (lua_State *L, int nd, bool noerror); static pllua_pairs_start_t pllua_pairs_start_p; typedef int (*pllua_pairs_next_t) (lua_State *L); static pllua_pairs_next_t pllua_pairs_next_p; /* * Module initialize function: fetch function pointers for cross-module calls. */ void _PG_init(void) { #define EXTFUNCS(x_) #x_ #define EXTFUNCT(xp_) xp_##_t #define EXTFUNCP(xp_) xp_##_p #define EXTFUNC(lib_, n_) \ AssertVariableIsOfType(&n_, EXTFUNCT(n_)); \ EXTFUNCP(n_) = (EXTFUNCT(n_)) \ load_external_function(lib_, EXTFUNCS(n_), true, NULL); EXTFUNC("$libdir/hstore", hstoreUpgrade); EXTFUNC("$libdir/hstore", hstoreUniquePairs); EXTFUNC("$libdir/hstore", hstorePairs); EXTFUNC("$libdir/hstore", hstoreCheckKeyLen); EXTFUNC("$libdir/hstore", hstoreCheckValLen); EXTFUNC("$libdir/pllua", pllua_pcall); EXTFUNC("$libdir/pllua", pllua_trampoline); EXTFUNC("$libdir/pllua", pllua_pairs_start); EXTFUNC("$libdir/pllua", pllua_pairs_next); } /* These defines must be after the module init function */ #define hstoreUpgrade hstoreUpgrade_p #define hstoreUniquePairs hstoreUniquePairs_p #define hstorePairs hstorePairs_p #define hstoreCheckKeyLen hstoreCheckKeyLen_p #define hstoreCheckValLen hstoreCheckValLen_p #define pllua_pcall pllua_pcall_p #define pllua_trampoline pllua_trampoline_p #define pllua_pairs_start pllua_pairs_start_p #define pllua_pairs_next pllua_pairs_next_p static int hstore_to_pllua_real(lua_State *L) { HStore *in = lua_touserdata(L, 1); int i; int count = HS_COUNT(in); char *base = STRPTR(in); HEntry *entries = ARRPTR(in); lua_createtable(L, 0, count); for (i = 0; i < count; i++) { lua_pushlstring(L, HSTORE_KEY(entries, base, i), HSTORE_KEYLEN(entries, i)); if (HSTORE_VALISNULL(entries, i)) lua_pushboolean(L, 0); else lua_pushlstring(L, HSTORE_VAL(entries, base, i), HSTORE_VALLEN(entries, i)); lua_rawset(L, -3); } return 1; } /* * equivalent to: * * local keys,vals = {},{} * for k,v in pairs(hs) do keys[#keys+1] = k vals[#vals+1] = v end * then makes a full userdata with a Pairs array and refs to keys,vals * */ static int pllua_to_hstore_real(lua_State *L) { Pairs *pairs = NULL; int idx = 0; int pcount = 0; bool metaloop; /* * Decline if there isn't exactly 1 arg. */ if (lua_gettop(L) != 1) { lua_pushnil(L); lua_pushnil(L); return 2; } lua_newtable(L); /* index 2: keys */ lua_newtable(L); /* index 3: vals */ metaloop = pllua_pairs_start(L, 1, true); /* * If it doesn't have a pairs metamethod and it's not a plain table, * then we have to decline it. */ if (!metaloop && !lua_istable(L, 1)) { /* pairs_start already pushed one nil */ lua_pushnil(L); return 2; } while (metaloop ? pllua_pairs_next(L) : lua_next(L, 1)) { ++idx; if (lua_isnil(L, -1) || (lua_isboolean(L, -1) && !lua_toboolean(L, -1))) { lua_pop(L, 1); } else { luaL_tolstring(L, -1, NULL); lua_rawseti(L, 3, idx); lua_pop(L, 1); } luaL_tolstring(L, -1, NULL); lua_rawseti(L, 2, idx); } lua_settop(L, 3); pcount = idx; lua_pushinteger(L, pcount); /* first result */ pairs = lua_newuserdata(L, (idx ? idx : 1) * sizeof(Pairs)); lua_newtable(L); lua_pushvalue(L, 2); lua_setfield(L, -2, "keys"); lua_pushvalue(L, 3); lua_setfield(L, -2, "values"); lua_setuservalue(L, -2); for (idx = 0; idx < pcount; ++idx) { lua_rawgeti(L, 2, idx+1); pairs[idx].key = (char *) lua_tolstring(L, -1, &(pairs[idx].keylen)); pairs[idx].needfree = false; lua_pop(L, 1); if (lua_rawgeti(L, 3, idx+1) == LUA_TNIL) { pairs[idx].val = NULL; pairs[idx].vallen = 0; pairs[idx].isnull = true; } else { pairs[idx].val = (char *) lua_tolstring(L, -1, &(pairs[idx].vallen)); pairs[idx].isnull = false; } lua_pop(L, 1); } return 2; } PG_FUNCTION_INFO_V1(hstore_to_pllua); Datum hstore_to_pllua(PG_FUNCTION_ARGS) { HStore *in = PG_GETARG_HSTORE_P(0); pllua_node *node = (pllua_node *) fcinfo->context; lua_State *L; if (!node || node->type != T_Invalid || node->magic != PLLUA_MAGIC) elog(ERROR, "hstore_to_pllua must only be called from pllua"); L = node->L; pllua_pushcfunction(L, pllua_trampoline); lua_pushlightuserdata(L, hstore_to_pllua_real); lua_pushlightuserdata(L, in); pllua_pcall(L, 2, 1, 0); return (Datum)0; } PG_FUNCTION_INFO_V1(pllua_to_hstore); Datum pllua_to_hstore(PG_FUNCTION_ARGS) { pllua_node *node = (pllua_node *) fcinfo->context; lua_State *L; int32 pcount = 0; HStore *out = NULL; Pairs *pairs; if (!node || node->type != T_Invalid || node->magic != PLLUA_MAGIC) elog(ERROR, "pllua_to_hstore must only be called from pllua"); L = node->L; pllua_pushcfunction(L, pllua_trampoline); lua_insert(L, 1); lua_pushlightuserdata(L, pllua_to_hstore_real); lua_insert(L, 2); pllua_pcall(L, lua_gettop(L) - 1, 2, 0); /* * this ptr is the Pairs struct as a Lua full userdata, which carries * refs to the tables holding the keys and values strings to prevent * them being GC'd. hstorePairs will copy everything into a new palloc'd * value, and the storage will be GC'd sometime later after we pop it. */ pcount = lua_tointeger(L, -2); pairs = lua_touserdata(L, -1); if (pairs) { int i; int32 buflen; for (i = 0; i < pcount; ++i) { pairs[i].keylen = hstoreCheckKeyLen(pairs[i].keylen); pairs[i].vallen = hstoreCheckKeyLen(pairs[i].vallen); pg_verifymbstr(pairs[i].key, pairs[i].keylen, false); pg_verifymbstr(pairs[i].val, pairs[i].vallen, false); } pcount = hstoreUniquePairs(pairs, pcount, &buflen); out = hstorePairs(pairs, pcount, buflen); } lua_pop(L, 2); if (out) PG_RETURN_POINTER(out); else PG_RETURN_NULL(); } pllua-ng-REL_2_0_4/hstore/hstore_pllua.control000066400000000000000000000002551347047754200215260ustar00rootroot00000000000000# hstore_pllua extension default_version = '1.0' comment = 'Hstore transform for Lua' module_pathname = '$libdir/hstore_pllua' relocatable = true requires = 'hstore, pllua' pllua-ng-REL_2_0_4/hstore/hstore_plluau--1.0.sql000066400000000000000000000014651347047754200214070ustar00rootroot00000000000000/* hstore_plluau--1.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION hstore_plluau" to load this file. \quit -- force module load; at runtime this doesn't matter because the transforms -- can't be legitimately called except from within pllua.so, but we want -- CREATE EXTENSION to work even in a cold session do language plluau $$ $$; CREATE FUNCTION hstore_to_plluau(val internal) RETURNS internal LANGUAGE C STRICT IMMUTABLE AS 'MODULE_PATHNAME','hstore_to_pllua'; CREATE FUNCTION plluau_to_hstore(val internal) RETURNS hstore LANGUAGE C STRICT IMMUTABLE AS 'MODULE_PATHNAME','pllua_to_hstore'; CREATE TRANSFORM FOR hstore LANGUAGE plluau ( FROM SQL WITH FUNCTION hstore_to_plluau(internal), TO SQL WITH FUNCTION plluau_to_hstore(internal) ); pllua-ng-REL_2_0_4/hstore/hstore_plluau.control000066400000000000000000000002711347047754200217110ustar00rootroot00000000000000# hstore_plluau extension default_version = '1.0' comment = 'Hstore transform for untrusted Lua' module_pathname = '$libdir/hstore_pllua' relocatable = true requires = 'hstore, plluau' pllua-ng-REL_2_0_4/hstore/old_inc/000077500000000000000000000000001347047754200170305ustar00rootroot00000000000000pllua-ng-REL_2_0_4/hstore/old_inc/hstore/000077500000000000000000000000001347047754200203345ustar00rootroot00000000000000pllua-ng-REL_2_0_4/hstore/old_inc/hstore/hstore.h000066400000000000000000000156501347047754200220200ustar00rootroot00000000000000/* * contrib/hstore/hstore.h */ #ifndef __HSTORE_H__ #define __HSTORE_H__ #include "fmgr.h" #include "utils/array.h" /* * HEntry: there is one of these for each key _and_ value in an hstore * * the position offset points to the _end_ so that we can get the length * by subtraction from the previous entry. the ISFIRST flag lets us tell * whether there is a previous entry. */ typedef struct { uint32 entry; } HEntry; #define HENTRY_ISFIRST 0x80000000 #define HENTRY_ISNULL 0x40000000 #define HENTRY_POSMASK 0x3FFFFFFF /* note possible multiple evaluations, also access to prior array element */ #define HSE_ISFIRST(he_) (((he_).entry & HENTRY_ISFIRST) != 0) #define HSE_ISNULL(he_) (((he_).entry & HENTRY_ISNULL) != 0) #define HSE_ENDPOS(he_) ((he_).entry & HENTRY_POSMASK) #define HSE_OFF(he_) (HSE_ISFIRST(he_) ? 0 : HSE_ENDPOS((&(he_))[-1])) #define HSE_LEN(he_) (HSE_ISFIRST(he_) \ ? HSE_ENDPOS(he_) \ : HSE_ENDPOS(he_) - HSE_ENDPOS((&(he_))[-1])) /* * determined by the size of "endpos" (ie HENTRY_POSMASK), though this is a * bit academic since currently varlenas (and hence both the input and the * whole hstore) have the same limit */ #define HSTORE_MAX_KEY_LEN 0x3FFFFFFF #define HSTORE_MAX_VALUE_LEN 0x3FFFFFFF typedef struct { int32 vl_len_; /* varlena header (do not touch directly!) */ uint32 size_; /* flags and number of items in hstore */ /* array of HEntry follows */ } HStore; /* * It's not possible to get more than 2^28 items into an hstore, so we reserve * the top few bits of the size field. See hstore_compat.c for one reason * why. Some bits are left for future use here. MaxAllocSize makes the * practical count limit slightly more than 2^28 / 3, or INT_MAX / 24, the * limit for an hstore full of 4-byte keys and null values. Therefore, we * don't explicitly check the format-imposed limit. */ #define HS_FLAG_NEWVERSION 0x80000000 #define HS_COUNT(hsp_) ((hsp_)->size_ & 0x0FFFFFFF) #define HS_SETCOUNT(hsp_,c_) ((hsp_)->size_ = (c_) | HS_FLAG_NEWVERSION) /* * "x" comes from an existing HS_COUNT() (as discussed, <= INT_MAX/24) or a * Pairs array length (due to MaxAllocSize, <= INT_MAX/40). "lenstr" is no * more than INT_MAX, that extreme case arising in hstore_from_arrays(). * Therefore, this calculation is limited to about INT_MAX / 5 + INT_MAX. */ #define HSHRDSIZE (sizeof(HStore)) #define CALCDATASIZE(x, lenstr) ( (x) * 2 * sizeof(HEntry) + HSHRDSIZE + (lenstr) ) /* note multiple evaluations of x */ #define ARRPTR(x) ( (HEntry*) ( (HStore*)(x) + 1 ) ) #define STRPTR(x) ( (char*)(ARRPTR(x) + HS_COUNT((HStore*)(x)) * 2) ) /* note multiple/non evaluations */ #define HSTORE_KEY(arr_,str_,i_) ((str_) + HSE_OFF((arr_)[2*(i_)])) #define HSTORE_VAL(arr_,str_,i_) ((str_) + HSE_OFF((arr_)[2*(i_)+1])) #define HSTORE_KEYLEN(arr_,i_) (HSE_LEN((arr_)[2*(i_)])) #define HSTORE_VALLEN(arr_,i_) (HSE_LEN((arr_)[2*(i_)+1])) #define HSTORE_VALISNULL(arr_,i_) (HSE_ISNULL((arr_)[2*(i_)+1])) /* * currently, these following macros are the _only_ places that rely * on internal knowledge of HEntry. Everything else should be using * the above macros. Exception: the in-place upgrade in hstore_compat.c * messes with entries directly. */ /* * copy one key/value pair (which must be contiguous starting at * sptr_) into an under-construction hstore; dent_ is an HEntry*, * dbuf_ is the destination's string buffer, dptr_ is the current * position in the destination. lots of modification and multiple * evaluation here. */ #define HS_COPYITEM(dent_,dbuf_,dptr_,sptr_,klen_,vlen_,vnull_) \ do { \ memcpy((dptr_), (sptr_), (klen_)+(vlen_)); \ (dptr_) += (klen_)+(vlen_); \ (dent_)++->entry = ((dptr_) - (dbuf_) - (vlen_)) & HENTRY_POSMASK; \ (dent_)++->entry = ((((dptr_) - (dbuf_)) & HENTRY_POSMASK) \ | ((vnull_) ? HENTRY_ISNULL : 0)); \ } while(0) /* * add one key/item pair, from a Pairs structure, into an * under-construction hstore */ #define HS_ADDITEM(dent_,dbuf_,dptr_,pair_) \ do { \ memcpy((dptr_), (pair_).key, (pair_).keylen); \ (dptr_) += (pair_).keylen; \ (dent_)++->entry = ((dptr_) - (dbuf_)) & HENTRY_POSMASK; \ if ((pair_).isnull) \ (dent_)++->entry = ((((dptr_) - (dbuf_)) & HENTRY_POSMASK) \ | HENTRY_ISNULL); \ else \ { \ memcpy((dptr_), (pair_).val, (pair_).vallen); \ (dptr_) += (pair_).vallen; \ (dent_)++->entry = ((dptr_) - (dbuf_)) & HENTRY_POSMASK; \ } \ } while (0) /* finalize a newly-constructed hstore */ #define HS_FINALIZE(hsp_,count_,buf_,ptr_) \ do { \ int buflen = (ptr_) - (buf_); \ if ((count_)) \ ARRPTR(hsp_)[0].entry |= HENTRY_ISFIRST; \ if ((count_) != HS_COUNT((hsp_))) \ { \ HS_SETCOUNT((hsp_),(count_)); \ memmove(STRPTR(hsp_), (buf_), buflen); \ } \ SET_VARSIZE((hsp_), CALCDATASIZE((count_), buflen)); \ } while (0) /* ensure the varlena size of an existing hstore is correct */ #define HS_FIXSIZE(hsp_,count_) \ do { \ int bl = (count_) ? HSE_ENDPOS(ARRPTR(hsp_)[2*(count_)-1]) : 0; \ SET_VARSIZE((hsp_), CALCDATASIZE((count_),bl)); \ } while (0) /* DatumGetHStoreP includes support for reading old-format hstore values */ extern HStore *hstoreUpgrade(Datum orig); #define DatumGetHStoreP(d) hstoreUpgrade(d) #define PG_GETARG_HS(x) DatumGetHStoreP(PG_GETARG_DATUM(x)) /* * Pairs is a "decompressed" representation of one key/value pair. * The two strings are not necessarily null-terminated. */ typedef struct { char *key; char *val; size_t keylen; size_t vallen; bool isnull; /* value is null? */ bool needfree; /* need to pfree the value? */ } Pairs; extern int hstoreUniquePairs(Pairs *a, int32 l, int32 *buflen); extern HStore *hstorePairs(Pairs *pairs, int32 pcount, int32 buflen); extern size_t hstoreCheckKeyLen(size_t len); extern size_t hstoreCheckValLen(size_t len); extern int hstoreFindKey(HStore *hs, int *lowbound, char *key, int keylen); extern Pairs *hstoreArrayToPairs(ArrayType *a, int *npairs); #define HStoreContainsStrategyNumber 7 #define HStoreExistsStrategyNumber 9 #define HStoreExistsAnyStrategyNumber 10 #define HStoreExistsAllStrategyNumber 11 #define HStoreOldContainsStrategyNumber 13 /* backwards compatibility */ /* * defining HSTORE_POLLUTE_NAMESPACE=0 will prevent use of old function names; * for now, we default to on for the benefit of people restoring old dumps */ #ifndef HSTORE_POLLUTE_NAMESPACE #define HSTORE_POLLUTE_NAMESPACE 1 #endif #if HSTORE_POLLUTE_NAMESPACE #define HSTORE_POLLUTE(newname_,oldname_) \ PG_FUNCTION_INFO_V1(oldname_); \ Datum newname_(PG_FUNCTION_ARGS); \ Datum oldname_(PG_FUNCTION_ARGS) { return newname_(fcinfo); } \ extern int no_such_variable #else #define HSTORE_POLLUTE(newname_,oldname_) \ extern int no_such_variable #endif #endif /* __HSTORE_H__ */ pllua-ng-REL_2_0_4/hstore/sql/000077500000000000000000000000001347047754200162205ustar00rootroot00000000000000pllua-ng-REL_2_0_4/hstore/sql/create_ext.sql000066400000000000000000000001331347047754200210610ustar00rootroot00000000000000-- create extension hstore; create extension pllua; create extension hstore_pllua; --end pllua-ng-REL_2_0_4/hstore/sql/hstore.sql000066400000000000000000000017511347047754200202510ustar00rootroot00000000000000-- \set VERBOSITY terse -- smoke tests do language pllua $$ -- these should work even without the transform local hs = pgtype.hstore('"foo"=>"bar", "baz"=>"quux"') print(hs) local res = (spi.execute([[select pg_typeof($1) as type, $1::text as text, $1->'foo' as foo, $1->'baz' as bar]], hs))[1] print(res.type, res.text, res.foo, res.bar) -- but these use it: res = (spi.execute([[select $1 as hs]], hs))[1] print(type(res.hs)) do local ks = {} for k,v in pairs(res.hs) do ks[1+#ks] = k end table.sort(ks) for i,k in ipairs(ks) do print(k,res.hs[k]) end end local hs2 = pgtype.hstore({ foo = "bar", baz = "quux" }) res = (spi.execute([[select pg_typeof($1) as t2]], hs2))[1] print(res.t2) res = (spi.execute([[select $1 = $2 as eq]], hs, hs2))[1] print(res.eq) $$; -- make sure that non-table types don't crash the transform do language pllua $$ print(pgtype.hstore(123)) $$; do language pllua $$ print(pgtype.hstore(function() end)) $$; --end pllua-ng-REL_2_0_4/parallel_schedule000066400000000000000000000004221347047754200175060ustar00rootroot00000000000000# # this must be first since it installs the extension test: pllua # these should be independent test: pllua_old arrays numerics paths horology rowdatum spi subxact types triggers jsonb trusted # this must run alone because it messes up output from DDL test: event_triggers pllua-ng-REL_2_0_4/pllua.control000066400000000000000000000002371347047754200166360ustar00rootroot00000000000000# pllua extension default_version = '2.0' comment = 'Lua as a procedural language' module_pathname = '$libdir/pllua' relocatable = false schema = 'pg_catalog' pllua-ng-REL_2_0_4/plluau.control000066400000000000000000000002531347047754200170210ustar00rootroot00000000000000# plluau extension default_version = '2.0' comment = 'Lua as an untrusted procedural language' module_pathname = '$libdir/pllua' relocatable = false schema = 'pg_catalog' pllua-ng-REL_2_0_4/scripts/000077500000000000000000000000001347047754200156045ustar00rootroot00000000000000pllua-ng-REL_2_0_4/scripts/pllua--1.0--2.0.sql000066400000000000000000000006151347047754200203670ustar00rootroot00000000000000\echo Use "ALTER EXTENSION pllua UPDATE TO '2.0'" to load this file. \quit -- nothing actually needed here, the version change is cosmetic; -- but we take the opportunity to fix up some dubious properties ALTER FUNCTION pllua_call_handler() VOLATILE CALLED ON NULL INPUT; ALTER FUNCTION pllua_inline_handler(internal) VOLATILE STRICT; ALTER FUNCTION pllua_validator(oid) VOLATILE STRICT; --end pllua-ng-REL_2_0_4/scripts/pllua--2.0.sql000066400000000000000000000010441347047754200200130ustar00rootroot00000000000000\echo Use "CREATE EXTENSION pllua" to load this file. \quit CREATE FUNCTION pllua_call_handler() RETURNS language_handler AS 'MODULE_PATHNAME', 'pllua_call_handler' LANGUAGE C; CREATE FUNCTION pllua_inline_handler(internal) RETURNS VOID AS 'MODULE_PATHNAME', 'pllua_inline_handler' LANGUAGE C STRICT; CREATE FUNCTION pllua_validator(oid) RETURNS VOID AS 'MODULE_PATHNAME', 'pllua_validator' LANGUAGE C STRICT; CREATE TRUSTED LANGUAGE pllua HANDLER pllua_call_handler INLINE pllua_inline_handler VALIDATOR pllua_validator; -- pllua-ng-REL_2_0_4/scripts/plluau--1.0--2.0.sql000066400000000000000000000006201347047754200205500ustar00rootroot00000000000000\echo Use "ALTER EXTENSION plluau UPDATE TO '2.0'" to load this file. \quit -- nothing actually needed here, the version change is cosmetic -- but we take the opportunity to fix up some dubious properties ALTER FUNCTION plluau_call_handler() VOLATILE CALLED ON NULL INPUT; ALTER FUNCTION plluau_inline_handler(internal) VOLATILE STRICT; ALTER FUNCTION plluau_validator(oid) VOLATILE STRICT; --end pllua-ng-REL_2_0_4/scripts/plluau--2.0.sql000066400000000000000000000010471347047754200202030ustar00rootroot00000000000000\echo Use "CREATE EXTENSION plluau" to load this file. \quit CREATE FUNCTION plluau_call_handler() RETURNS language_handler AS 'MODULE_PATHNAME', 'plluau_call_handler' LANGUAGE C; CREATE FUNCTION plluau_inline_handler(internal) RETURNS VOID AS 'MODULE_PATHNAME', 'plluau_inline_handler' LANGUAGE C STRICT; CREATE FUNCTION plluau_validator(oid) RETURNS VOID AS 'MODULE_PATHNAME', 'plluau_validator' LANGUAGE C STRICT; CREATE LANGUAGE plluau HANDLER plluau_call_handler INLINE plluau_inline_handler VALIDATOR plluau_validator; -- pllua-ng-REL_2_0_4/serial_schedule000066400000000000000000000003061347047754200171720ustar00rootroot00000000000000# test: pllua test: pllua_old test: trusted test: arrays test: jsonb test: numerics test: horology test: paths test: rowdatum test: spi test: subxact test: types test: triggers test: event_triggers pllua-ng-REL_2_0_4/sql/000077500000000000000000000000001347047754200147145ustar00rootroot00000000000000pllua-ng-REL_2_0_4/sql/arrays.sql000066400000000000000000000127411347047754200167430ustar00rootroot00000000000000-- \set VERBOSITY terse -- -- -- By having a real table with both short and long array values, -- we get to involve both short varlenas and external toast. -- create temp table adata (id serial, a integer[], b text[], c numeric[], d date[]); insert into adata(a) values (array[1,2]), (array[10,20,30,40,50]), (array(select i from generate_series(1,100) i)), (array(select i from generate_series(1,100000) i)), ('{}'); insert into adata(b) values ('{}'), (array['foo','bar','baz']), (array(select 'val'||i from generate_series(1,100) i)), (array(select 'val'||i from generate_series(1,10000) i)); insert into adata(c) values ('{}'), (array[1.234,exp(1.0::numeric(32,30)),27!]), (array(select i from generate_series(1,100) i)), (array(select i from generate_series(1,10000) i)); insert into adata(d) values ('{}'), (array[date '2017-12-11', '1968-05-10', '1983-09-26', '1962-10-27']), (array(select date '2017-01-01' + i from generate_series(0,364,18) i)); do language pllua $$ package.preload['myutil'] = function() local expect_next = { string = function(s) return string.gsub(s, "%d+$", function(n) return string.format("%d", n + 1) end) end, number = function(n) return n + 1 end, [pgtype.numeric] = function(n) return n + 1 end, } local function map(a,f) local r = {} for i = 1,#a do r[#r+1] = f(a[i]) end return r end local function summarize(a) if a == nil then return nil end local expect,first_match,result = nil,nil,{} for i = 1,#a do if first_match == nil then expect,first_match = a[i],i elseif a[i] ~= expect then if first_match < i-1 then result[#result+1] = { a[first_match], a[i-1] } else result[#result+1] = a[i-1] end expect,first_match = a[i],i end --[[ update the "expected" next element ]] expect = (expect_next[pgtype(expect) or type(expect)] or function(x) return x end)(expect) end if first_match == #a then result[#result+1] = a[#a] elseif first_match ~= nil then result[#result+1] = { a[first_match], a[#a] } end return table.concat(map(result, function(e) if type(e)=='table' then return string.format("[%s..%s]", tostring(e[1]), tostring(e[2])) else return tostring(e) end end), ',') end return { map = map, summarize = summarize } end $$; do language pllua $$ local u = require 'myutil' for r in spi.rows([[ select a, b, array_append(a, -1) as xa, array_append(b, 'wombat') as xb from adata where a is not null or b is not null order by id ]]) do print(u.summarize(r.a),u.summarize(r.b)) print(u.summarize(r.xa),u.summarize(r.xb)) end for r in spi.rows([[ select c, array_append(c, -1.0) as xc from adata where c is not null order by id ]]) do print(u.summarize(r.c)) print(u.summarize(r.xc)) end for r in spi.rows([[ select d from adata where d is not null order by id ]]) do print(r.d) end $$; create function af1(a anyarray) returns text language pllua stable as $$ return tostring(u.summarize(a)) end u = require 'myutil' do $$; -- array_append returns its result as an expanded datum select af1(a) from adata where a is not null order by id; with t as (select a from adata where a is not null order by id) select af1(array_append(a, -1)) from t; select af1(b) from adata where b is not null order by id; with t as (select b from adata where b is not null order by id) select af1(array_append(b, 'wombat')) from t; select af1(c) from adata where c is not null order by id; with t as (select c from adata where c is not null order by id) select af1(array_append(c, -1.0)) from t; select af1(d) from adata where d is not null order by id; with t as (select d from adata where d is not null order by id) select af1(array_append(d, date '1970-01-01')) from t; -- conversion edge cases create function pg_temp.af2() returns integer[] language pllua as $$ return nil $$; select pg_temp.af2(); create function pg_temp.af3() returns integer[] language pllua as $$ return $$; select pg_temp.af3(); create function pg_temp.af4() returns integer[] language pllua as $$ return 1,2 $$; select pg_temp.af4(); create function pg_temp.af5() returns integer[] language pllua as $$ return pgtype(nil,0)() $$; select pg_temp.af5(); create function pg_temp.af5b() returns integer[] language pllua as $$ return {} $$; select pg_temp.af5b(); create function pg_temp.af6() returns integer[] language pllua as $$ return { 1, nil, 3 } $$; select pg_temp.af6(); create function pg_temp.af7() returns integer[] language pllua as $$ return pgtype.integer(1) $$; select pg_temp.af7(); create function pg_temp.af8() returns integer[] language pllua as $$ return { pgtype.integer(1) } $$; select pg_temp.af8(); create type acomp1 as (foo integer, bar text); create function pg_temp.af9() returns acomp1[] language pllua as $$ return { { foo = 1, bar = "zot" } } $$; select pg_temp.af9(); create function pg_temp.af10() returns acomp1[] language pllua as $$ return { pgtype(nil,0):element()(1, "zot") } $$; select pg_temp.af10(); -- pllua-ng-REL_2_0_4/sql/event_triggers.sql000066400000000000000000000012251347047754200204640ustar00rootroot00000000000000-- \set VERBOSITY terse \set QUIET 0 -- Test event triggers. create function evtrig() returns event_trigger language pllua as $$ print(trigger.event, trigger.tag) $$; create event trigger et1 on ddl_command_start execute procedure evtrig(); create event trigger et2 on ddl_command_end execute procedure evtrig(); create event trigger et3 on sql_drop execute procedure evtrig(); create event trigger et4 on table_rewrite execute procedure evtrig(); create table evt1 (a text); alter table evt1 alter column a type integer using null; drop table evt1; drop event trigger et1; drop event trigger et2; drop event trigger et3; drop event trigger et4; --end pllua-ng-REL_2_0_4/sql/horology.sql000066400000000000000000000335741347047754200173130ustar00rootroot00000000000000-- \set VERBOSITY terse -- -- 1. Input of datetime values from table form. set timezone = 'GMT'; set datestyle = 'ISO,YMD'; do language pllua $$ print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=2419, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=1919, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=1819, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=1019, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=-2019, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=-4713, month=12, day=1, hour=12, min=23, sec=34.1 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34, usec=123456 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, msec=1, usec=1 }); $$; do language pllua $$ print(pgtype.timestamptz{ epoch=0 }); print(pgtype.timestamptz{ epoch=1555891200 }); print(pgtype.timestamptz{ epoch=1555891200.000001 }); print(pgtype.timestamptz{ epoch_msec=1555891200001 }); print(pgtype.timestamptz{ epoch_usec=1555891200000001 }); print(pgtype.timestamptz{ epoch=1555891200, msec=1, usec=1 }); print(pgtype.timestamptz{ epoch=1555891200, msec=-1, usec=1 }); print(pgtype.timestamptz{ epoch=-1555891200 }); print(pgtype.timestamptz{ epoch=-1555891200, msec=1, usec=-1 }); print(pgtype.timestamptz{ epoch=-1555891200, msec=-1, usec=1 }); print(pgtype.timestamptz{ epoch_msec=-1555891200001 }); print(pgtype.timestamptz{ epoch_usec=-1555891200000001 }); $$; do language pllua $$ print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone=10800 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone=-10800 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="+0300" }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="-0300" }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="+1400" }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="-1400" }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="America/Los_Angeles" }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="Pacific/Auckland" }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, timezone="Asia/Kathmandu" }); print(pgtype.timestamptz{ year=2018, month=10, day=28, hour=1, min=30, sec=0, timezone="Europe/London" }); print(pgtype.timestamptz{ year=2018, month=10, day=28, hour=1, min=30, sec=0, isdst=true, timezone="Europe/London" }); print(pgtype.timestamptz{ year=2018, month=10, day=28, hour=1, min=30, sec=0, isdst=false, timezone="Europe/London" }); $$; do language pllua $$ print(pgtype.timestamptz{ epoch=1/0 }); print(pgtype.timestamptz{ epoch=-1/0 }); $$; do language pllua $$ print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=1, usec=59000000 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=-1 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=-60 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=-61 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=60 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=61 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=23, sec=120 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=0, sec=3661 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=0, sec=43201 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=0, sec=-43201 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=0, sec=864000 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=12, min=720, sec=3661 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=24, min=0, sec=0 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=24, min=0, sec=1 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=240, min=0, sec=1 }); print(pgtype.timestamptz{ year=2019, month=4, day=22, hour=-1, min=0, sec=1 }); print(pgtype.timestamptz{ year=2019, month=-4, day=22, hour=12, min=23, sec=34 }); print(pgtype.timestamptz{ year=2019, month=16, day=22, hour=12, min=23, sec=34 }); $$; do language pllua $$ print(pgtype.timestamp{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=2419, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=1919, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=1819, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=1019, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=-2019, month=4, day=22, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=-4713, month=12, day=1, hour=12, min=23, sec=34.1 }); print(pgtype.timestamp{ year=2019, month=4, day=22, hour=12, min=23, sec=34, usec=123456 }); print(pgtype.timestamp{ year=2019, month=4, day=22, hour=12, min=23, sec=34.1, msec=1, usec=1 }); $$; do language pllua $$ print(pgtype.timestamp{ epoch=0 }); print(pgtype.timestamp{ epoch=1555891200 }); print(pgtype.timestamp{ epoch=1555891200.000001 }); print(pgtype.timestamp{ epoch_msec=1555891200001 }); print(pgtype.timestamp{ epoch_usec=1555891200000001 }); print(pgtype.timestamp{ epoch=1555891200, msec=1, usec=1 }); print(pgtype.timestamp{ epoch=1555891200, msec=-1, usec=1 }); print(pgtype.timestamp{ epoch=-1555891200 }); print(pgtype.timestamp{ epoch=-1555891200, msec=1, usec=-1 }); print(pgtype.timestamp{ epoch=-1555891200, msec=-1, usec=1 }); print(pgtype.timestamp{ epoch_msec=-1555891200001 }); print(pgtype.timestamp{ epoch_usec=-1555891200000001 }); print(pgtype.timestamp{ epoch=1555891200, timezone="America/Los_Angeles" }); print(pgtype.timestamp{ epoch=1555891200, timezone="Pacific/Auckland" }); print(pgtype.timestamp{ epoch=1555891200, timezone="Asia/Kathmandu" }); $$; do language pllua $$ print(pgtype.date{ year=2019, month=4, day=22 }); print(pgtype.date{ year=2419, month=2, day=22 }); print(pgtype.date{ year=1919, month=1, day=22 }); print(pgtype.date{ year=1819, month=3, day=22 }); print(pgtype.date{ year=1019, month=5, day=22 }); print(pgtype.date{ year=-2019, month=4, day=22 }); print(pgtype.date{ year=-4713, month=12, day=1 }); print(pgtype.date{ year=2019, month=4, day=22, hour=24 }); print(pgtype.date{ year=2019, month=4, day=22, hour=24, min=1 }); $$; do language pllua $$ print(pgtype.date{ epoch=0 }); print(pgtype.date{ epoch=1555891200 }); print(pgtype.date{ epoch=1555891200.000001 }); print(pgtype.date{ epoch_msec=1555891200001 }); print(pgtype.date{ epoch_usec=1555891200000001 }); print(pgtype.date{ epoch=1555891200 }); print(pgtype.date{ epoch=-1555891200 }); print(pgtype.date{ epoch_msec=-1555891200001 }); print(pgtype.date{ epoch_usec=-1555891200000001 }); print(pgtype.date{ epoch=1555891200, timezone="America/Los_Angeles" }); print(pgtype.date{ epoch=1555891200, timezone="Pacific/Auckland" }); print(pgtype.date{ epoch=1555891200, timezone="Asia/Kathmandu" }); $$; do language pllua $$ print(pgtype.time{ hour=12, min=23, sec=34.1 }); print(pgtype.time{ hour=12, min=120, sec=1 }); print(pgtype.time{ hour=24, min=23, sec=34.1 }); print(pgtype.time{ hour=25, min=23, sec=34.1 }); print(pgtype.time{ hour=12, min=23, sec=34, usec=123456 }); print(pgtype.time{ hour=12, min=23, sec=34.1, msec=1, usec=1 }); $$; do language pllua $$ print(pgtype.time{ epoch=0 }); print(pgtype.time{ epoch=3601 }); print(pgtype.time{ epoch=86400 }); print(pgtype.time{ epoch=1555891200 }); $$; do language pllua $$ print(pgtype.timetz{ hour=12, min=23, sec=34.1, timezone=7200 }); $$; do language pllua $$ print(pgtype.timetz{ epoch=0, timezone=3600 }); print(pgtype.timetz{ epoch=3601, timezone=-3600 }); print(pgtype.timetz{ epoch=86400, timezone=-43200 }); print(pgtype.timetz{ epoch=1555891200, timezone=0 }); $$; do language pllua $$ print(pgtype.interval{ year=0, month=0, day=0, hour=0, min=0, sec=0, usec=0 }); print(pgtype.interval{ year=100 }); print(pgtype.interval{ month=100 }); print(pgtype.interval{ day=100 }); print(pgtype.interval{ hour=100 }); print(pgtype.interval{ min=100 }); print(pgtype.interval{ sec=100 }); print(pgtype.interval{ usec=1 }); print(pgtype.interval{ year=1, month=2, day=3, hour=4, min=5, sec=6, usec=7 }); $$; do language pllua $$ print(pgtype.interval{ epoch=0 }); print(pgtype.interval{ epoch=120 }); print(pgtype.interval{ epoch=43200 }); print(pgtype.interval{ epoch=86400 }); $$; -- input error cases. do language pllua $$ print(pgtype.interval{ hour="foo" }); $$; do language pllua $$ print(pgtype.interval{ hour=1.2 }); $$; do language pllua $$ print(pgtype.interval{ hour=0/0 }); $$; do language pllua $$ print(pgtype.interval{ hour=1/0 }); $$; do language pllua $$ print(pgtype.interval{ sec=1/0 }); $$; do language pllua $$ print(pgtype.interval{ usec=1/0 }); $$; do language pllua $$ print(pgtype.timestamptz{ year=1/0, month=1, day=-1/0 }); $$; do language pllua $$ print(pgtype.timestamp{ epoch=0, timezone="1234" }); $$; do language pllua $$ print(pgtype.timestamp{ epoch=0, timezone=1234.5 }); $$; do language pllua $$ print(pgtype.timestamp{ epoch=0, timezone=function() end }); $$; do language pllua $$ print(pgtype.timestamptz{ epoch=0, epoch_msec=0 }); $$; do language pllua $$ print(pgtype.timestamptz{ epoch=0, year=2019 }); $$; do language pllua $$ print(pgtype.timestamptz{ epoch=0, hour=20 }); $$; do language pllua $$ print(pgtype.timestamptz{ year=2019 }); $$; do language pllua $$ print(pgtype.timestamptz{ year=2019, month=4 }); $$; do language pllua $$ print(pgtype.time{ min=10 }); $$; do language pllua $$ print(pgtype.time{ hour=20, sec=10 }); $$; do language pllua $$ print(pgtype.timetz{ hour=20, timezone="America/Los_Angeles" }); $$; do language pllua $$ print(pgtype.time{ hour=20, timezone="America/Los_Angeles" }); $$; do language pllua $$ print(pgtype.date{ year=2019, month=4, day=22, timezone="America/Los_Angeles" }); $$; do language pllua $$ print(pgtype.timestamp{ year=2019, month=4, day=22, hour=20, timezone="America/Los_Angeles" }); $$; do language pllua $$ print(pgtype.timestamptz{ epoch=-300000000000 }); $$; do language pllua $$ print(pgtype.timestamptz{ year=-5000, month=1, day=1 }); $$; do language pllua $$ print(pgtype.date{ year=-5000, month=1, day=1 }); $$; -- 2. output of values in table form. do language pllua $$ local function prt(t,z) print(t) local rt = t:as_table(z) local o = {} for k,_ in pairs(rt) do o[1+#o] = k end table.sort(o) for _,k in ipairs(o) do print(k,rt[k]) end end prt(pgtype.timestamptz('2019-04-22 10:20:30+00')) prt(pgtype.timestamptz('2019-04-22 10:20:30+00'),'America/Los_Angeles') prt(pgtype.timestamptz('2019-04-22 10:20:30+00'),'Asia/Kathmandu') prt(pgtype.timestamp('2019-04-22 10:20:30+00')) prt(pgtype.date('2019-04-22')) prt(pgtype.time('10:20:30')) prt(pgtype.timetz('10:20:30+04')) prt(pgtype.interval('P1Y2M3DT4H5M6S')) $$; -- error do language pllua $$ print(pgtype.timestamp('2019-04-22 10:20:30+00'):as_table('Europe/London')) $$; -- 3. Field access -- note, fields come out in session timezone, so set that: set timezone = 'Europe/London'; do language pllua $$ local t = pgtype.timestamptz('1968-05-10 03:45:01.234567+01') for _,k in ipairs{ 'century', 'day', 'decade', 'dow', 'doy', 'epoch', 'epoch_msec', 'epoch_usec', 'hour', 'isodow', 'isoweek', 'isoyear', 'julian', 'microseconds', 'millennium', 'milliseconds', 'minute', 'month', 'quarter', 'second', 'timezone', 'timezone_hour', 'timezone_minute', 'week', 'year' } do print(k, t[k]) end $$; set timezone = 'UTC'; do language pllua $$ local t = pgtype.timestamp('1968-05-10 03:45:01.234567') for _,k in ipairs{ 'century', 'day', 'decade', 'dow', 'doy', 'epoch', 'epoch_msec', 'epoch_usec', 'hour', 'isodow', 'isoweek', 'isoyear', 'julian', 'microseconds', 'millennium', 'milliseconds', 'minute', 'month', 'quarter', 'second', 'week', 'year' } do print(k, t[k]) end $$; do language pllua $$ local t = pgtype.date('1968-05-10') for _,k in ipairs{ 'century', 'day', 'decade', 'dow', 'doy', 'epoch', 'epoch_msec', 'epoch_usec', 'hour', 'isodow', 'isoweek', 'isoyear', 'julian', 'microseconds', 'millennium', 'milliseconds', 'minute', 'month', 'quarter', 'second', 'week', 'year' } do print(k, string.format("%.18g",t[k])) end $$; do language pllua $$ local t = pgtype.time('03:45:01.234567') for _,k in ipairs{ 'epoch', 'epoch_msec', 'epoch_usec', 'hour', 'microseconds', 'milliseconds', 'minute', 'second' } do print(k, t[k]) end $$; do language pllua $$ local t = pgtype.timetz('03:45:01.234567+01') for _,k in ipairs{ 'epoch', 'epoch_msec', 'epoch_usec', 'hour', 'microseconds', 'milliseconds', 'minute', 'second', 'timezone', 'timezone_hour', 'timezone_minute', } do print(k, t[k]) end $$; do language pllua $$ local t = pgtype.interval('P1Y2M3DT4H5M6.789001S') for _,k in ipairs{ 'century', 'day', 'decade', 'epoch', 'epoch_msec', 'epoch_usec', 'hour', 'microseconds', 'millennium', 'milliseconds', 'minute', 'month', 'quarter', 'second', 'year' } do print(k, t[k]) end $$; -- errors (not worth testing many combinations, they all share a code path) do language pllua $$ print(pgtype.time('03:45:01.234567').dow) $$; --end pllua-ng-REL_2_0_4/sql/jsonb.sql000066400000000000000000000061261347047754200165550ustar00rootroot00000000000000-- \set VERBOSITY terse -- do language pllua $$ a = { json_test = "This is only a test.", foo = "If this were real data, it would make more sense.", piem = [[ Now I, even I, would celebrate In rhymes unapt the great Immortal Syracusan rivaled nevermore, Who in his wondrous lore, Passed on before, Left men his guidance How to circles mensurate. ]], names = { "Dougal", "Florence", "Ermintrude", "Zebedee", "Brian", "Dylan" }, mixed = { nil, nil, 123, "foo", nil, true, false, [23] = "fred" }, empty = {}, empty2 = {{},{}}, nested = { "arrayelem", { ["object key"] = "object val", subobject = { subarray = { { { 123 } } } } } } } b = pgtype.jsonb(a) print(b) c = pgtype.jsonb(a, { null = false }) print(c) d = pgtype.jsonb(a, { map = function(v) if type(v) == "boolean" then v = tostring(v) end return v end }) print(d) e = pgtype.jsonb(a, { array_thresh = 1 }) print(e) f = pgtype.jsonb(a, { empty_object = true }) print(f) spi.execute([[ create temp table jt1 as select $1 as a ]], b) $$; select a, pg_typeof(a) from jt1; create temp table jt2(id serial, a jsonb); insert into jt2(a) values ('1'); insert into jt2(a) values ('"foo"'); insert into jt2(a) values ('true'); insert into jt2(a) values ('null'); insert into jt2(a) values ('{"foo":123}'); insert into jt2(a) values ('{"foo":null}'); insert into jt2(a) values ('[10,20,30]'); insert into jt2(a) values ('{"foo":[2,4,6]}'); insert into jt2(a) values ('[{"foo":"bar"},{"baz":"foo"},123,null]'); -- check objects with keys that look like numbers insert into jt2(a) values ('{"1":"foo", "2":[false,true], "foo":{}}'); insert into jt2(a) values ('{"1":"foo", "2":[false,true]}'); do language pllua $$ s = spi.prepare([[ select a from jt2 order by id ]]) for r in s:rows() do print(r.a) b = r.a(function(k,v,...) if type(v)~="table" then print("mapfunc",type(k),k,v,...) else print("mapfunc",type(k),k,type(v),...) end return k,v end) print(type(b)) end $$; create temp table jt3(id integer, a jsonb); -- first row should be plain, then a couple with compressed values, -- then a couple with external toast insert into jt3 select i, ('[' || repeat('"foo",',10*(10^i)::integer) || i || ']')::jsonb from generate_series(1,5) i; do language pllua $$ s = spi.prepare([[ select a from jt3 where id = $1 ]]) for i = 1,5 do local r = (s:execute(i))[1] local a = r.a() print(#a,a[#a]) end $$; -- test jsonb in jsonb and similar paths do language pllua $$ local jtst1 = pgtype.jsonb('"foo"') -- json scalar local jtst2 = pgtype.jsonb('{"foo":true,"bar":[1,2,false]}') -- json container local ts1 = pgtype.timestamp('2017-12-19 12:00:00') print(pgtype.jsonb({ v1 = jtst1, v2 = jtst2, v3 = ts1 })) $$; -- test round-trip conversions do language pllua $$ local j_in = pgtype.jsonb('{"foo":[1,null,false,{"a":null,"b":[]},{},[]]}') local nvl = {} local val = j_in{ null = nvl } local j_out = pgtype.jsonb(val, { null = nvl }) print(j_in) print(j_out) $$; --end pllua-ng-REL_2_0_4/sql/numerics.sql000066400000000000000000000056601347047754200172710ustar00rootroot00000000000000-- \set VERBOSITY terse -- test numerics create function lua_numexec(code text, n1 numeric, n2 numeric) returns text language pllua as $$ local f,e = load("return function(n1,n2) return "..code.." end", code, "t", self) assert(f,e) f = f() assert(f) return tostring(f(n1,n2)) end do num = require "pllua.numeric" $$; create function pg_numexec(code text, n1 numeric, n2 numeric) returns text language plpgsql as $$ declare r text; begin execute format('select (%s)::text', regexp_replace(regexp_replace(code, '\mnum\.', '', 'g'), '\mn([0-9])', '$\1', 'g')) into r using n1,n2; return r; end; $$; with t as (select code, lua_numexec(code, 5439.123456, -1.9) as lua, pg_numexec(code, 5439.123456, -1.9) as pg from unnest(array[ $$ n1 + n2 $$, $$ n1 - n2 $$, $$ n1 * n2 $$, $$ n1 / n2 $$, $$ n1 % n2 $$, $$ n1 ^ n2 $$, $$ (-n1) + n2 $$, $$ (-n1) - n2 $$, $$ (-n1) * n2 $$, $$ (-n1) / n2 $$, $$ (-n1) % n2 $$, $$ (-n1) ^ 3 $$, $$ (-n1) + (-n2) $$, $$ (-n1) - (-n2) $$, $$ (-n1) * (-n2) $$, $$ (-n1) / (-n2) $$, $$ (-n1) % (-n2) $$, $$ (-n1) ^ (-3) $$, $$ (n1) > (n2) $$, $$ (n1) < (n2) $$, $$ (n1) >= (n2) $$, $$ (n1) <= (n2) $$, $$ (n1) > (n2*10000) $$, $$ (n1) < (n2*10000) $$, $$ (n1) >= (n2 * -10000) $$, $$ (n1) <= (n2 * -10000) $$, $$ num.round(n1) $$, $$ num.round(n2) $$, $$ num.round(n1,4) $$, $$ num.round(n1,-1) $$, $$ num.trunc(n1) $$, $$ num.trunc(n2) $$, $$ num.trunc(n1,4) $$, $$ num.trunc(n1,-1) $$, $$ num.floor(n1) $$, $$ num.floor(n2) $$, $$ num.ceil(n1) $$, $$ num.ceil(n2) $$, $$ num.abs(n1) $$, $$ num.abs(n2) $$, $$ num.sign(n1) $$, $$ num.sign(n2) $$, $$ num.sqrt(n1) $$, $$ num.exp(12.3) $$, $$ num.exp(n2) $$ ]) as u(code)) select (lua = pg) as ok, * from t; -- calculate pi to 40 places do language pllua $$ -- Chudnovsky formula; ~14 digits per round, we use 4 rounds local num = require 'pllua.numeric' local prec = 100 -- precision of intermediate values local function fact(n) local r = pgtype.numeric(1):round(prec) for i = 2,n do r = r * i end return r:round(prec) end local c640320 = pgtype.numeric(640320):round(prec) local c13591409 = pgtype.numeric(13591409):round(prec) local c545140134 = pgtype.numeric(545140134):round(prec) local function chn(k) return (fact(6*k) * (c13591409 + (c545140134 * k))) / (fact(3*k) * fact(k)^3 * (-c640320)^(3*k)) end local function pi() return (1 / ((chn(0) + chn(1) + chn(2) + chn(3))*12 / num.sqrt(c640320^3))):round(40) end print(pi()) $$; -- check sanity of maxinteger/mininteger do language pllua $$ local num = require 'pllua.numeric' local maxi = num.maxinteger local mini = num.mininteger print(type(num.tointeger(maxi)), type(num.tointeger(maxi+1))) print(type(num.tointeger(mini)), type(num.tointeger(mini-1))) $$ --end pllua-ng-REL_2_0_4/sql/paths.sql000066400000000000000000000012111347047754200165470ustar00rootroot00000000000000-- \set VERBOSITY terse -- create function pg_temp.tmp1(n text) returns text language plluau immutable strict as $$ return (require "pllua.paths")[n]() $$; -- some of the dirs might not actually exist, so we test only the -- important ones. We can't actually test that the dir exists or what -- the contents are, since many pg versions reject pg_stat_file on -- absolute paths; so just check that we got some string that looks -- like a path. select u.n, f.path ~ '^(?:[[:alpha:]]:)?/' from unnest(array['bin','lib','libdir','pkglib','share']) with ordinality as u(n,ord), pg_temp.tmp1(u.n) f(path) order by u.ord; --end pllua-ng-REL_2_0_4/sql/pllua.sql000066400000000000000000000152221347047754200165540ustar00rootroot00000000000000-- CREATE EXTENSION pllua; CREATE EXTENSION plluau; \set VERBOSITY terse -- smoke test do language pllua $$ print "hello world!" $$; do language plluau $$ print "hello world!" $$; create function pg_temp.f1() returns text language pllua as $$ return "hello world" $$; select pg_temp.f1(); create function pg_temp.f2() returns text language plluau as $$ return "hello world" $$; select pg_temp.f2(); -- Rest of this file concentrates on simple tests of code paths in -- compile, exec, and interpreter setup. Tests of other parts of the -- module are separate. -- validator create function pg_temp."bad name"() returns text language pllua as $$ $$; create function pg_temp.f3("bad arg" integer) returns text language pllua as $$ $$; -- simple params and results (see types.sql for detailed checks) create function pg_temp.f4(a integer) returns integer language pllua as $$ return a + 1 $$; select pg_temp.f4(1); create function pg_temp.f5(a text) returns text language pllua as $$ return a.."bar" $$; select pg_temp.f5('foo'); create function pg_temp.f6(a text, b integer) returns text language pllua as $$ return a..b $$; select pg_temp.f6('foo',1); -- try some polymorphism too create function pg_temp.f7(a anyelement) returns anyelement language pllua as $$ return a $$; select pg_temp.f7(text 'foo'); select pg_temp.f7(json '{"foo":1}'); --select pg_temp.f7(xml 'bar'); -- don't bother with this, might be compiled out select pg_temp.f7(varchar 'foo'); select 'x',pg_temp.f7('foo'::char(20)),'x'; select pg_temp.f7(cstring 'foo'); select pg_temp.f7(name 'foo'); select pg_temp.f7(bytea 'foo\000bar'); select pg_temp.f7(smallint '2'); select pg_temp.f7(integer '2'); select pg_temp.f7(bigint '123456789012345'); select pg_temp.f7(oid '10'); select pg_temp.f7(oid '4294967295'); select pg_temp.f7(true); select pg_temp.f7(false); select pg_temp.f7(1.5::float8); select pg_temp.f7(1.5::float4); -- variadics create function pg_temp.f8(a text, variadic b integer[]) returns void language pllua as $$ print(a,type(b),b) $$; select pg_temp.f8('foo', 1, 2, 3); create function pg_temp.f9(a integer, variadic b text[]) returns void language pllua as $$ print(a,type(b),b) $$; select pg_temp.f9(1, 'foo', 'bar', 'baz'); create function pg_temp.f10(a integer, variadic "any") returns void language pllua as $$ print(a,...) $$; select pg_temp.f10(1, 'foo', 2, 'baz'); -- SRF code paths create function pg_temp.f11(a integer) returns setof text language pllua as $$ return $$; -- 0 rows select * from pg_temp.f11(1); create function pg_temp.f11b(a integer) returns setof text language pllua as $$ return 'foo' $$; -- 1 row select * from pg_temp.f11b(1); create function pg_temp.f12(a integer) returns setof text language pllua as $$ coroutine.yield() $$; -- 1 row, null select * from pg_temp.f12(1); create function pg_temp.f13(a integer) returns setof text language pllua as $$ for i = 1,a do coroutine.yield("row "..i) end $$; select * from pg_temp.f13(4); create function pg_temp.f14(a integer, out x text, out y integer) returns setof record language pllua as $$ for i = 1,a do coroutine.yield("row "..i, i) end $$; select * from pg_temp.f14(4); create function pg_temp.f15(a integer) returns table(x text, y integer) language pllua as $$ for i = 1,a do coroutine.yield("row "..i, i) end $$; select * from pg_temp.f15(4); create function pg_temp.f16(a inout integer, x out text) returns setof record language pllua as $$ for i = 1,a do coroutine.yield(i, "row "..i) end $$; select * from pg_temp.f16(4); -- SRF vs null returns create function pg_temp.f16b(a integer) returns table(x text, y integer) language pllua as $$ coroutine.yield() $$; -- 1 row, null select * from pg_temp.f16b(1); create function pg_temp.f16c(a integer) returns table(x text, y integer) language pllua as $$ coroutine.yield() for i = 1,a do coroutine.yield('foo',i) end $$; select * from pg_temp.f16c(3); -- compiler and validator code paths do language pllua $$ _G.rdepth = 40 $$; -- global var hack -- This function will try and call itself at a point where it is visible -- but has no definition interned yet; the recursive call will likewise -- not see an interned definition and recurses again. without any limits -- this would hit a stack depth check somewhere; we eat about 3 levels of -- C function recursion inside lua each time, and that gets capped at 200. -- We don't expect this to be actually useful, the test is just that we -- don't crash. create function pg_temp.f17(a integer) returns integer language pllua as $$ return a end do if _G.rdepth > 0 then _G.rdepth = _G.rdepth - 1 u = spi.execute("select pg_temp.f17(1)") end $$; select pg_temp.f17(1); create type pg_temp.t1 as (a integer, b text); create function pg_temp.f18(a integer, b text) returns pg_temp.t1 language pllua as $$ return a,b $$; select * from pg_temp.f18(123,'foo'); create function pg_temp.f19(a integer) returns text language pllua as $$ return 'foo '..a $$; select pg_temp.f19(2); create or replace function pg_temp.f19(a integer) returns text language pllua as $$ return 'bar '..a $$; select pg_temp.f19(3); -- trusted interpreter setup -- check we really do have different interpreters -- this is hard because we intentionally isolate trusted-language code -- from the normal global env of its interpreter, so we would only be -- able to verify isolation if we were able to break out of the -- sandbox, which would rather defeat the point. We have to take the -- outside view, by generating an interpreter-dependent value and -- checking that it differs. The stringification of a closure, such as -- server.error, suffices since this contains an interpreter-dependent -- address (whereas base C functions do not differ between -- interpreters in recent lua versions). create function pg_temp.f20() returns text language pllua as $$ return tostring(spi.error) $$; create function pg_temp.f21() returns text language plluau as $$ return tostring(spi.error) $$; select pg_temp.f20() as a intersect select pg_temp.f21(); -- should be empty -- check the global table do language pllua $$ local gk = { "io", "dofile", "debug" } -- must not exist for i = 1,#gk do print(gk[i],type(_G[gk[i]])) end $$; do language plluau $$ local gk = { "io", "dofile" } -- probably exist for i = 1,#gk do print(gk[i],type(_G[gk[i]])) end $$; -- check that trusted gets only the restricted os module, even from -- require do language pllua $$ local os = require 'os' local gk = { "time", "difftime", "execute", "getenv", "exit" } for i = 1,#gk do print(gk[i],type(os[gk[i]])) end $$; -- check that trusted can't require dangerous core modules do language pllua $$ print((lpcall(require,"debug"))) print((lpcall(require,"io"))) $$; --end pllua-ng-REL_2_0_4/sql/pllua_old.sql000066400000000000000000000243421347047754200174150ustar00rootroot00000000000000-- \set VERBOSITY terse set pllua.on_common_init = 'require "pllua.compat"'; -- tests taken from old pllua -- 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'); SET client_min_messages = warning; CREATE TABLE tree (id INT PRIMARY KEY, lchild INT, rchild INT); RESET client_min_messages; CREATE FUNCTION filltree (t text, n int) RETURNS void AS $$ local p = server.prepare("insert into " .. t .. " values($1, $2, $3)", {"int4", "int4", "int4"}) for i = 1, n do local lchild, rchild = 2 * i, 2 * i + 1 -- siblings p:execute{i, lchild, rchild} -- insert values end $$ LANGUAGE pllua; SELECT filltree('tree', 10); CREATE FUNCTION preorder (t text, s int) RETURNS SETOF int AS $$ coroutine.yield(s) local q = server.execute("select * from " .. t .. " where id=" .. s, true, 1) -- read-only, only 1 result if q ~= nil then local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query if lchild ~= nil then preorder(t, lchild) end if rchild ~= nil then preorder(t, rchild) end end $$ LANGUAGE pllua; SELECT * from preorder('tree', 1); CREATE FUNCTION postorder (t text, s int) RETURNS SETOF int AS $$ local p = _U[t] if p == nil then -- plan not cached? p = server.prepare("select * from " .. t .. " where id=$1", {"int4"}) _U[t] = p:save() end local c = p:getcursor({s}, true) -- read-only local q = c:fetch(1) -- one row if q ~= nil then local lchild, rchild = q[1].lchild, q[1].rchild -- store before next query c:close() if lchild ~= nil then postorder(t, lchild) end if rchild ~= nil then postorder(t, rchild) end end coroutine.yield(s) end do _U = {} -- plan cache $$ LANGUAGE pllua; SELECT * FROM postorder('tree', 1); -- trigger CREATE FUNCTION treetrigger() RETURNS trigger AS $$ local row, operation = trigger.row, trigger.operation if operation == "update" then trigger.row = nil -- updates not allowed elseif operation == "insert" then local id, lchild, rchild = row.id, row.lchild, row.rchild if lchild == rchild or id == lchild or id == rchild -- avoid loops or (lchild ~= nil and _U.intree(lchild)) -- avoid cycles or (rchild ~= nil and _U.intree(rchild)) or (_U.nonemptytree() and not _U.isleaf(id)) -- not leaf? then trigger.row = nil -- skip operation end else -- operation == "delete" if not _U.isleafparent(row.id) then -- not both leaf parent? trigger.row = nil end end end do local getter = function(cmd, ...) local plan = server.prepare(cmd, {...}):save() return function(...) return plan:execute({...}, true) ~= nil end end _U = { -- plan closures nonemptytree = getter("select * from tree"), intree = getter("select node from (select id as node from tree " .. "union select lchild from tree union select rchild from tree) as q " .. "where node=$1", "int4"), isleaf = getter("select leaf from (select lchild as leaf from tree " .. "union select rchild from tree except select id from tree) as q " .. "where leaf=$1", "int4"), isleafparent = getter("select lp from (select id as lp from tree " .. "except select ti.id from tree ti join tree tl on ti.lchild=tl.id " .. "join tree tr on ti.rchild=tr.id) as q where lp=$1", "int4") } $$ LANGUAGE pllua; CREATE TRIGGER tree_trigger BEFORE INSERT OR UPDATE OR DELETE ON tree FOR EACH ROW EXECUTE PROCEDURE treetrigger(); SELECT * FROM tree WHERE id = 1; UPDATE tree SET rchild = 1 WHERE id = 1; SELECT * FROM tree WHERE id = 10; DELETE FROM tree where id = 10; DELETE FROM tree where id = 1; -- passthru types CREATE FUNCTION echo_int2(arg int2) RETURNS int2 AS $$ return arg $$ LANGUAGE pllua; SELECT echo_int2('12345'); CREATE FUNCTION echo_int4(arg int4) RETURNS int4 AS $$ return arg $$ LANGUAGE pllua; SELECT echo_int4('1234567890'); CREATE FUNCTION echo_int8(arg int8) RETURNS int8 AS $$ return arg $$ LANGUAGE pllua; SELECT echo_int8('1234567890'); SELECT echo_int8('12345678901236789'); SELECT echo_int8('1234567890123456789'); CREATE FUNCTION echo_text(arg text) RETURNS text AS $$ return arg $$ LANGUAGE pllua; SELECT echo_text('qwe''qwe'); CREATE FUNCTION echo_bytea(arg bytea) RETURNS bytea AS $$ return arg $$ LANGUAGE pllua; SELECT echo_bytea('qwe''qwe'); SELECT echo_bytea(E'q\\000w\\001e''q\\\\we'); CREATE FUNCTION echo_timestamptz(arg timestamptz) RETURNS timestamptz AS $$ return arg $$ LANGUAGE pllua; SELECT echo_timestamptz('2007-01-06 11:11 UTC') AT TIME ZONE 'UTC'; CREATE FUNCTION echo_timestamp(arg timestamp) RETURNS timestamp AS $$ return arg $$ LANGUAGE pllua; SELECT echo_timestamp('2007-01-06 11:11'); CREATE FUNCTION echo_date(arg date) RETURNS date AS $$ return arg $$ LANGUAGE pllua; SELECT echo_date('2007-01-06'); CREATE FUNCTION echo_time(arg time) RETURNS time AS $$ return arg $$ LANGUAGE pllua; SELECT echo_time('11:11'); CREATE FUNCTION echo_arr(arg text[]) RETURNS text[] AS $$ return arg $$ LANGUAGE pllua; SELECT echo_arr(array['a', 'b', 'c']); CREATE DOMAIN mynum AS numeric(6,3); CREATE FUNCTION echo_mynum(arg mynum) RETURNS mynum AS $$ return arg $$ LANGUAGE pllua; SELECT echo_mynum(666.777); CREATE TYPE mytype AS (id int2, val mynum, val_list numeric[]); CREATE FUNCTION echo_mytype(arg mytype) RETURNS mytype AS $$ return arg $$ LANGUAGE pllua; SELECT echo_mytype((1::int2, 666.777, array[1.0, 2.0]) ); CREATE FUNCTION nested_server_rows () RETURNS SETOF text as $$ for left in server.rows('select generate_series as left from generate_series(3,4) ') do for right in server.rows('select generate_series as right from generate_series(5,6) ') do local s = left.left.." "..right.right coroutine.yield(s) end end $$ language pllua; select nested_server_rows(); CREATE OR REPLACE FUNCTION pg_temp.srf() RETURNS SETOF integer AS $$ coroutine.yield(1) coroutine.yield(nil) coroutine.yield(2) $$ LANGUAGE pllua; select quote_nullable(pg_temp.srf()); CREATE OR REPLACE FUNCTION pg_temp.srf() RETURNS SETOF integer AS $$ coroutine.yield(1) coroutine.yield() coroutine.yield(2) $$ LANGUAGE pllua; select quote_nullable(pg_temp.srf()); CREATE or replace FUNCTION pg_temp.inoutf(a integer, INOUT b text, INOUT c text) AS $$ begin c = a||'c:'||c; b = 'b:'||b; end $$ LANGUAGE plpgsql; do $$ local a = server.execute("SELECT pg_temp.inoutf(5, 'ABC', 'd') as val "); local r = a[1].val print(r.b) print(r.c) $$ language pllua; -- body reload SELECT hello('PostgreSQL'); CREATE OR REPLACE FUNCTION hello(name text) RETURNS text AS $$ return string.format("Bye, %s!", name) $$ LANGUAGE pllua; SELECT hello('PostgreSQL'); pllua-ng-REL_2_0_4/sql/procedures.sql000066400000000000000000000037331347047754200176160ustar00rootroot00000000000000-- \set VERBOSITY terse -- -- test procedures, non-atomic DO-blocks, and spi.commit/rollback -- (all pg11 new features) create table xatst2 (a integer); create procedure pg_temp.tp1(a text) language pllua as $$ print("hello world", a) print(spi.is_atomic() and "atomic context" or "non-atomic context") $$; call pg_temp.tp1('foo'); begin; call pg_temp.tp1('foo'); commit; do language pllua $$ print(spi.is_atomic() and "atomic context" or "non-atomic context") $$; begin; do language pllua $$ print(spi.is_atomic() and "atomic context" or "non-atomic context") $$; commit; create procedure pg_temp.tp2() language pllua as $$ local stmt = spi.prepare([[ insert into xatst2 values ($1) ]]); stmt:execute(1); spi.commit(); stmt:execute(2); spi.rollback(); stmt:execute(3); spi.commit(); stmt:execute(4); $$; call pg_temp.tp2(); -- should now be three different xids in xatst2, and 3 rows select count(*), count(distinct age(xmin)) from xatst2; -- proper handling of open cursors create procedure pg_temp.tp3() language pllua as $$ local stmt = spi.prepare([[ select i from generate_series(1,10) i ]]); for r in stmt:rows() do print(r.i) spi.commit(); end $$; call pg_temp.tp3(); create procedure pg_temp.tp4() language pllua as $$ local stmt = spi.prepare([[ select i from generate_series(1,10) i ]], {}, { hold = true }); for r in stmt:rows() do print(r.i) spi.commit(); end $$; call pg_temp.tp4(); -- no commit inside subxact truncate table xatst2; do language pllua $$ local stmt = spi.prepare([[ insert into xatst2 values ($1) ]]); stmt:execute(1); spi.commit(); stmt:execute(2); print(pcall(function() stmt:execute(3) spi.commit() end)) -- the commit threw a lua error and the subxact was rolled back, -- so we should be in the same xact as row 2 stmt:execute(4); spi.commit(); $$; -- should now be two different xids in xatst2, and 3 rows select count(*), count(distinct age(xmin)) from xatst2; --end pllua-ng-REL_2_0_4/sql/rowdatum.sql000066400000000000000000000053621347047754200173050ustar00rootroot00000000000000-- \set VERBOSITY terse -- -- tests of operations on nested row values create type ntype1 as (fred integer, jim numeric); create type ntype2 as (thingy text[], wotsit ntype1); create type ntype3 as (foo text, bar ntype1, baz ntype2); create type ntype4 as (col0 text, col1 ntype1, col2 ntype2, col3 ntype3); -- do language pllua $$ local r = pgtype.ntype1(1,2) print(r.fred, r.jim) -- deform r.fred = 3 -- explode after deform print(r.fred, r.jim) r.jim = 4 -- modify pre-exploded value print(pgtype.ntype1(r), r.fred, r.jim) r = pgtype.ntype1(1,2) r.fred = 3 -- explode before deform print(r.fred, r.jim) r.jim = 4 -- modify pre-exploded value print(pgtype.ntype1(r), r.fred, r.jim) $$; do language pllua $$ local r0 = pgtype.ntype4("zzz", { fred = 1, jim = 2 }, { thingy = {"a","b","c"}, wotsit = { fred = 3, jim = 4} }, { foo = "abcde", bar = { fred = 5, jim = 6 }, baz = { thingy = {"x","y","z"}, wotsit = { fred = 7, jim = 8 } } }) local r = pgtype.ntype4(r0) -- deform from inner to outer print(r.col3.baz.wotsit.fred) print(r.col3.foo) print(r.col1.fred) -- and from outer to inner print(r.col2.wotsit.jim) print(r.col3.bar.jim) -- start over with un-deformed datum r = pgtype.ntype4(r0) --deform from outer to inner print(r.col1, r.col2, r.col3) print(r.col1.jim, r.col2.thingy[3], r.col3.foo) print(r.col2.wotsit.jim, r.col3.bar.jim, r.col3.baz.thingy[3]) print(r.col3.baz.wotsit.jim) print(r) print(pgtype.ntype4(r)) -- start over with un-deformed datum r = pgtype.ntype4(r0) -- explode from inner r.col3.baz.wotsit.jim = 10 r.col2.thingy[2] = "k" print(r) print(pgtype.ntype4(r)) -- start over with un-deformed datum r = pgtype.ntype4(r0) -- explode from middle r.col3.foo = "edcba" r.col1.fred = -1 r.col3.baz.wotsit.jim = 80 r.col3.baz.wotsit.fred = 0 print(r) print(pgtype.ntype4(r)) -- start over with un-deformed datum r = pgtype.ntype4(r0) -- explode from top r.col0 = "yyy" r.col3.baz.thingy[1] = "@" r.col1.jim = 20 print(r) print(pgtype.ntype4(r)) -- start over with un-deformed datum r = pgtype.ntype4(r0) -- partially deform then explode from a deformed element print(r.col0, r.col2.wotsit.fred) r.col2.wotsit.jim = 40 r.col1.jim = 0 r.col3.bar.jim = 0 print(r) print(pgtype.ntype4(r)) -- start over with un-deformed datum r = pgtype.ntype4(r0) -- partially deform then explode from an undeformed element print(r.col0, r.col2.wotsit.fred) r.col3.bar.jim = 0 r.col3.bar.fred = -1 r.col2.wotsit.fred = 0 r.col0 = "yyy" r.col1 = { fred = 100, jim = 200 } print(r) print(pgtype.ntype4(r)) $$; --end pllua-ng-REL_2_0_4/sql/spi.sql000066400000000000000000000127621347047754200162400ustar00rootroot00000000000000-- \set VERBOSITY terse -- -- Test of SPI-related functionality. create temp table tsttab ( id integer primary key, a integer, b text, c numeric, d date ); insert into tsttab(id, a,b,c,d) values (1, 1,'foo',2.34,'2017-01-01'), (2, 2,'bar',2.34,'2017-02-01'), (3, 3,'baz',2.34,'2017-03-01'), (4, 4, 'fred',2.34,'2017-04-01'), (5, 5,'jim',2.34,'2017-05-01'), (6, 6,'sheila',2.34,'2017-06-01'); -- basics do language pllua $$ local tbl tbl = spi.execute([[ select 1 as a, 'foo'::text as b ]]) print(#tbl,tbl[1],type(tbl[1])) print(tbl[1].a,tbl[1].b) tbl = spi.execute([[ select i, 'foo'::text as b from generate_series(1,10000) i ]]) print(#tbl,tbl[1],tbl[10000]) tbl = spi.execute([[ select * from tsttab order by id ]]) for i = 1,#tbl do print(tbl[i]) end $$; -- statements do language pllua $$ local stmt,tbl stmt = spi.prepare([[ select * from tsttab where id=$1 ]], {"integer"}) tbl = stmt:execute(1) print(tbl[1]) -- __call metamethod tbl = stmt(6) print(tbl[1]) stmt = spi.prepare([[ select * from tsttab where id = ANY ($1) order by id ]], {pgtype.array.integer}) tbl = stmt:execute(pgtype.array.integer(1,nil,3)) print(#tbl,tbl[1],tbl[2]) -- type deduction: stmt = spi.prepare([[ select 1 + $1 as a, pg_typeof($1) ]]) tbl = stmt:execute(1) print(#tbl,tbl[1]) $$; -- iterators do language pllua $$ for r in spi.rows([[ select * from tsttab order by id ]]) do print(r) end stmt = spi.prepare([[ select * from tsttab where id = ANY ($1) ]], {pgtype.array.integer}) for r in stmt:rows(pgtype.array.integer(1,nil,3)) do print(r) end $$; do language pllua $$ local c = spi.newcursor('curs1') local stmt = spi.prepare([[ select * from tsttab order by id for update ]]) c:open(stmt) for r in c:rows() do print(r) if r.id == 3 then spi.execute([[ update tsttab set c = c + 10 where current of curs1 ]]) end end c:move(0, 'absolute') for r in c:rows() do print(r) end for r in spi.rows([[ select * from tsttab order by id ]]) do print(r) end spi.execute([[ update tsttab set c = c - 10 where id=3 ]]) c:close() $$; -- cursors begin; declare foo scroll cursor for select * from tsttab order by id; do language pllua $$ local c = spi.findcursor("foo") local tbl tbl = c:fetch(1,'next') print(#tbl,tbl[1]) tbl = c:fetch(2,'forward') -- same as 'next' print(#tbl,tbl[1],tbl[2]) tbl = c:fetch(1,'absolute') print(#tbl,tbl[1]) tbl = c:fetch(4,'relative') print(#tbl,tbl[1]) tbl = c:fetch(1,'prior') -- same as 'backward' print(#tbl,tbl[1]) tbl = c:fetch(1,'backward') print(#tbl,tbl[1]) print(c:isopen()) spi.execute("close foo") print(c:isopen()) $$; commit; do language pllua $$ local c = spi.newcursor("bar") c:open([[ select * from tsttab where id >= $1 order by id ]], 3) local tbl tbl = c:fetch(1,'next') print(#tbl,tbl[1]) for k,v in ipairs(spi.execute([[ select name, statement from pg_cursors ]])) do print(v.name, v.statement) end c:close() for k,v in ipairs(spi.execute([[ select name, statement from pg_cursors ]])) do print(v.name, v.statement) end c:open([[ select * from tsttab where id < $1 order by id desc ]], 3) tbl = c:fetch(3,'next') print(#tbl,tbl[1],tbl[2]) for k,v in ipairs(spi.execute([[ select name, statement from pg_cursors ]])) do print(v.name, v.statement) end c:close() $$; -- cursor options on statement do language pllua $$ local stmt = spi.prepare([[ select * from tsttab where id >= $1 order by id ]], {"integer"}, { scroll = true, fast_start = true, generic_plan = true }) local stmt2 = spi.prepare([[ select * from tsttab where id >= $1 order by id ]], {"integer"}, { no_scroll = true }) local c = stmt:getcursor(4) local tbl tbl = c:fetch(3,'next') print(#tbl,tbl[1],tbl[2]) c:move(0,'absolute') tbl = c:fetch(3,'next') print(#tbl,tbl[1],tbl[2]) for k,v in ipairs(spi.execute([[ select name, statement, is_scrollable from pg_cursors ]])) do print(v.name, v.statement, v.is_scrollable) end c:close() c = stmt2:getcursor(4) local c2 = spi.findcursor(c:name()) print(c:name(), rawequal(c,c2)) for k,v in ipairs(spi.execute([[ select name, statement, is_scrollable from pg_cursors ]])) do print(v.name, v.statement, v.is_scrollable) end c:close() $$; -- check missing params are OK do language pllua $$ local stmt = spi.prepare([[ select * from generate_series($1::integer, $3) i ]]); print(stmt:argtype(1):name()) print(type(stmt:argtype(2))) print(stmt:argtype(3):name()) $$; -- check execute_count do language pllua $$ local q = [[ select * from generate_series($1::integer,$2) i ]] local r1 = spi.execute_count(q, 2, 1, 5) print(#r1) local s = spi.prepare(q, {"integer","integer"}) r1 = s:execute_count(3,1,5) print(#r1) $$; -- cursors as parameters and return values create function do_fetch(c refcursor) returns void language pllua as $$ while true do local r = (c:fetch())[1] if r==nil then break end print(r) end $$; create function do_exec(q text, n text) returns refcursor language pllua as $$ local s = spi.prepare(q) local c = spi.newcursor(n) return c:open(s):disown() $$; begin; declare mycur cursor for select * from tsttab order by id; select do_fetch('mycur'); commit; begin; select do_exec('select * from tsttab order by id desc', 'mycur2'); do language pllua $$ collectgarbage() $$; -- check cursor stays open fetch all from mycur2; commit; --end pllua-ng-REL_2_0_4/sql/subxact.sql000066400000000000000000000077731347047754200171240ustar00rootroot00000000000000-- \set VERBOSITY terse -- create table xatst (a integer); do language pllua $$ local stmt = spi.prepare([[ insert into xatst values ($1) ]]); stmt:execute(1); pcall(function() stmt:execute(2) end) stmt:execute(3); $$; -- should now be two different xids in xatst, and 3 rows select count(*), count(distinct age(xmin)) from xatst; truncate table xatst; do language pllua $$ local stmt = spi.prepare([[ insert into xatst values ($1) ]]); stmt:execute(1); print(pcall(function() stmt:execute(2) error("foo") end)) stmt:execute(3); $$; -- should now be one xid in xatst, and 2 rows select count(*), count(distinct age(xmin)) from xatst; truncate table xatst; do language pllua $$ local stmt = spi.prepare([[ insert into xatst values ($1) ]]); stmt:execute(1); print(pcall(function() stmt:execute(2) spi.error("foo") end)) stmt:execute(3); $$; -- should now be one xid in xatst, and 2 rows select count(*), count(distinct age(xmin)) from xatst; do language pllua $$ local function f() for r in spi.rows([[ select * from xatst order by a ]]) do print(r) end end print(pcall(f)) $$; do language pllua $$ local function f() for r in spi.rows([[ select * from xatst order by a ]]) do print(r) end end local function f2() error("foo") end print(pcall(f2)) f() $$; do language pllua $$ local function f(e) print("error",e) for r in spi.rows([[ select * from xatst order by a ]]) do print(r) end end local function f2() error("foo") end print(xpcall(f2,f)) $$; truncate table xatst; do language pllua $$ local stmt = spi.prepare([[ insert into xatst values ($1) ]]); local function f(e) print("error",e) stmt:execute(3) end local function f2() stmt:execute(2) error("foo") end stmt:execute(1) print(xpcall(f2,f)) $$; -- should now be one xid in xatst, and 2 rows select count(*), count(distinct age(xmin)) from xatst; do language pllua $$ local function f(e) error("bar") end local function f2() error("foo") end print(xpcall(f2,f)) $$; -- tricky error-in-error cases: -- -- pg error inside xpcall handler func needs to abort out to the -- parent of the xpcall, not the xpcall itself. begin; -- we get (harmless) warnings with lua53 but not with luajit for this -- case. suppress them. set local client_min_messages = error; do language pllua $$ local function f(e) spi.error("nested") end local function f2() error("foo") end -- don't print the result because it differs with luajit, all that -- really matters here is that we don't crash and don't reach the -- last print pcall(function() print("entering xpcall"); print("inner xpcall", xpcall(f2,f)) print("should not be reached") end) $$; commit; do language pllua $$ local level = 0 local function f(e) level = level + 1 if level==1 then print("in error handler",level,e) spi.error("nested") end end local function f2() error("foo") end print("outer pcall", pcall(function() print("entering xpcall"); print("inner xpcall", xpcall(f2,f)) print("should not be reached") end)) $$; do language pllua $$ print(lpcall(function() error("caught") end)) $$; do language pllua $$ print(lpcall(function() spi.error("not caught") end)) $$; -- make sure PG errors in coroutines are propagated (but not lua errors) do language pllua $$ local c = coroutine.create(function() coroutine.yield() error("caught") end) print(coroutine.resume(c)) print(coroutine.resume(c)) $$; do language pllua $$ local c = coroutine.create(function() coroutine.yield() spi.error("not caught") end) print(coroutine.resume(c)) print(coroutine.resume(c)) $$; -- error object funcs do language pllua $$ local err = require 'pllua.error' local r,e = pcall(function() spi.error("22003", "foo", "bar", "baz") end) print(err.type(e), err.category(e), err.errcode(e)) print(e.severity, e.category, e.errcode, e.sqlstate, e.message, e.detail, e.hint) local r,e = pcall(function() error("foo") end) print(err.type(e), err.category(e), err.errcode(e), e) $$; --end pllua-ng-REL_2_0_4/sql/triggers.sql000066400000000000000000000164651347047754200172770ustar00rootroot00000000000000-- \set VERBOSITY terse \set QUIET 0 -- Test triggers. -- -- Don't use pg10-specific stuff here; that goes in triggers_10.sql create table trigtst ( id integer primary key, name text, flag boolean, qty integer, weight numeric ); create function misctrig() returns trigger language pllua as $$ print(trigger.name,...) print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) if trigger.level == "row" then print(old,new) end $$; create trigger t1 before insert or update or delete on trigtst for each statement execute procedure misctrig('foo','bar'); create trigger t2 after insert or update or delete on trigtst for each statement execute procedure misctrig('foo','bar'); insert into trigtst values (1, 'fred', true, 23, 1.73), (2, 'jim', false, 11, 3.1), (3, 'sheila', false, 9, 1.3), (4, 'dougal', false, 1, 9.3), (5, 'brian', false, 31, 51.5), (6, 'ermintrude', true, 91, 52.7), (7, 'dylan', false, 35, 12.1), (8, 'florence', false, 23, 5.4), (9, 'zebedee', false, 199, 7.4); update trigtst set qty = qty + 1; delete from trigtst where name = 'sheila'; create trigger t3 before insert or update or delete on trigtst for each row execute procedure misctrig('wot'); create trigger t4 after insert or update or delete on trigtst for each row execute procedure misctrig('wot'); insert into trigtst values (3, 'sheila', false, 9, 1.3); update trigtst set flag = true where name = 'dylan'; delete from trigtst where name = 'jim'; -- check result is as expected select * from trigtst order by id; drop trigger t1 on trigtst; drop trigger t2 on trigtst; drop trigger t3 on trigtst; drop trigger t4 on trigtst; -- compatible mode: assign to row fields create function modtrig1() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) trigger.row.weight = 10 * trigger.row.qty trigger.row.flag = false print(trigger.name,trigger.operation,old,new) $$; create trigger t1 before insert or update or delete on trigtst for each row execute procedure modtrig1(); insert into trigtst values (2, 'jim', true, 11, 3.1); update trigtst set flag = true where name = 'ermintrude'; delete from trigtst where name = 'fred'; select * from trigtst order by id; drop trigger t1 on trigtst; -- compatible mode: assign to row wholesale create function modtrig2() returns trigger language pllua as $$ print(trigger.name,trigger.op,old,new) local id,name,flag,qty,weight = new.id, new.name, new.flag, new.qty, new.weight qty = 2 + qty weight = weight * 2 flag = not flag trigger.row = { id = id, name = name, flag = flag, qty = qty, weight = weight } $$; create trigger t1 before insert or update on trigtst for each row execute procedure modtrig2(); insert into trigtst values (1, 'fred', true, 23, 1.73); update trigtst set flag = true where name = 'zebedee'; delete from trigtst where name = 'jim'; select * from trigtst order by id; drop trigger t1 on trigtst; -- compatible mode: assign to row wholesale with new datum row create function modtrig3() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) local id,name,flag,qty,weight = new.id, new.name, new.flag, new.qty, new.weight qty = 2 + qty weight = weight * 2 flag = not flag trigger.row = pgtype(new)(id,name,flag,qty,weight) $$; create trigger t1 before insert or update on trigtst for each row execute procedure modtrig3(); insert into trigtst values (2, 'jim', false, 11, 3.1); update trigtst set flag = true where name = 'zebedee'; delete from trigtst where name = 'fred'; select * from trigtst order by id; drop trigger t1 on trigtst; -- return value mode create function modtrig4() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) local id,name,flag,qty,weight = new.id, new.name, new.flag, new.qty, new.weight qty = 2 + qty weight = weight * 2 flag = not flag return { id = id, name = name, flag = flag, qty = qty, weight = weight } $$; create trigger t1 before insert or update on trigtst for each row execute procedure modtrig4(); insert into trigtst values (1, 'fred', true, 23, 1.73); update trigtst set flag = false where name = 'dylan'; delete from trigtst where name = 'jim'; select * from trigtst order by id; drop trigger t1 on trigtst; -- return value mode create function modtrig5() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) local id,name,flag,qty,weight = new.id, new.name, new.flag, new.qty, new.weight qty = 2 + qty weight = weight * 2 flag = not flag return pgtype(new)(id,name,flag,qty,weight) $$; create trigger t1 before insert or update on trigtst for each row execute procedure modtrig5(); insert into trigtst values (2, 'jim', false, 11, 3.1); update trigtst set flag = false where name = 'dougal'; delete from trigtst where name = 'fred'; select * from trigtst order by id; drop trigger t1 on trigtst; -- throw error from trigger create function modtrig6() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) if new.flag ~= old.flag then error("no changing flags") end $$; create trigger t1 before update on trigtst for each row execute procedure modtrig6(); update trigtst set flag = false where name = 'dougal'; select * from trigtst order by id; drop trigger t1 on trigtst; -- throw error from trigger create function modtrig7() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) if new.flag ~= old.flag then error("no changing flags") end $$; create trigger t1 before update on trigtst for each row execute procedure modtrig7(); update trigtst set flag = true where name = 'florence'; select * from trigtst order by id; drop trigger t1 on trigtst; -- suppress action 1 create function modtrig8() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) if new.flag ~= old.flag then return nil end $$; create trigger t1 before update on trigtst for each row execute procedure modtrig8(); update trigtst set flag = true where name = 'florence'; select * from trigtst order by id; drop trigger t1 on trigtst; -- suppress action 2 create function modtrig9() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) if new.flag ~= old.flag then trigger.row = nil end $$; create trigger t1 before update on trigtst for each row execute procedure modtrig9(); update trigtst set flag = true where name = 'florence'; select * from trigtst order by id; drop trigger t1 on trigtst; -- table with one column exercises several edge cases: create table trigtst1col (col integer); create function modtrig10() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) new.col = 123 $$; create trigger t1 before insert on trigtst1col for each row execute procedure modtrig10(); insert into trigtst1col values (1); insert into trigtst1col values (2); select * from trigtst1col; create type t2col as (a integer, b text); create table trigtst1col2 (col t2col); create function modtrig11() returns trigger language pllua as $$ print(trigger.name,trigger.operation,old,new) new.col.a = 123 $$; create trigger t1 before insert on trigtst1col2 for each row execute procedure modtrig11(); insert into trigtst1col2 values (row(1,'foo')::t2col); select * from trigtst1col2; -- pllua-ng-REL_2_0_4/sql/triggers_10.sql000066400000000000000000000034331347047754200175660ustar00rootroot00000000000000-- \set VERBOSITY terse \set QUIET 0 -- Test pg10+ trigger functionality. create table trigtst2 ( id integer primary key, name text, flag boolean, qty integer, weight numeric ); create function ttrig1() returns trigger language pllua as $$ print(trigger.name,...) print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) for r in spi.rows([[ select * from newtab ]]) do print(r) end $$; create function ttrig2() returns trigger language pllua as $$ print(trigger.name,...) print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) for r in spi.rows([[ select 'old', * from oldtab union all select 'new', * from newtab ]]) do print(r) end $$; create function ttrig3() returns trigger language pllua as $$ print(trigger.name,...) print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) for r in spi.rows([[ select * from oldtab ]]) do print(r) end $$; create trigger t1 after insert on trigtst2 referencing new table as newtab for each statement execute procedure ttrig1('t1 insert'); create trigger t2 after update on trigtst2 referencing old table as oldtab new table as newtab for each statement execute procedure ttrig2('t2 update'); create trigger t3 after delete on trigtst2 referencing old table as oldtab for each statement execute procedure ttrig3('t3 delete'); insert into trigtst2 values (1, 'fred', true, 23, 1.73), (2, 'jim', false, 11, 3.1), (3, 'sheila', false, 9, 1.3), (4, 'dougal', false, 1, 9.3), (5, 'brian', false, 31, 51.5), (6, 'ermintrude', true, 91, 52.7), (7, 'dylan', false, 35, 12.1), (8, 'florence', false, 23, 5.4), (9, 'zebedee', false, 199, 7.4); update trigtst2 set qty = qty + 1; delete from trigtst2 where name = 'sheila'; -- pllua-ng-REL_2_0_4/sql/trusted.sql000066400000000000000000000012261347047754200171300ustar00rootroot00000000000000-- \set VERBOSITY terse set pllua.on_trusted_init=$$ local e = require 'pllua.elog' package.preload['testmod1'] = function() e.info("testmod1 loaded") return { testfunc = function() print("testfunc1") end } end; package.preload['testmod2'] = function() e.info("testmod2 loaded") return { testfunc = function() print("testfunc2") end } end; trusted.allow('testmod1', nil, nil, nil, false); trusted.allow('testmod2', nil, nil, nil, true); $$; -- do language pllua $$ print("interpreter loaded") $$; do language pllua $$ local m = require 'testmod1' m.testfunc() $$; do language pllua $$ local m = require 'testmod2' m.testfunc() $$; --end pllua-ng-REL_2_0_4/sql/types.sql000066400000000000000000000135121347047754200166030ustar00rootroot00000000000000-- \set VERBOSITY terse -- create type ctype3 as (fred integer, jim numeric); do $$ begin if current_setting('server_version_num')::integer >= 110000 then execute 'create domain dtype as ctype3 check((VALUE).jim is not null)'; else execute 'create type dtype as (fred integer, jim numeric)'; end if; end; $$; create type ctype2 as (thingy text, wotsit integer); create type ctype as (foo text, bar ctype2, baz dtype); create table tdata ( intcol integer, textcol text, charcol char(32), varcharcol varchar(32), compcol ctype, dcompcol dtype ); insert into tdata values (1, 'row 1', 'padded with blanks', 'not padded', ('x',('y',1111),(111,11.1)), (11,1.1)), (2, 'row 2', 'padded with blanks', 'not padded', ('x',('y',2222),(222,22.2)), (22,2.2)), (3, 'row 3', 'padded with blanks', 'not padded', ('x',('y',3333),(333,33.3)), (33,3.3)); create function tf1() returns setof tdata language pllua as $f$ for i = 1,4 do coroutine.yield({ intcol = i, textcol = "row "..i, charcol = "padded with blanks", varcharcol = "not padded", compcol = { foo = "x", bar = { thingy = "y", wotsit = i*1111 }, baz = { fred = i*111, jim = i*11.1 } }, dcompcol = { fred = i*11, jim = i*1.1 } }) end $f$; select * from tf1(); -- -- various checks of type handling -- do language pllua $$ print(pgtype(nil,'ctype3')(1,2)) $$; do language pllua $$ print(pgtype(nil,'ctype3')({1,2})) $$; do language pllua $$ print(pgtype(nil,'ctype3')(true,true)) $$; do language pllua $$ print(pgtype(nil,'ctype3')("1","2")) $$; do language pllua $$ print(pgtype(nil,'ctype3')({fred=1,jim=2})) $$; do language pllua $$ print(pgtype(nil,'ctype3')({fred=1,jim={}})) $$; do language pllua $$ print(pgtype(nil,'ctype3')({fred=1,jim=nil})) $$; --do language pllua $$ print(pgtype(nil,'dtype')({fred=1,jim=nil})) $$; create function tf2() returns setof tdata language pllua as $f$ local t = spi.execute("select * from tdata") for i,v in ipairs(t) do coroutine.yield(v) end $f$; select * from tf2(); do language pllua $$ print(pgtype.ctype3()) $$; -- ensure detoasting of nested composites works right do language pllua $f$ for r in spi.rows("select * from tdata") do print(r.intcol, r.compcol.foo, r.compcol.bar.wotsit, r.dcompcol.jim) end $f$; do language pllua $$ a = pgtype.array.integer({{{1,2}},{{3,4}},{{5,6}}},3,1,2) print(a) print(#a,#(a[1]),#(a[1][1])) print(a[3][1][2],a[1][1][1]) $$; do language pllua $$ print(pgtype.int4range(123,456)) $$; do language pllua $$ print(pgtype.int4range()) $$; do language pllua $$ print(pgtype.int4range(123,456,'(]')) $$; do language pllua $$ print(pgtype.int4range(nil,456,'(]')) $$; do language pllua $$ print(pgtype.int4range(nil,nil)) $$; do language pllua $$ print(pgtype.int4range(123,nil)) $$; do language pllua $$ print(pgtype.int4range('[12,56]')) $$; do language pllua $$ local r1,r2,r3 = pgtype.numrange('[12,56]'), pgtype.numrange('empty'), pgtype.numrange('(12,)') print(r1.lower,r1.upper,r1.lower_inc,r1.upper_inc,r1.lower_inf,r1.upper_inf,r1.isempty) print(r2.lower,r2.upper,r2.lower_inc,r2.upper_inc,r2.lower_inf,r2.upper_inf,r2.isempty) print(r3.lower,r3.upper,r3.lower_inc,r3.upper_inc,r3.lower_inf,r3.upper_inf,r3.isempty) $$; create type myenum as enum ('TRUE', 'FALSE', 'FILE_NOT_FOUND'); create function pg_temp.f1(a myenum) returns text language pllua as $$ print(a,type(a)) return a $$; select pg_temp.f1(x) from unnest(enum_range(null::myenum)) x; create function pg_temp.f2() returns myenum language pllua as $$ return 'FILE_NOT_FOUND' $$; select pg_temp.f2(); -- domains create domain mydom1 as varchar(3); create domain mydom2 as varchar(3) check (value in ('foo','bar','baz')); create domain mydom3 as varchar(3) not null; create domain mydom4 as varchar(3) not null check (value in ('foo','bar','baz')); create function pg_temp.f3(a mydom1) returns void language pllua as $$ print(pgtype(nil,1):name(), type(a), #a) $$; select pg_temp.f3('foo') union all select pg_temp.f3('bar '); create function pg_temp.f4d1(a text) returns mydom1 language pllua as $$ return a $$; select pg_temp.f4d1('foo'); select pg_temp.f4d1('bar '); select pg_temp.f4d1('toolong'); select pg_temp.f4d1(null); create function pg_temp.f4d2(a text) returns mydom2 language pllua as $$ return a $$; select pg_temp.f4d2('bar '); select pg_temp.f4d2('bad'); select pg_temp.f4d2('toolong'); select pg_temp.f4d2(null); create function pg_temp.f4d3(a text) returns mydom3 language pllua as $$ return a $$; select pg_temp.f4d3('bar '); select pg_temp.f4d3('toolong'); select pg_temp.f4d3(null); create function pg_temp.f4d4(a text) returns mydom4 language pllua as $$ return a $$; select pg_temp.f4d4('bar '); select pg_temp.f4d4('bad'); select pg_temp.f4d4('toolong'); select pg_temp.f4d4(null); -- array coercions -- relabeltype path do language pllua $$ local a = pgtype.array.varchar("foo","bar") local b = pgtype.array.text(a) print(b) $$; -- cast function path do language pllua $$ local a = pgtype.array.boolean(false,true) local b = pgtype.array.text(a) print(b) $$; -- IO path do language pllua $$ local a = pgtype.array.integer(10,20) local b = pgtype.array.text(a) print(b) $$; -- array typmod coercions create temp table atc (a varchar(10)[], b char(10)[]); do language pllua $$ local a = pgtype.array.varchar('foo','bar','value_too_long_for_type') local b = pgtype.atc(a,nil) $$; do language pllua $$ local a = pgtype.array.bpchar('foo','bar','value ') local b = pgtype.atc(nil,a) print(b) $$; -- composite type construction edge cases do language pllua $$ print(pgtype.ctype3()) print(pgtype.ctype3(nil)) $$; do language pllua $$ print(pgtype.ctype3(1)) -- error $$; do language pllua $$ print(pgtype.ctype3(1,2)) $$; --end pllua-ng-REL_2_0_4/src/000077500000000000000000000000001347047754200147045ustar00rootroot00000000000000pllua-ng-REL_2_0_4/src/compat.lua000066400000000000000000000056111347047754200166750ustar00rootroot00000000000000-- compat.lua --[[ This is an attempt to emulate the old pllua as closely as possible to try and make porting easy. Configure pllua.on_common_init='require "pllua.compat"' to enable it. ]] do local pgtype = require 'pllua.pgtype' function _G.fromstring(t,s) return pgtype[t]:fromstring(s) end end do local meta = ... local shared = setmetatable({}, { __index = _G }) _G.shared = shared local rawget, rawset = rawget, rawset local function shared_assign(t,k,v) rawset(rawget(shared,k) and shared or t, k, v) end function _G.setshared(k,v) if meta.__index ~= shared then meta.__index = shared meta.__newindex = shared_assign end shared[k] = v end end do local pcall = pcall local error = error local err = require 'pllua.error' _G.subtransaction = err.spcall function _G.pgfunc(...) error('pgfunc is not implemented') end end do local e = require 'pllua.elog' _G.info, _G.log, _G.notice, _G.warning = e.info, e.log, e.notice, e.warning end do local spi = require 'pllua.spi' local unpack = table.unpack or unpack local type = type local setmetatable = setmetatable -- map new result convention to old one local function fixresult(r) return type(r)=='table' and #r > 0 and r or nil end -- wrap the SPI cursor object local curs = { fetch = function(self,n) return fixresult(self.curs:fetch(n)) end; move = function(self,n) return fixresult(self.curs:move(n)) end; posfetch = function(self,n,rel) return fixresult(self.curs:fetch(n, rel and 'relative' or 'absolute')) end; posmove = function(self,n,rel) return fixresult(self.curs:move(n, rel and 'relative' or 'absolute')) end; close = function(self) return self.curs:close() end; } local curs_meta = { __index = curs } local function newcurs(c) return setmetatable({ curs = c }, curs_meta) end -- wrap the SPI statement object local plan = { execute = function(self,args,ronly,count) local s = self.stmt local nargs = s:numargs() return fixresult(s:execute_count(count, unpack(args, 1, nargs))) end; getcursor = function(self,args,ronly,name) local s = self.stmt local nargs = s:numargs() local c = spi.newcursor(type(name)=='string' and name or nil) return newcurs(c:open(s, unpack(args, 1, nargs))) end; rows = function(self,args) local s = self.stmt local nargs = s:numargs() return s:rows(unpack(args, 1, nargs)) end; issaved = function() return true end; save = function(self) return self end; } local plan_meta = { __index = plan } local function newplan(s) return setmetatable({ stmt = s }, plan_meta) end local server = { execute = function(cmd,ronly,count) return fixresult(spi.execute_count(cmd,count)) end; rows = spi.rows; prepare = function(cmd,argtypes) return newplan(spi.prepare(cmd,argtypes)) end; find = function(name) return newcurs(spi.findcursor(name)) end; } _G.server = server end return true pllua-ng-REL_2_0_4/src/compile.c000066400000000000000000000603571347047754200165130ustar00rootroot00000000000000 #include "pllua.h" #include "access/htup_details.h" #include "catalog/pg_proc.h" #include "catalog/pg_language.h" #include "catalog/pg_type.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/syscache.h" #include "utils/lsyscache.h" /* * Do fairly minimalist validation on the procTup to ensure that we're not * going to do something dangerous or security-violating. More detailed checks * can be left to the validator func. Throws a pg error on failure. * * We only do this when compiling a new function. */ static void pllua_validate_proctup(lua_State *L, Oid fn_oid, HeapTuple procTup, bool trusted) { HeapTuple lanTup; Form_pg_language lanStruct; Form_pg_proc procStruct; procStruct = (Form_pg_proc) GETSTRUCT(procTup); ASSERT_PG_CONTEXT; lanTup = SearchSysCache1(LANGOID, ObjectIdGetDatum(procStruct->prolang)); if (!HeapTupleIsValid(lanTup)) elog(ERROR, "cache lookup failed for language %u", procStruct->prolang); lanStruct = (Form_pg_language) GETSTRUCT(lanTup); if ((trusted && !lanStruct->lanpltrusted) || (!trusted && lanStruct->lanpltrusted)) { /* this can't happen unless someone is monkeying with catalogs. */ elog(ERROR, "trusted state mismatch for function %u in language %u", fn_oid, procStruct->prolang); } ReleaseSysCache(lanTup); } /* * Given a function body chunk or inline chunk on stack top, prepare it for * execution. The function is left below its single first arg. */ static void pllua_prepare_function(lua_State *L, bool trusted) { lua_newtable(L); if (lua_rawgetp(L, LUA_REGISTRYINDEX, trusted ? PLLUA_SANDBOX_META : PLLUA_GLOBAL_META) != LUA_TTABLE) luaL_error(L, "missing environment metatable"); lua_setmetatable(L, -2); lua_pushvalue(L, -1); pllua_set_environment(L, -3); } /* * Given the body of a DO-block, compile it. This is here mostly to centralize * in this file (pllua_compile_inline and pllua_compile) the environment tweaks * that we do. */ void pllua_compile_inline(lua_State *L, const char *str, bool trusted) { if (luaL_loadbufferx(L, str, strlen(str), "DO-block", "t")) pllua_rethrow_from_lua(L, LUA_ERRRUN); pllua_prepare_function(L, trusted); } /* * Given a comp_info containing the info we need, compile a function and make * an object for it. However, we don't actually store the func_info into the * object; caller does that, after reparenting the memory context. * * Note that "compiling" a function in the current setup may execute some user * code (except in validate_only mode). * * Returns the object on the stack (except in validate_only mode, which returns * nothing) */ int pllua_compile(lua_State *L) { pllua_function_compile_info *comp_info = lua_touserdata(L, 1); pllua_function_info *func_info = comp_info->func_info; const char *fname = func_info->name; const char *src; luaL_Buffer b; if (!comp_info->validate_only) { /* caller fills in pointer */ pllua_newrefobject(L, PLLUA_FUNCTION_OBJECT, NULL, true); } luaL_buffinit(L, &b); /* * New-style function environment: * * The string we construct is: * local self = (...) local function f(args) body end return f * * We construct an empty table and set a metatable on it so that it * inherits from _G (the sandbox env or the real env, depending). * We set the environment of the chunk to this table. * Then we pass that table as the first arg when we run the chunk, * so it gets set as the value of the local "self" as well. * * For trigger funcs, the args list is a standardized one. */ luaL_addstring(&b, "local self = (...) local function "); luaL_addstring(&b, fname); luaL_addchar(&b, '('); if (func_info->is_trigger) { luaL_addstring(&b, "trigger,old,new,..."); } else if (func_info->is_event_trigger) { luaL_addstring(&b, "trigger"); } else if (comp_info->nargs > 0) { int n = 0; int i; /* * Build up the list from the parameter names, excluding any * OUT parameters; stop when we find an unnamed arg. Note that * argmodes can be null if all params are IN. */ if (comp_info->argnames && comp_info->argnames[0]) { for(i = 0; i < comp_info->nallargs; ++i) { if (!comp_info->argmodes || comp_info->argmodes[i] != 'o') { if (comp_info->argnames[i] && comp_info->argnames[i][0]) { if (n > 0) luaL_addchar(&b, ','); luaL_addstring(&b, comp_info->argnames[i]); ++n; } else break; } } } /* * If we didn't get all the args (which includes the VARIADIC "any" * case since we don't let that have a name), append ... to get * the rest. */ if (n < comp_info->nargs) { if (n > 0) luaL_addchar(&b, ','); luaL_addstring(&b, "..."); } } luaL_addstring(&b, ") "); /* * Actual function body. */ luaL_addlstring(&b, VARDATA_ANY(comp_info->prosrc), VARSIZE_ANY_EXHDR(comp_info->prosrc)); /* * Terminate string and convert to lua value */ luaL_addstring(&b, " end return "); luaL_addstring(&b, fname); luaL_pushresult(&b); src = lua_tostring(L, -1); /* * Load the code into lua but run nothing. (Syntax errors show up here.) */ if (luaL_loadbufferx(L, src, strlen(src), fname, "t")) pllua_rethrow_from_lua(L, LUA_ERRRUN); lua_remove(L, -2); /* drop source */ /* * Bail out here if validating. */ if (comp_info->validate_only) return 0; pllua_prepare_function(L, func_info->trusted); /* * Run the code to obtain the function value as a result. */ lua_call(L, 1, 1); /* * Store in the function object's uservalue table. */ lua_getuservalue(L, -2); lua_insert(L, -2); lua_rawsetp(L, -2, PLLUA_FUNCTION_MEMBER); lua_pop(L, 1); return 1; } /* * Intern a function into the functions table, indexed by numeric oid. * * This will NOT replace an existing entry, unless the function passed in is * nil, which signifies uninterning an existing function. * * Returns true if something was stored, false if not. */ int pllua_intern_function(lua_State *L) { lua_Integer oid = luaL_checkinteger(L, 2); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_FUNCS); if (!lua_isnil(L, 1)) { pllua_checkrefobject(L, 1, PLLUA_FUNCTION_OBJECT); lua_rawgeti(L, -1, oid); if (!lua_isnil(L, -1)) { lua_pushboolean(L, 0); return 1; } lua_pop(L, 1); } lua_pushvalue(L, 1); lua_rawseti(L, -2, oid); lua_pushboolean(L, 1); return 1; } /* * Call this to resolve an activation before use * * The act->func_info must have been set up as far as the pg state goes, but * the actual lua function may not have been compiled yet (since we want to * ensure that this is fully valid before running any user-supplied code). * This handles polymorphism and so on. * * Note that act->func_info may not have been filled in yet, and we don't do * that. In error cases we ensure the activation is de-resolved, which should * prevent anything accessing any dangling fields. */ static void pllua_resolve_activation(lua_State *L, pllua_func_activation *act, pllua_function_info *func_info, FunctionCallInfo fcinfo) { MemoryContext oldcontext; FmgrInfo *flinfo = fcinfo->flinfo; Oid rettype = func_info->rettype; if (act->resolved) return; ASSERT_PG_CONTEXT; oldcontext = MemoryContextSwitchTo(flinfo->fn_mcxt); if (func_info->polymorphic_ret || func_info->returns_row) { act->typefuncclass = get_call_result_type(fcinfo, &act->rettype, &act->tupdesc); if (act->tupdesc && act->tupdesc->tdrefcount != -1) { /* * This is a ref-counted tupdesc, but we can't pin it because any * such pin would belong to a resource owner, and we can't * guarantee that our required data lifetimes nest properly with * the resource owners. So make a copy instead. (If it's not * refcounted, then it'll already be in fn_mcxt.) */ act->tupdesc = CreateTupleDescCopy(act->tupdesc); } } else { act->rettype = rettype; act->typefuncclass = TYPEFUNC_SCALAR; } act->retdomain = get_typtype(act->rettype) == TYPTYPE_DOMAIN; act->polymorphic = func_info->polymorphic; act->variadic_call = get_fn_expr_variadic(fcinfo->flinfo); act->nargs = func_info->nargs; act->retset = func_info->retset; act->readonly = func_info->readonly; if (act->polymorphic) { /* * Polymorphic arguments. Copy the argtypes list and resolve the actual * types from the call site. */ act->argtypes = palloc(act->nargs * sizeof(Oid)); memcpy(act->argtypes, func_info->argtypes, act->nargs * sizeof(Oid)); if (!resolve_polymorphic_argtypes(act->nargs, act->argtypes, NULL, /* these are IN params only */ flinfo->fn_expr)) elog(ERROR,"failed to resolve polymorphic argtypes"); } else act->argtypes = func_info->argtypes; MemoryContextSwitchTo(oldcontext); act->resolved = true; } /* * Load up our func_info and comp_info structures from the function's catalog * entry. */ static void pllua_load_from_proctup(lua_State *L, Oid fn_oid, pllua_function_info *func_info, pllua_function_compile_info *comp_info, HeapTuple procTup, bool trusted) { MemoryContext oldcontext = MemoryContextSwitchTo(func_info->mcxt); Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup); bool isnull; Datum psrc; int i; func_info->name = pstrdup(NameStr(procStruct->proname)); func_info->fn_oid = fn_oid; func_info->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data); func_info->fn_tid = procTup->t_self; func_info->rettype = procStruct->prorettype; func_info->returns_row = type_is_rowtype(func_info->rettype); func_info->retset = procStruct->proretset; func_info->polymorphic_ret = IsPolymorphicType(func_info->rettype); func_info->language_oid = procStruct->prolang; func_info->trusted = trusted; func_info->nargs = procStruct->pronargs; func_info->variadic = procStruct->provariadic != InvalidOid; func_info->variadic_any = procStruct->provariadic == ANYOID; func_info->readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE); func_info->is_trigger = (procStruct->prorettype == TRIGGEROID); func_info->is_event_trigger = (procStruct->prorettype == EVTTRIGGEROID); func_info->polymorphic = false; /* set below */ Assert(func_info->nargs == procStruct->proargtypes.dim1); func_info->argtypes = (Oid *) palloc(func_info->nargs * sizeof(Oid)); memcpy(func_info->argtypes, procStruct->proargtypes.values, func_info->nargs * sizeof(Oid)); /* check for polymorphic arg */ for (i = 0; i < func_info->nargs; ++i) { if (IsPolymorphicType(func_info->argtypes[i]) || func_info->argtypes[i] == ANYOID) { func_info->polymorphic = true; break; } } /* * Redo the most essential validation steps out of sheer paranoia */ pllua_validate_proctup(L, fn_oid, procTup, trusted); /* * Stuff below is only needed for compiling */ MemoryContextSwitchTo(comp_info->mcxt); psrc = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "null prosrc"); comp_info->prosrc = DatumGetTextPP(psrc); comp_info->validate_only = false; /* * Compile needs the allargs list (to get names and modes) as well as the * runtime (IN only) argtypes list set above. */ comp_info->nargs = procStruct->pronargs; comp_info->nallargs = get_func_arg_info(procTup, &comp_info->allargtypes, &comp_info->argnames, &comp_info->argmodes); comp_info->variadic = procStruct->provariadic; MemoryContextSwitchTo(oldcontext); } /* * Return true if func_info is an up to date compile of procTup. */ static bool pllua_function_valid(pllua_function_info *func_info, HeapTuple procTup) { return (func_info && func_info->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) && ItemPointerEquals(&func_info->fn_tid, &procTup->t_self)); } /* * Returns with a function activation object on top of the lua stack. * * Also returns a pointer to the activation as a convenience to the caller. */ pllua_func_activation * pllua_validate_and_push(lua_State *L, FunctionCallInfo fcinfo, bool trusted) { MemoryContext oldcontext = CurrentMemoryContext; pllua_func_activation *volatile retval = NULL; FmgrInfo *flinfo = fcinfo->flinfo; ReturnSetInfo *rsi = ((fcinfo->resultinfo && IsA(fcinfo->resultinfo, ReturnSetInfo)) ? (ReturnSetInfo *)(fcinfo->resultinfo) : NULL); ASSERT_LUA_CONTEXT; /* * We need the pg_proc row etc. every time, but we have to avoid throwing * pg errors through lua. */ PLLUA_TRY(); { pllua_func_activation *act = flinfo->fn_extra; Oid fn_oid = flinfo->fn_oid; int rc; /* * If we don't have an activation yet, make one (it'll initially be * invalid). We have to ensure that it's safe to leave a * not-yet-filled-in activation attached to flinfo. * * If we do have one already, find its lua object. * * The activation is left on the lua stack. */ if (!act) { pllua_pushcfunction(L, pllua_newactivation); lua_pushlightuserdata(L, flinfo->fn_mcxt); pllua_pcall(L, 1, 1, 0); act = lua_touserdata(L, -1); flinfo->fn_extra = act; } else pllua_getactivation(L, act); /* * This part may have to be repeated in some rare recursion scenarios. */ for (;;) { pllua_function_info *func_info; pllua_function_compile_info *comp_info; MemoryContext fcxt; MemoryContext ccxt; HeapTuple procTup; /* Get the pg_proc tuple. */ procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid)); if (!HeapTupleIsValid(procTup)) elog(ERROR, "cache lookup failed for function %u", fn_oid); if (pllua_function_valid(act->func_info, procTup)) { /* fastpath out when data is already valid. */ ReleaseSysCache(procTup); break; } /* * Lookup function by oid in our lua table (this can't throw) */ lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_FUNCS); if (lua_rawgeti(L, -1, (lua_Integer) fn_oid) == LUA_TUSERDATA) { void **p = pllua_torefobject(L, -1, PLLUA_FUNCTION_OBJECT); func_info = p ? *p : NULL; /* might be out of date. */ if (pllua_function_valid(func_info, procTup)) { /* * The activation is out of date, but the existing * compiled function is not. Just update the activation. */ /* stack: activation funcs_table funcobject */ pllua_pushcfunction(L, pllua_setactivation); lua_pushlightuserdata(L, act); lua_pushvalue(L, -3); pllua_pcall(L, 2, 0, 0); /* stack: activation funcs_table funcobject */ lua_pop(L, 2); ReleaseSysCache(procTup); break; } /* * Compiled function in cache is out of date. Unintern it * before proceeding (recursion worries). This might lead to it * being GC'd, so forget about the pointer too. */ func_info = NULL; pllua_pushcfunction(L, pllua_intern_function); lua_pushnil(L); lua_pushinteger(L, (lua_Integer) fn_oid); pllua_pcall(L, 2, 0, 0); } /* stack: activation funcs_table funcobject */ lua_pop(L, 2); act->resolved = false; act->func_info = NULL; /* * If we get this far, we need to compile up the function from * scratch. Create the func_info, compile_info and contexts. Note * that the compile context is always transient, but the function * context is reparented to the long-lived lua context on success. * * (CurrentMemoryContext at this point is still the original * caller's context, assumed transient) */ fcxt = AllocSetContextCreate(CurrentMemoryContext, "pllua function object", ALLOCSET_SMALL_SIZES); ccxt = AllocSetContextCreate(CurrentMemoryContext, "pllua compile context", ALLOCSET_SMALL_SIZES); func_info = MemoryContextAlloc(fcxt, sizeof(pllua_function_info)); func_info->mcxt = fcxt; comp_info = MemoryContextAlloc(ccxt, sizeof(pllua_function_compile_info)); comp_info->mcxt = ccxt; comp_info->func_info = func_info; pllua_load_from_proctup(L, fn_oid, func_info, comp_info, procTup, trusted); /* * Resolve the activation before compiling in case the user code * tries to do something that needs access to it. */ pllua_resolve_activation(L, act, func_info, fcinfo); /* * Beware, compiling can invoke user-supplied code, which might * in turn recurse here. We trust that stack depth checks will * break any such loop if need be. */ pllua_pushcfunction(L, pllua_compile); lua_pushlightuserdata(L, comp_info); rc = pllua_pcall_nothrow(L, 1, 1, 0); MemoryContextSwitchTo(oldcontext); MemoryContextDelete(ccxt); if (rc) { /* error. bail out */ act->resolved = false; MemoryContextDelete(fcxt); pllua_rethrow_from_lua(L, rc); } else { void **p = lua_touserdata(L, -1); MemoryContextSetParent(fcxt, pllua_get_memory_cxt(L)); *p = func_info; } /* * Try and intern the function. Since we uninterned it earlier, we * expect this to succeed, but a recursive call could have interned * a new version already (which will be at least as new as ours). * Worse, if so, that new version could already be out of date, * meaning that we have to loop back to check the pg_proc row * again. */ /* stack: activation funcinfo */ pllua_pushcfunction(L, pllua_intern_function); lua_insert(L, -2); lua_pushinteger(L, (lua_Integer) fn_oid); pllua_pcall(L, 2, 0, 0); func_info = NULL; ReleaseSysCache(procTup); } /* * Post-compile per-call validation (mostly here to avoid more catch * blocks elsewhere) */ if (act->func_info->retset) { if (!rsi || !IsA(rsi, ReturnSetInfo) || !(rsi->allowedModes & SFRM_ValuePerCall)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); } if (!act->resolved) pllua_resolve_activation(L, act, act->func_info, fcinfo); retval = act; } PLLUA_CATCH_RETHROW(); MemoryContextSwitchTo(oldcontext); return retval; } /* * Returns true if typeid (a pseudotype) is acceptable for either a result type * (if is_result) or a param of mode "argmode". */ static bool pllua_acceptable_pseudotype(lua_State *L, Oid typeid, bool is_result, char argmode) { bool is_input = !is_result; bool is_output = is_result; if (!is_result) { switch (argmode) { case PROARGMODE_VARIADIC: case PROARGMODE_IN: is_input = true; is_output = false; break; case PROARGMODE_INOUT: is_input = true; is_output = true; break; case PROARGMODE_TABLE: case PROARGMODE_OUT: is_input = false; is_output = true; break; } } /* * we actually support most pseudotypes, but we whitelist rather * than blacklist to reduce the chance of future breakage. */ switch (typeid) { /* only as return types */ case TRIGGEROID: case EVTTRIGGEROID: case VOIDOID: return !is_input; /* only as argument type */ case ANYOID: return !is_output; /* ok for either argument or result */ case RECORDOID: case RECORDARRAYOID: case CSTRINGOID: return true; /* core code has the job of validating these are correctly used */ case ANYARRAYOID: case ANYNONARRAYOID: case ANYELEMENTOID: case ANYENUMOID: case ANYRANGEOID: return true; default: return false; } } static bool pllua_acceptable_name(lua_State *L, const char *name) { unsigned char *p = (unsigned char *) name; unsigned char c; if (!name) return false; c = *p; if (!c || (c >= '0' && c <= '9')) return false; while ((c = *p++)) if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '_'))) return false; switch (name[0]) { case 'a': return strcmp(name,"and") != 0; case 'b': return strcmp(name,"break") != 0; case 'd': return strcmp(name,"do") != 0; case 'e': return ((strcmp(name,"else") != 0) && (strcmp(name,"elseif") != 0) && (strcmp(name,"end") != 0)); case 'f': return ((strcmp(name,"false") != 0) && (strcmp(name,"for") != 0) && (strcmp(name,"function") != 0)); case 'g': return strcmp(name,"goto") != 0; case 'i': return ((strcmp(name,"if") != 0) && (strcmp(name,"in") != 0)); case 'l': return strcmp(name,"local") != 0; case 'n': return ((strcmp(name,"nil") != 0) && (strcmp(name,"not") != 0)); case 'o': return strcmp(name,"or") != 0; case 'r': return ((strcmp(name,"repeat") != 0) && (strcmp(name,"return") != 0)); case 't': return ((strcmp(name,"then") != 0) && (strcmp(name,"true") != 0)); case 'u': return strcmp(name,"until") != 0; case 'w': return strcmp(name,"while") != 0; default: return true; } } /* * This is the guts of the validator function */ void pllua_validate_function(lua_State *L, Oid fn_oid, bool trusted) { ASSERT_LUA_CONTEXT; PLLUA_TRY(); { HeapTuple procTup; pllua_function_info *func_info; pllua_function_compile_info *comp_info; int i; bool nameless = false; procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid)); if (!HeapTupleIsValid(procTup)) elog(ERROR, "cache lookup failed for function %u", fn_oid); /* don't worry about memory contexts, it's all transient */ func_info = palloc(sizeof(pllua_function_info)); func_info->mcxt = CurrentMemoryContext; comp_info = palloc(sizeof(pllua_function_compile_info)); comp_info->func_info = func_info; comp_info->mcxt = CurrentMemoryContext; pllua_load_from_proctup(L, fn_oid, func_info, comp_info, procTup, trusted); /* * Produce a better error message if the function name itself would * break the syntax. */ if (!pllua_acceptable_name(L, func_info->name)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Lua function name \"%s\" is not a valid Lua identifier", func_info->name))); /* nitpick over the argument and result types. */ if (get_typtype(func_info->rettype) == TYPTYPE_PSEUDO && !pllua_acceptable_pseudotype(L, func_info->rettype, true, ' ')) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Lua functions cannot return type %s", format_type_be(func_info->rettype)))); } /* check OUT as well as IN args */ for (i = 0; i < comp_info->nallargs; ++i) { Oid argtype = comp_info->allargtypes[i]; char argmode = (comp_info->argmodes ? comp_info->argmodes[i] : PROARGMODE_IN); const char *argname = (comp_info->argnames ? comp_info->argnames[i] : ""); if (get_typtype(argtype) == TYPTYPE_PSEUDO && !pllua_acceptable_pseudotype(L, argtype, false, argmode)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Lua functions cannot accept type %s", format_type_be(argtype)))); } /* * IN or INOUT argument with a name should not follow one without a * name. VARIADIC "any" must not have a name. These restrictions * don't matter at SQL level, but violating them leads to the * possibility of non-obvious errors with variable scopes, so best * to forbid them. */ switch (argmode) { case PROARGMODE_IN: case PROARGMODE_INOUT: if (argname[0]) { if (nameless) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Lua function arguments with names must not follow arguments without names"))); } else nameless = true; break; case PROARGMODE_TABLE: case PROARGMODE_OUT: break; case PROARGMODE_VARIADIC: if (argtype == ANYOID && argname[0]) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Lua function arguments of type VARIADIC \"any\" must not have names"))); break; } /* * Produce a better error message if the argument name would break * the syntax. */ if (argname && argname[0] && !pllua_acceptable_name(L, argname)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Lua argument name \"%s\" is not a valid Lua identifier", argname))); } /* * We really don't want to invoke any user-defined code for this, so we * arrange to load() the function body but execute nothing. This should * catch syntax errors just fine. But disable body checks entirely if * chech_function_bodies is false. */ comp_info->validate_only = true; if (check_function_bodies) { pllua_pushcfunction(L, pllua_compile); lua_pushlightuserdata(L, comp_info); pllua_pcall(L, 1, 0, 0); } ReleaseSysCache(procTup); } PLLUA_CATCH_RETHROW(); } pllua-ng-REL_2_0_4/src/datum.c000066400000000000000000004145141347047754200161730ustar00rootroot00000000000000/* datum.c */ #include "pllua.h" #include "access/htup_details.h" #include "access/sysattr.h" #include "access/tuptoaster.h" #include "catalog/pg_type.h" #include "mb/pg_wchar.h" #include "parser/parse_coerce.h" #include "parser/parse_type.h" #include "utils/arrayaccess.h" #include "utils/builtins.h" #include "utils/datum.h" #include "utils/lsyscache.h" #include "utils/rangetypes.h" #include "utils/syscache.h" #include "utils/typcache.h" #if PG_VERSION_NUM < 110000 #define DatumGetRangeTypeP(d_) DatumGetRangeType(d_) #define DatumGetAnyArrayP(d_) DatumGetAnyArray(d_) #endif /* * Basic plan of attack: * * A Datum object has this directly in its body: * * Datum value; * int32 typmod; * bool need_gc; * bool modified; * * We create the object initially with just the value, and need_gc false. * However, we then have to (more or less immediately) copy the value if it's a * byref type, since we have no control over its lifetime inside lua (it may * even need to survive across transactions, so we have to detoast it too). * The code that does the copying is separated from the initial creation for * reasons of error handling. * * An exception is made for datums extracted from a row which is itself already * a datum. For this case we leave need_gc false, and put a reference to the * parent value in the uservalue slot. * * Typmod is -1 unless we took this datum from a column in which case it's the * column atttypmod. * * Information about the object type is contained in a typeinfo object. We keep * a cache of type info (by oid) and tupdesc info (by typmod for RECORD * tupdescs). Because our cache is decoupled from the syscache and very * long-lived, we register for invalidations. * * The uservalue of the typeinfo contains the metatable to be used for datum * objects of this type. In addition we cache stuff there. * * Global caches: * * reg[PLLUA_RECORDS] = { [typmod] = typeobject } * reg[PLLUA_TYPES] = { [oid] = typeobject } * * */ static bool pllua_typeinfo_iofunc(lua_State *L, pllua_typeinfo *t, IOFuncSelector whichfunc); static void pllua_typeconv_register(lua_State *L, int tabidx, int typeidx); static const char *pllua_typeinfo_raw_output(lua_State *L, Datum value, pllua_typeinfo *t); #if LUAJIT_VERSION_NUM > 0 && !defined(NO_LUAJIT) static char PLLUA_INT8HACK_INFUNC[] = "int8hack infunc"; static char PLLUA_INT8HACK_OUTFUNC[] = "int8hack outfunc"; static const char *luajit_lua = "local ffi = require 'ffi' \n" "local u64 = ffi.typeof('uint64_t') \n" "local s64 = ffi.typeof('int64_t') \n" "local u32 = ffi.typeof('uint32_t') \n" "local s32 = ffi.typeof('int32_t') \n" "local u16 = ffi.typeof('uint16_t') \n" "local s16 = ffi.typeof('int16_t') \n" "local u8 = ffi.typeof('uint8_t') \n" "local s8 = ffi.typeof('int8_t') \n" "local function infunc(lo,hi) \n" " return s64(u64(hi) * 4294967296ULL + u64(lo)) \n" "end \n" "local function outfunc(v) \n" " if ffi.istype(s64,v) then \n" " return tonumber(u64(v) / 4294967296ULL), tonumber(u64(v) % 4294967296ULL), true \n" " elseif ffi.istype(u64,v) then \n" " return tonumber(v / 4294967296ULL), tonumber(v % 4294967296ULL), false \n" " elseif ffi.istype(s32,v) \n" " or ffi.istype(u32,v) \n" " or ffi.istype(s8,v) \n" " or ffi.istype(u8,v) \n" " or ffi.istype(s16,v) \n" " or ffi.istype(u16,v) \n" " then \n" " return v < 0 and -1 or 0, tonumber(u32(v)), true \n" " end \n" "end \n" "return infunc,outfunc\n"; #endif /* * IMPORTANT!!! * * It is _our_ responsibility to verify encoding correctness when passing any * string data from untrusted sources (i.e. the Lua code) into PG server apis. */ void pllua_verify_encoding(lua_State *L, const char *str) { /* XXX improve error message */ if (str && !pg_verifymbstr(str, strlen(str), true)) { if (pllua_context == PLLUA_CONTEXT_LUA) luaL_error(L, "invalid encoding"); else elog(ERROR, "invalid encoding"); } } bool pllua_verify_encoding_noerror(lua_State *L, const char *str) { if (!str) return true; return pg_verifymbstr(str, strlen(str), true); } /* * "light" detoast function that does not copy or align values. */ static Datum pllua_detoast_light(lua_State *L, Datum d) { volatile Datum nd; if (!VARATT_IS_EXTENDED(d) || (VARATT_IS_SHORT(d) && !VARATT_IS_EXTERNAL(d))) return d; PLLUA_TRY(); { nd = PointerGetDatum(PG_DETOAST_DATUM_COPY(d)); } PLLUA_CATCH_RETHROW(); if (nd != d) pllua_record_gc_debt(L, VARSIZE(DatumGetPointer(nd))); return nd; } void *pllua_palloc(lua_State *L, size_t sz) { void *volatile res = NULL; PLLUA_TRY(); { res = palloc(sz); } PLLUA_CATCH_RETHROW(); pllua_record_gc_debt(L, sz); return res; } static Datum pllua_float4_get_datum(lua_State *L, float4 val) { #if USE_FLOAT4_BYVAL return Float4GetDatum(val); #else float4 *p = pllua_palloc(L, sizeof(float4)); *p = val; return Float4GetDatumFast(*p); #endif } static Datum pllua_float8_get_datum(lua_State *L, float8 val) { #if USE_FLOAT8_BYVAL return Float8GetDatum(val); #else float8 *p = pllua_palloc(L, sizeof(float8)); *p = val; return Float8GetDatumFast(*p); #endif } static Datum pllua_int64_get_datum(lua_State *L, int64 val) { #if USE_FLOAT8_BYVAL /* yes, this controls int64 too */ return Int64GetDatum(val); #else int64 *p = pllua_palloc(L, sizeof(int64)); *p = val; return Int64GetDatumFast(*p); #endif } /* * Get the typeid/typmod from a datum tuple, regardless of its toast status. * * This works in either lua or pg context. */ static void pllua_get_tuple_type(lua_State *L, Datum value, Oid *typeid, int32 *typmod) { *typeid = InvalidOid; if (typmod) *typmod = -1; if (VARATT_IS_EXTENDED(value)) { PLLUA_TRY(); { HeapTupleHeader htup = (HeapTupleHeader) heap_tuple_untoast_attr_slice( (struct varlena *) DatumGetPointer(value), 0, sizeof(HeapTupleHeaderData)); *typeid = HeapTupleHeaderGetTypeId(htup); if (typmod) *typmod = HeapTupleHeaderGetTypMod(htup); pfree(htup); } PLLUA_CATCH_RETHROW(); } else { HeapTupleHeader htup = (HeapTupleHeader) DatumGetPointer(value); *typeid = HeapTupleHeaderGetTypeId(htup); if (typmod) *typmod = HeapTupleHeaderGetTypMod(htup); } } /* * If a datum is representable directly as a Lua type, then push it as that * type. Otherwise push nothing. * * Returns the Lua type or LUA_TNONE */ int pllua_value_from_datum(lua_State *L, Datum value, Oid typeid) { ASSERT_LUA_CONTEXT; switch (typeid) { /* * Everything has a text representation, but we use this only for those * types where there isn't really any structure _other_ than text. */ case TEXTOID: case VARCHAROID: case BPCHAROID: case XMLOID: case JSONOID: case BYTEAOID: { Datum v = pllua_detoast_light(L, value); lua_pushlstring(L, VARDATA_ANY(v), VARSIZE_ANY_EXHDR(v)); } return LUA_TSTRING; case CSTRINGOID: case NAMEOID: { const char *str = DatumGetPointer(value); lua_pushlstring(L, str, strlen(str)); } return LUA_TSTRING; case FLOAT4OID: lua_pushnumber(L, DatumGetFloat4(value)); return LUA_TNUMBER; case FLOAT8OID: lua_pushnumber(L, DatumGetFloat8(value)); return LUA_TNUMBER; case BOOLOID: lua_pushboolean(L, DatumGetBool(value) ? 1 : 0); return LUA_TBOOLEAN; case OIDOID: lua_pushinteger(L, (lua_Integer) DatumGetObjectId(value)); return LUA_TNUMBER; case INT2OID: lua_pushinteger(L, (lua_Integer) DatumGetInt16(value)); return LUA_TNUMBER; case INT4OID: lua_pushinteger(L, (lua_Integer) DatumGetInt32(value)); return LUA_TNUMBER; #if defined(PLLUA_INT8_OK) case INT8OID: lua_pushinteger(L, (lua_Integer) DatumGetInt64(value)); return LUA_TNUMBER; #elif defined(PLLUA_INT8_LUAJIT_HACK) case INT8OID: { int64 v = DatumGetInt64(value); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_INT8HACK_INFUNC); lua_pushnumber(L, (lua_Number) (double) (v & 0xFFFFFFFF)); lua_pushnumber(L, (lua_Number) (double) ((v >> 32) & 0xFFFFFFFF)); lua_call(L, 2, 1); } return lua_type(L, -1); #endif case REFCURSOROID: lua_pushcfunction(L, pllua_spi_newcursor); { Datum v = pllua_detoast_light(L, value); lua_pushlstring(L, VARDATA_ANY(v), VARSIZE_ANY_EXHDR(v)); } lua_call(L, 1, 1); return LUA_TUSERDATA; default: return LUA_TNONE; } } /* * If a datum type corresponds to a simple Lua type, then take a value of that * type and return as Datum/isnull. May copy the data into the current memory * context (but uses a catch block for that; requires Lua context). * * nil is accepted as input for any type whatsoever (and treated as NULL). * * Throws a lua error only on memory exhaustion. * * Note: for some value types (notably cstring), does not copy the data. Caller * must ensure that savedatum/formtuple/construct_array is done before dropping * the reference to the lua value. */ bool pllua_datum_from_value(lua_State *L, int nd, Oid typeid, Datum *result, bool *isnull, const char **errstr) { ASSERT_LUA_CONTEXT; nd = lua_absindex(L, nd); if (lua_type(L, nd) == LUA_TNIL) { *isnull = true; *result = (Datum)0; return true; } else *isnull = false; switch (lua_type(L, nd)) { case LUA_TNIL: case LUA_TNONE: *errstr = "missing value"; return true; case LUA_TBOOLEAN: if (typeid == BOOLOID) { *result = BoolGetDatum( (lua_toboolean(L, nd) != 0) ); return true; } return false; case LUA_TSTRING: { size_t len; const char *str = lua_tolstring(L, nd, &len); /* * Only handle the common cases here, we punt everything else * to the input functions. (The only one that really matters here * is bytea, where the semantics are different.) */ switch (typeid) { case TEXTOID: case VARCHAROID: case REFCURSOROID: { text *t; if (len != strlen(str)) *errstr = "null characters not allowed in text values"; else if (!pllua_verify_encoding_noerror(L, str)) *errstr = "invalid encoding for text value"; else { t = pllua_palloc(L, len + VARHDRSZ); memcpy(VARDATA(t), str, len); SET_VARSIZE(t, len + VARHDRSZ); *result = PointerGetDatum(t); } } return true; case BYTEAOID: { bytea *b = pllua_palloc(L, len + VARHDRSZ); memcpy(VARDATA(b), str, len); SET_VARSIZE(b, len + VARHDRSZ); *result = PointerGetDatum(b); } return true; case CSTRINGOID: { if (len != strlen(str)) *errstr = "null characters not allowed in cstring values"; else if (!pllua_verify_encoding_noerror(L, str)) *errstr = "invalid encoding for cstring value"; else *result = CStringGetDatum(str); } return true; case BOOLOID: { bool v = false; if (parse_bool_with_len(str, len, &v)) *result = BoolGetDatum(v); else *errstr = "invalid boolean value"; } return true; } } return false; case LUA_TNUMBER: { int isint = 0; lua_Integer intval = lua_tointegerx(L, nd, &isint); lua_Number floatval = lua_tonumber(L, nd); switch (typeid) { case FLOAT4OID: *result = pllua_float4_get_datum(L, (float4) floatval); return true; case FLOAT8OID: *result = pllua_float8_get_datum(L, (float8) floatval); return true; case BOOLOID: if (isint) *result = BoolGetDatum( (intval != 0) ); else *errstr = "invalid boolean value"; return true; case OIDOID: if (isint && intval == (lua_Integer)(Oid)intval) *result = ObjectIdGetDatum( (Oid)intval ); else *errstr = "oid value out of range"; return true; case INT2OID: if (isint && intval >= PG_INT16_MIN && intval <= PG_INT16_MAX) *result = Int16GetDatum(intval); else *errstr = "smallint value out of range"; return true; case INT4OID: if (isint && intval >= PG_INT32_MIN && intval <= PG_INT32_MAX) *result = Int32GetDatum(intval); else *errstr = "integer value out of range"; return true; case INT8OID: if (isint) *result = pllua_int64_get_datum(L, intval); else *errstr = "bigint out of range"; return true; case NUMERICOID: PLLUA_TRY(); { if (isint) *result = DirectFunctionCall1(int8_numeric, Int64GetDatumFast(intval)); else *result = DirectFunctionCall1(float8_numeric, Float8GetDatumFast(floatval)); } PLLUA_CATCH_RETHROW(); return true; } } return false; case LUA_TUSERDATA: if (typeid == REFCURSOROID && pllua_toobject(L, nd, PLLUA_SPI_CURSOR_OBJECT)) { text *t; size_t len; const char *str; lua_pushcfunction(L, pllua_cursor_name); lua_pushvalue(L, nd); lua_call(L, 1, 1); if (!lua_isnil(L, -1)) { /* cursor name is already checked for encoding correctness */ str = lua_tolstring(L, -1, &len); t = pllua_palloc(L, len + VARHDRSZ); memcpy(VARDATA(t), str, len); SET_VARSIZE(t, len + VARHDRSZ); *result = PointerGetDatum(t); } else { *isnull = true; *result = (Datum)0; } return true; } return false; /* * Enable this even if we don't convert bigint to cdata ourselves, since * there's no reason not to allow cdata parameters to constructor calls. */ #if LUAJIT_VERSION_NUM > 0 && !defined(NO_LUAJIT) case LUA_TCDATA: lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_INT8HACK_OUTFUNC); lua_pushvalue(L, nd); lua_call(L, 1, 3); if (lua_isnil(L, -1)) { lua_pop(L, 3); return false; } else { bool s64 = lua_toboolean(L, -1); uint64 lo = (uint64) lua_tonumber(L, -2); uint64 hi = (uint64) lua_tonumber(L, -3); uint64 uval = (uint64) ((hi << 32) | lo); int64 val = (int64) uval; lua_pop(L, 3); switch (typeid) { case FLOAT4OID: if (s64) *result = pllua_float4_get_datum(L, (float4) val); else *result = pllua_float4_get_datum(L, (float4) uval); return true; case FLOAT8OID: if (s64) *result = pllua_float8_get_datum(L, (float8) val); else *result = pllua_float8_get_datum(L, (float8) uval); return true; case BOOLOID: *result = BoolGetDatum( (val != 0) ); return true; case OIDOID: if (uval == (uint64)(Oid)uval) *result = ObjectIdGetDatum( (Oid)uval ); else *errstr = "oid value out of range"; return true; case INT2OID: if (s64 ? (val >= PG_INT16_MIN && val <= PG_INT16_MAX) : (uval <= PG_INT16_MAX)) *result = Int16GetDatum(val); else *errstr = "smallint value out of range"; return true; case INT4OID: if (s64 ? (val >= PG_INT32_MIN && val <= PG_INT32_MAX) : (uval <= PG_INT32_MAX)) *result = Int32GetDatum(val); else *errstr = "integer value out of range"; return true; case INT8OID: if (s64 || uval <= PG_INT64_MAX) *result = pllua_int64_get_datum(L, val); else *errstr = "bigint value out of range"; return true; case NUMERICOID: PLLUA_TRY(); { if (s64 || uval <= PG_INT64_MAX) *result = DirectFunctionCall1(int8_numeric, Int64GetDatumFast(val)); else { char str[32]; snprintf(str, 32, UINT64_FORMAT, uval); *result = DirectFunctionCall3(numeric_in, CStringGetDatum(str), ObjectIdGetDatum(InvalidOid), Int32GetDatum(-1)); } } PLLUA_CATCH_RETHROW(); return true; } } return false; #endif default: return false; } } /* * Make the datum at "nd" hold a reference to the one on the stack top. */ static void pllua_datum_reference(lua_State *L, int nd) { ASSERT_LUA_CONTEXT; pllua_set_user_field(L, nd, ".datumref"); } static int pllua_datum_gc(lua_State *L) { pllua_datum *p = lua_touserdata(L, 1); if (!p || !p->need_gc || !DatumGetPointer(p->value)) return 0; ASSERT_LUA_CONTEXT; /* * Don't retry if something goes south. */ p->need_gc = false; /* * Remove our metatable. There are ways (using keys of ephemeron tables) * that Lua code can hold on to references to post-finalized objects; this * makes sure that all they end up seeing is an opaque and inert userdata. */ lua_pushnil(L); lua_setmetatable(L, 1); PLLUA_TRY(); { if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(p->value))) { pllua_debug(L, "pllua_datum_gc: expanded object %p", DatumGetPointer(p->value)); DeleteExpandedObject(p->value); } else if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(p->value))) { /* how'd this get here? */ elog(ERROR, "unexpected expanded datum"); } else { pllua_debug(L, "pllua_datum_gc: flat object %p", DatumGetPointer(p->value)); pfree(DatumGetPointer(p->value)); } } PLLUA_CATCH_RETHROW(); return 0; } pllua_typeinfo *pllua_totypeinfo(lua_State *L, int nd) { void **p = pllua_torefobject(L, nd, PLLUA_TYPEINFO_OBJECT); return p ? *p : NULL; } pllua_typeinfo *pllua_checktypeinfo(lua_State *L, int nd, bool revalidate) { pllua_typeinfo *t = *pllua_checkrefobject(L, nd, PLLUA_TYPEINFO_OBJECT); if (!t) luaL_error(L, "invalid typeinfo"); if (!revalidate || !t->revalidate || t->obsolete || t->modified) return t; lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) t->typeoid); lua_pushinteger(L, (lua_Integer) t->typmod); lua_call(L, 2, 0); /* discard result, we don't need it */ /* t->revalidate must have been cleared by cache replacement */ Assert(!t->revalidate); return t; } /* * check that the item at "nd" is a datum whose typeinfo is "td" * (caller must have already checked that it really is a typeinfo) */ pllua_datum *pllua_todatum(lua_State *L, int nd, int td) { void *p = lua_touserdata(L, nd); td = lua_absindex(L, td); if (p != NULL) { if (lua_getmetatable(L, nd)) { lua_getuservalue(L, td); if (!lua_rawequal(L, -1, -2)) p = NULL; lua_pop(L, 2); return p; } } return NULL; } pllua_datum *pllua_checkdatum(lua_State *L, int nd, int td) { pllua_datum *p = pllua_todatum(L, nd, td); if (!p) luaL_argerror(L, nd, "datum"); return p; } /* * check that the item at "nd" is a datum, and also (if it is) push its * typeinfo and return it (else push nothing) */ pllua_datum *pllua_toanydatum(lua_State *L, int nd, pllua_typeinfo **ti) { pllua_typeinfo *t = NULL; void *p = lua_touserdata(L,nd); nd = lua_absindex(L,nd); if (p) { if (lua_getmetatable(L, nd)) { if (lua_getfield(L, -1, "typeinfo") != LUA_TUSERDATA) { lua_pop(L, 2); return NULL; } t = pllua_totypeinfo(L, -1); if (!t) { lua_pop(L, 2); return NULL; } lua_insert(L, -2); lua_getuservalue(L, -2); if (!lua_rawequal(L, -1, -2)) { lua_pop(L, 3); return NULL; } lua_pop(L, 2); if (t->revalidate) { lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) t->typeoid); lua_pushinteger(L, (lua_Integer) t->typmod); lua_call(L, 2, 0); /* discard result, we don't need it */ } if (ti) *ti = t; return p; } } return NULL; } pllua_datum *pllua_checkanydatum(lua_State *L, int nd, pllua_typeinfo **ti) { pllua_datum *p = pllua_toanydatum(L, nd, ti); if (!p) luaL_argerror(L, nd, "datum"); return p; } pllua_datum *pllua_newdatum(lua_State *L, int nt, Datum value) { pllua_datum *d; pllua_typeinfo *t = pllua_checktypeinfo(L, nt, false); lua_pushvalue(L, nt); d = lua_newuserdata(L, sizeof(pllua_datum)); #if MANDATORY_USERVALUE lua_newtable(L); lua_setuservalue(L, -2); #endif d->value = value; d->typmod = -1; d->need_gc = false; d->modified = false; /* * If this is a record type of unknown structure but known value, see about * replacing the caller-supplied typeinfo with one that reflects the actual * value. * * XXX revisit when certain pg issues are addressed. */ if (t->is_anonymous_record && value != (Datum)0) { Oid typeid; int32 typmod; pllua_get_tuple_type(L, value, &typeid, &typmod); lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) typeid); lua_pushinteger(L, (lua_Integer) typmod); lua_call(L, 2, 1); if (!lua_isnil(L, -1)) { t = pllua_checktypeinfo(L, -1, false); lua_replace(L, -3); } } lua_getuservalue(L, -2); lua_setmetatable(L, -2); lua_remove(L, -2); return d; } /* * Caller should have already written the value into d->value. */ void pllua_savedatum(lua_State *L, pllua_datum *d, pllua_typeinfo *t) { Datum nv; ASSERT_PG_CONTEXT; if (t->typbyval) return; if (t->typlen != -1) { nv = datumCopy(d->value, false, t->typlen); d->value = nv; d->need_gc = true; pllua_record_gc_debt(L, t->typlen); return; } /* * Varlena type, which may need detoast. For record types, we may need to * detoast internal fields. */ if (t->natts >= 0) { HeapTupleHeader htup = (HeapTupleHeader) DatumGetPointer(d->value); HeapTupleData tuple; /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(htup); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_tableOid = InvalidOid; tuple.t_data = htup; nv = heap_copy_tuple_as_datum(&tuple, t->tupdesc); d->value = nv; } else if (t->is_array) { if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d->value))) { /* * read/write pointer to an expanded array; we should be safe to * just own it. */ nv = TransferExpandedObject(d->value, CurrentMemoryContext); d->value = nv; } else { /* * Otherwise, expand it into the current memory context. */ nv = expand_array(d->value, CurrentMemoryContext, &t->array_meta); d->value = nv; } } else { nv = PointerGetDatum(PG_DETOAST_DATUM_COPY(d->value)); d->value = nv; } pllua_record_gc_debt(L, toast_datum_size(d->value)); d->need_gc = true; return; } /* * We should avoid saving individual datums retail, but it happens anyway. */ void pllua_save_one_datum(lua_State *L, pllua_datum *d, pllua_typeinfo *t) { ASSERT_LUA_CONTEXT; PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); pllua_savedatum(L, d, t); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); } /* * __tostring(d) returns the string representation of the datum. * * We get the typeinfo object from the closure. */ static int pllua_datum_tostring(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_typeinfo *t = pllua_checktypeinfo(L, lua_upvalueindex(1), true); const char *volatile str = NULL; ASSERT_LUA_CONTEXT; if (d->modified) { /* form a new datum by imploding the arg */ lua_pushvalue(L, lua_upvalueindex(1)); lua_pushvalue(L, 1); lua_call(L, 1, 1); d = pllua_checkdatum(L, -1, lua_upvalueindex(1)); } PLLUA_TRY(); { str = pllua_typeinfo_raw_output(L, d->value, t); } PLLUA_CATCH_RETHROW(); if (str) lua_pushstring(L, str); else lua_pushnil(L); /* should never happen? */ return 1; } /* * _tobinary(d) returns the binary-protocol representation of the datum. * * We get the typeinfo object from the closure. * * CAVEAT: some types will render text parts of the result into the current * client encoding. */ static int pllua_datum_tobinary(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_typeinfo *t = pllua_checktypeinfo(L, lua_upvalueindex(1), true); bytea *volatile res = NULL; volatile bool done = false; ASSERT_LUA_CONTEXT; if (d->modified) { /* form a new datum by imploding the arg */ lua_pushvalue(L, lua_upvalueindex(1)); lua_pushvalue(L, 1); lua_call(L, 1, 1); d = pllua_checkdatum(L, -1, lua_upvalueindex(1)); } PLLUA_TRY(); { if ((OidIsValid(t->sendfuncid) && OidIsValid(t->sendfunc.fn_oid)) || pllua_typeinfo_iofunc(L, t, IOFunc_send)) { res = SendFunctionCall(&t->sendfunc, d->value); done = true; } } PLLUA_CATCH_RETHROW(); if (!done) luaL_error(L, "failed to find send function for type"); if (res) lua_pushlstring(L, VARDATA_ANY(res), VARSIZE_ANY_EXHDR(res)); else lua_pushnil(L); /* should never happen? */ return 1; } /* * Leaves the table on top of the stack. */ static void pllua_datum_deform_tuple(lua_State *L, int nd, pllua_datum *d, pllua_typeinfo *t) { HeapTupleHeader htup = (HeapTupleHeader) DatumGetPointer(d->value); Datum values[MaxTupleAttributeNumber + 1]; bool nulls[MaxTupleAttributeNumber + 1]; bool needsave[MaxTupleAttributeNumber + 1]; pllua_datum *savedatum[MaxTupleAttributeNumber + 1]; pllua_typeinfo *saveti[MaxTupleAttributeNumber + 1]; TupleDesc tupdesc = t->tupdesc; MemoryContext mcxt = pllua_get_memory_cxt(L); bool anysave = false; int i; nd = lua_absindex(L, nd); if (pllua_get_user_field(L, nd, ".deformed") == LUA_TTABLE) return; lua_pop(L, 1); if (luaL_getmetafield(L, nd, "attrtypes") != LUA_TTABLE) luaL_error(L, "mising attrtypes table"); lua_createtable(L, t->natts, 8); /* stack: attrtypes,table */ /* actually do the deform */ PLLUA_TRY(); { HeapTupleData tuple; /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(htup); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_tableOid = InvalidOid; tuple.t_data = htup; /* Break down the tuple into fields */ heap_deform_tuple(&tuple, tupdesc, values, nulls); /* * Fields with substructure that we know about, like composites, might * have been converted to short-varlena format. We need to convert them * back if so, since otherwise lots of stuff breaks. Such values can't * be non-copied "child" datums, but at least they must be small. * * On the other hand, we might encounter a compressed value, and we * have to expand that. * * We intentionally *don't* do this for arrays. We point at the original * value as an opaque blob until we need to deform or explode it, and at * that point we convert it to an expanded object. * * We don't look at the substructure of range types ourselves, but we * do allow calls to functions that will detoast a range if it is a * short varlena. So better to expand it once here than risk doing so * many times elsewhere. */ for (i = 0; i < t->natts; ++i) { Form_pg_attribute att = TupleDescAttr(tupdesc, i); char typtype = (att->attlen == -1) ? get_typtype(getBaseType(att->atttypid)) : '\0'; if (!nulls[i] && att->attlen == -1 && (att->atttypid == RECORDOID || typtype == TYPTYPE_RANGE || typtype == TYPTYPE_COMPOSITE) && VARATT_IS_EXTENDED(DatumGetPointer(values[i]))) { struct varlena *vl = (struct varlena *) DatumGetPointer(values[i]); values[i] = PointerGetDatum(heap_tuple_untoast_attr(vl)); needsave[i] = true; } else needsave[i] = false; } } PLLUA_CATCH_RETHROW(); /* stack: attrtypes,table */ for (i = 0; i < t->natts; ++i) { Form_pg_attribute att = TupleDescAttr(tupdesc, i); lua_rawgeti(L, -2, i+1); /* stack: attrtypes,table,typeinfo */ if (att->attisdropped) lua_pushboolean(L, 0); else if (nulls[i]) lua_pushboolean(L, 1); /* can't use the more natural "nil" */ else { pllua_typeinfo *newt = pllua_checktypeinfo(L, -1, false); pllua_datum *newd = pllua_newdatum(L, -1, values[i]); if (newt->typeoid != RECORDOID) newd->typmod = att->atttypmod; newd->need_gc = false; /* * the uservalue of the new datum points to the old one in order to * hold a reference, _regardless_ of whether we actually made a * copy. Otherwise, if we modify the copy we made, the parent tuple * is never informed of that fact, and so might get copied without * our changes. */ lua_pushvalue(L, nd); pllua_datum_reference(L, -2); if (needsave[i]) { saveti[i] = newt; savedatum[i] = newd; anysave = true; } } /* stack: attrtypes,table,typeinfo,value */ lua_rawseti(L, -3, i+1); lua_pop(L, 1); } if (anysave) { PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(mcxt); for (i = 0; i < t->natts; ++i) { if (needsave[i]) { pllua_datum *newd = savedatum[i]; void *oldp = DatumGetPointer(newd->value); pllua_savedatum(L, newd, saveti[i]); /* * We don't normally worry about freeing transient data, * but here it's likely to be worthwhile. */ pfree(oldp); } } MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); } /* stack: attrtypes,table */ /* handle oid column specially */ if (t->hasoid) { Oid oid = HeapTupleHeaderGetOid(htup); lua_pushinteger(L, (lua_Integer) oid); lua_setfield(L, -2, "oid"); } /* stack: attrtypes,table */ lua_pushvalue(L, -1); pllua_set_user_field(L, nd, ".deformed"); lua_remove(L, -2); } /* * Current tuple's deformed table is on top of the stack. */ static void pllua_datum_explode_tuple_inner(lua_State *L, int nd, pllua_datum *d, pllua_typeinfo *t); static void pllua_datum_explode_tuple_recurse(lua_State *L, pllua_datum *d, pllua_typeinfo *t) { int i; int natts = t->natts; luaL_checkstack(L, 20, NULL); /* need to check pg stack here because we recurse in lua context */ PLLUA_CHECK_PG_STACK_DEPTH(); for (i = 1; i <= natts; ++i) { if (lua_rawgeti(L, -1, i) == LUA_TUSERDATA) { pllua_typeinfo *et; pllua_datum *ed = pllua_toanydatum(L, -1, &et); /* * Datums at this level are handled by the caller, our job is to * handle datums of deeper levels. * * We don't handle arrays by explosion (instead using the * expanded-object representation) so no need to consider them * here. * * We need to explode nested tuples even when need_gc is true. * It should be impossible to get here if modified is already * true. */ Assert(!ed->modified); if (et->natts >= 0) { pllua_datum_deform_tuple(L, -2, ed, et); pllua_datum_explode_tuple_inner(L, -3, ed, et); lua_pop(L, 1); } lua_pop(L, 1); } lua_pop(L,1); } } /* * Deform (if needed) a datum, and then detach the column values from the * original record, which is then freed. (This is used when we want to modify * the datum.) * * Expects the result of deform on the stack top and leaves it there. */ static void pllua_datum_explode_tuple_inner(lua_State *L, int nd, pllua_datum *d, pllua_typeinfo *t) { int i; int natts = t->natts; /* must include dropped cols */ if (d->value == (Datum)0) return; nd = lua_absindex(L, nd); ASSERT_LUA_CONTEXT; /* * If a composite value is nested inside another, we might have already * deformed the inner value, in which case it has its own set of child * datums that depend on the outer tuple's storage. So recursively explode * all nested values before modifying anything. (Separate loop here to * handle the fact that we want to recurse from lua context, not pg * context.) * * (We can't just un-deform the child values, because * something might be holding references to their values.) */ pllua_datum_explode_tuple_recurse(L, d, t); /* * If this errors partway through, we may have saved some values but not * others, so cope. */ PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); for (i = 1; i <= natts; ++i) { if (lua_rawgeti(L, -1, i) == LUA_TUSERDATA) { pllua_typeinfo *et; pllua_datum *ed = pllua_toanydatum(L, -1, &et); if (!ed->need_gc && !ed->modified) { /* * nested child datums must have already been handled in * recursion above. Can't do the deref here since we're in * pg context; do that below. */ pllua_savedatum(L, ed, et); } lua_pop(L, 1); } lua_pop(L, 1); } if (d->need_gc) { void *oldval = DatumGetPointer(d->value); d->modified = true; d->need_gc = false; d->value = (Datum)0; pfree(oldval); } else { d->modified = true; d->value = (Datum)0; } MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); for (i = 1; i <= natts; ++i) { if (lua_rawgeti(L, -1, i) == LUA_TUSERDATA) { lua_pushnil(L); pllua_datum_reference(L, -2); } lua_pop(L, 1); } lua_pushnil(L); pllua_datum_reference(L, nd); } /* * Deform (if needed) a datum, and then detach the column values from the * original record, which is then freed. (This is used when we want to modify * the datum.) * * The tricky part of this is that if we're a dependent child tuple, we need to * go back and explode our parent, and it's parent, and so on, and recurse into * all children. So this function is split into three: * pllua_datum_explode_tuple proper handles finding the ultimate parent, * pllua_datum_explode_tuple_inner handles exploding one tuple, and * pllua_datum_explode_tuple_recurse handles exploding the children of one tuple * * Leaves the result of deform on the stack. */ static void pllua_datum_explode_tuple(lua_State *L, int nd, pllua_datum *d, pllua_typeinfo *t) { nd = lua_absindex(L, nd); ASSERT_LUA_CONTEXT; pllua_datum_deform_tuple(L, nd, d, t); if (d->value == (Datum)0) return; lua_pushvalue(L, nd); for (;;) { pllua_get_user_field(L, -1, ".datumref"); if (lua_isnil(L, -1)) break; lua_remove(L, -2); } lua_pop(L, 1); /* stack top is now the ultimate parent */ /* * If a composite value is nested inside another, we might have already * deformed the inner value, in which case it has its own set of child * datums that depend on the outer tuple's storage. So recursively explode * all nested values before modifying anything. (Separate loop here to * handle the fact that we want to recurse from lua context, not pg * context.) * * Likewise, we might be the child value of a parent, which will also need * exploding in order to inherit any of our changes. * * (We can't just un-deform the child values, because something might be * holding references to their values.) */ if (!lua_rawequal(L, -1, nd)) { pllua_typeinfo *parent_t; pllua_datum *parent_d = pllua_toanydatum(L, -1, &parent_t); pllua_datum_deform_tuple(L, -2, parent_d, parent_t); pllua_datum_explode_tuple_inner(L, -3, parent_d, parent_t); lua_pop(L, 3); /* pop deform, typeinfo, parent */ } else { lua_pop(L, 1); /* pop parent, deform is on stack top */ pllua_datum_explode_tuple_inner(L, nd, d, t); } /* our own deform is now on stack top */ } static bool pllua_datum_column(lua_State *L, int attno, bool skip_dropped) { switch (lua_geti(L, -1, attno)) { case LUA_TUSERDATA: { pllua_typeinfo *et; pllua_datum *ed = pllua_checkanydatum(L, -1, &et); if (pllua_value_from_datum(L, ed->value, et->basetype) == LUA_TNONE && pllua_datum_transform_fromsql(L, ed->value, -1, et) == LUA_TNONE) lua_pop(L,1); else { lua_remove(L,-2); lua_remove(L,-2); } } break; case LUA_TBOOLEAN: /* false is a dropped col; true is a present but null col */ if (skip_dropped && !lua_toboolean(L,-1)) { lua_pop(L,1); return false; } lua_pop(L,1); lua_pushnil(L); break; case LUA_TNIL: luaL_error(L, "missing attribute"); default: luaL_error(L, "unexpected type in datum cache"); } return true; } /* * __tostring(d) returns the string representation of an unregistered row. * * We get the typeinfo object from the closure. */ static int pllua_datum_row_tostring(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_typeinfo *t = pllua_checktypeinfo(L, lua_upvalueindex(1), true); int i; int natts = t->natts; luaL_Buffer b; const char *ptr; const char *str; size_t len; bool needquote; lua_settop(L, 1); pllua_datum_deform_tuple(L, 1, d, t); /* deform is at index 2 */ luaL_buffinit(L, &b); luaL_addchar(&b, '('); for (i = 1; i <= natts; ++i) { /* * we don't use pllua_datum_column here since we want the raw datum, * otherwise bytea etc. won't come out right. */ switch (lua_geti(L, 2, i)) { default: case LUA_TNIL: return luaL_error(L, "unexpected type in datum cache"); case LUA_TBOOLEAN: /* false is a dropped col; true is a present but null col */ if (!lua_toboolean(L, -1)) { lua_pop(L, 1); continue; } lua_pop(L, 1); if (i > 1) lua_pushliteral(L, ","); else lua_pushliteral(L, ""); break; case LUA_TUSERDATA: str = luaL_tolstring(L, -1, &len); lua_remove(L, -2); needquote = false; for (ptr = str; *ptr; ++ptr) { char ch = *ptr; if (ch == '"' || ch == '\\' || ch == '(' || ch == ')' || ch == ',' || isspace((unsigned char) ch)) { needquote = true; break; } } if (i > 1 || needquote) { luaL_Buffer nb; luaL_buffinit(L, &nb); if (i > 1) luaL_addchar(&nb, ','); if (!needquote) luaL_addlstring(&nb, str, len); else { luaL_addchar(&nb, '"'); for (ptr = str; *ptr; ++ptr) { char ch = *ptr; if (ch == '\\' || ch == '"') luaL_addchar(&nb, ch); luaL_addchar(&nb, ch); } luaL_addchar(&nb, '"'); } luaL_pushresult(&nb); lua_remove(L, -2); } break; } luaL_addvalue(&b); } luaL_addchar(&b, ')'); luaL_pushresult(&b); return 1; } static void pllua_datum_getattrs(lua_State *L, int nd) { if (luaL_getmetafield(L, nd, "attrs") != LUA_TTABLE) luaL_error(L, "missing attrs table"); } /* * __index(self,key) */ static int pllua_datum_row_index(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_typeinfo *t = pllua_totypeinfo(L, lua_upvalueindex(1)); lua_Integer attno; if (!d) luaL_error(L, "pllua_datum_index: not a datum object"); if (t->natts < 0) luaL_error(L, "datum is not a row type"); switch (lua_type(L, 2)) { default: lua_pushnil(L); return 1; case LUA_TSTRING: pllua_datum_getattrs(L, 1); /* stack: attrs{ attname = attno } */ lua_pushvalue(L, 2); if (lua_gettable(L, -2) != LUA_TNUMBER) luaL_error(L, "datum has no column \"%s\"", lua_tostring(L, 2)); /*FALLTHROUGH*/ case LUA_TNUMBER: /* column number */ attno = lua_tointeger(L, -1); if (IsObjectIdAttributeNumber(attno)) { if (!t->hasoid) luaL_error(L, "datum has no oid column"); } else if ((attno < 1 || attno > t->natts) || TupleDescAttr(t->tupdesc, attno-1)->attisdropped) luaL_error(L, "datum has no column number %d", attno); pllua_datum_deform_tuple(L, 1, d, t); if (IsObjectIdAttributeNumber(attno)) lua_getfield(L, -1, "oid"); else pllua_datum_column(L, attno, false); return 1; } } /* * __newindex(self,key,val) self[key] = val */ static int pllua_datum_row_newindex(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_typeinfo *t = pllua_totypeinfo(L, lua_upvalueindex(1)); lua_Integer attno; if (!d) luaL_error(L, "pllua_datum_newindex: not a datum object"); if (t->natts < 0) luaL_error(L, "datum is not a row type"); switch (lua_type(L, 2)) { default: luaL_error(L, "invalid type for key field"); return 0; case LUA_TSTRING: pllua_datum_getattrs(L, 1); /* stack: attrs{ attname = attno } */ lua_pushvalue(L, 2); if (lua_gettable(L, -2) != LUA_TNUMBER) luaL_error(L, "datum has no column \"%s\"", lua_tostring(L, 2)); lua_replace(L, 2); /*FALLTHROUGH*/ case LUA_TNUMBER: /* column number */ attno = lua_tointeger(L, 2); if (IsObjectIdAttributeNumber(attno)) { if (!t->hasoid) luaL_error(L, "datum has no oid column"); } else if ((attno < 1 || attno > t->natts) || TupleDescAttr(t->tupdesc, attno-1)->attisdropped) luaL_error(L, "datum has no column number %d", attno); pllua_datum_explode_tuple(L, 1, d, t); if (IsObjectIdAttributeNumber(attno)) { int isint = 0; lua_Integer newoid = lua_tointegerx(L, 3, &isint); if (!isint || (newoid != (lua_Integer)(Oid)newoid)) luaL_error(L, "invalid oid value"); lua_pushinteger(L, newoid); lua_setfield(L, -2, "oid"); } else { lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, TupleDescAttr(t->tupdesc, attno-1)->atttypid); lua_pushinteger(L, TupleDescAttr(t->tupdesc, attno-1)->atttypmod); lua_call(L, 2, 1); lua_pushvalue(L, 3); lua_call(L, 1, 1); lua_seti(L, -2, attno); } return 0; } } /* * Not exposed to the user directly, only as a closure over its index var * * upvalues: typeinfo, datum, index, deform, attrs */ static int pllua_datum_row_next(lua_State *L) { pllua_typeinfo *t = pllua_totypeinfo(L, lua_upvalueindex(1)); int idx = lua_tointeger(L, lua_upvalueindex(3)); /* don't need the original datum but do this for sanity check */ pllua_checkdatum(L, lua_upvalueindex(2), lua_upvalueindex(1)); lua_pushvalue(L, lua_upvalueindex(4)); for (++idx; idx <= t->natts; ++idx) { if (pllua_datum_column(L, idx, true)) { lua_pushinteger(L, idx); lua_replace(L, lua_upvalueindex(3)); lua_geti(L, lua_upvalueindex(5), idx); lua_insert(L, -2); lua_pushinteger(L, idx); return 3; } } lua_pushinteger(L, idx); lua_replace(L, lua_upvalueindex(3)); return 0; } static int pllua_datum_row_pairs(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_typeinfo *t = pllua_checktypeinfo(L, lua_upvalueindex(1), false); if (t->natts < 0) luaL_error(L, "pairs(): datum is not a rowtype"); lua_pushvalue(L, lua_upvalueindex(1)); lua_pushvalue(L, 1); lua_pushinteger(L, 0); pllua_datum_deform_tuple(L, 1, d, t); pllua_datum_getattrs(L, 1); lua_pushcclosure(L, pllua_datum_row_next, 5); lua_pushnil(L); lua_pushnil(L); return 3; } static int pllua_datum_row_len(lua_State *L) { pllua_typeinfo *t = pllua_checktypeinfo(L, lua_upvalueindex(1), false); pllua_checkdatum(L, 1, lua_upvalueindex(1)); if (t->natts < 0) luaL_error(L, "attempt to get length of a non-rowtype datum"); /* length is the arity, not natts, because we skip dropped columns */ lua_pushinteger(L, t->arity); return 1; } /* * __call(row) * __call(row,func) * __call(row,nullvalue) * __call(row,configtable) * * mapfunc is function(k,v,n,d) * * * Apply a mapping to the row and return the result as a Lua table. */ static int pllua_datum_row_map(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_typeinfo *t = pllua_checktypeinfo(L, lua_upvalueindex(1), false); int funcidx = 0; int nullvalue = 0; bool noresult = false; lua_Integer attno = 0; PLLUA_CHECK_PG_STACK_DEPTH(); lua_settop(L, 2); if (t->natts < 0) luaL_error(L, "datum is not a row type"); switch (lua_type(L, 2)) { case LUA_TTABLE: if (lua_getfield(L, 2, "map") == LUA_TFUNCTION) { funcidx = lua_absindex(L, -1); /* leave on stack */ } else lua_pop(L, 1); if (lua_getfield(L, 2, "discard") && lua_toboolean(L, -1)) noresult = true; lua_pop(L, 1); lua_getfield(L, 2, "null"); nullvalue = lua_absindex(L, -1); break; case LUA_TFUNCTION: funcidx = 2; break; case LUA_TNIL: break; default: nullvalue = 2; break; } if (!noresult) lua_newtable(L); pllua_datum_getattrs(L, 1); pllua_datum_deform_tuple(L, 1, d, t); /* stack: [table] attrs deform */ for (++attno; attno <= t->natts; ++attno) { if (pllua_datum_column(L, attno, true)) { /* stack: [table] attrs deform value */ lua_geti(L, -3, attno); lua_insert(L, -2); /* stack: [table] attrs deform key value */ if (nullvalue && lua_isnil(L, -1)) { lua_pop(L, 1); lua_pushvalue(L, nullvalue); } if (funcidx) { lua_pushvalue(L, funcidx); lua_insert(L, -2); /* ... deform key func value */ lua_pushvalue(L, -3); /* ... deform key func value key */ lua_insert(L, -2); /* ... deform key func key value */ lua_pushinteger(L, attno); lua_pushvalue(L, 1); /* ... deform key func key value attno datum */ lua_call(L, 4, 1); /* ... [table] attrs deform key newvalue */ } if (!noresult) lua_settable(L, -5); else lua_pop(L, 2); } } lua_pop(L, 2); return noresult ? 0 : 1; } struct idxlist { int ndim; /* ndim of parent array */ int cur_dim; /* dim 1..ndim of max currently specified index */ int idx[MAXDIM]; /* specified indexes [dim-1] */ }; static struct idxlist * pllua_datum_array_make_idxlist(lua_State *L, int nd, struct idxlist *idxlist) { struct idxlist *nlist = pllua_newobject(L, PLLUA_IDXLIST_OBJECT, sizeof(struct idxlist), true); *nlist = *idxlist; lua_pushvalue(L, nd); pllua_set_user_field(L, -2, "datum"); return nlist; } static int pllua_datum_idxlist_index(lua_State *L) { struct idxlist *idxlist = pllua_toobject(L, 1, PLLUA_IDXLIST_OBJECT); int idx = luaL_checkinteger(L, 2); pllua_get_user_field(L, 1, "datum"); idxlist = pllua_datum_array_make_idxlist(L, lua_absindex(L, -1), idxlist); idxlist->idx[idxlist->cur_dim++] = idx; if (idxlist->cur_dim >= idxlist->ndim) lua_gettable(L, -2); return 1; } static int pllua_datum_idxlist_newindex(lua_State *L) { struct idxlist *idxlist = pllua_toobject(L, 1, PLLUA_IDXLIST_OBJECT); int idx = luaL_checkinteger(L, 2); luaL_checkany(L, 3); pllua_get_user_field(L, 1, "datum"); idxlist = pllua_datum_array_make_idxlist(L, lua_absindex(L, -1), idxlist); idxlist->idx[idxlist->cur_dim++] = idx; if (idxlist->cur_dim != idxlist->ndim) luaL_error(L, "incorrect number of dimensions in array assignment (expected %d got %d)", idxlist->ndim, idxlist->cur_dim); lua_pushvalue(L, 3); lua_settable(L, -2); return 0; } static int pllua_datum_idxlist_len(lua_State *L) { pllua_checkobject(L, 1, PLLUA_IDXLIST_OBJECT); pllua_get_user_field(L, 1, "datum"); if (luaL_getmetafield(L, -1, "__len") == LUA_TNIL) luaL_error(L, "array len error"); lua_pushvalue(L, -2); lua_pushvalue(L, 1); lua_call(L, 2, 1); return 1; } static int pllua_datum_array_next(lua_State *L); static ExpandedArrayHeader * pllua_datum_array_value(lua_State *L, pllua_datum *d, pllua_typeinfo *t) { /* Switch to expanded representation if we haven't already. */ if (!VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d->value))) { PLLUA_TRY(); { d->value = expand_array(d->value, pllua_get_memory_cxt(L), &t->array_meta); pllua_record_gc_debt(L, toast_datum_size(d->value)); d->need_gc = true; } PLLUA_CATCH_RETHROW(); } return (ExpandedArrayHeader *) DatumGetEOHP(d->value); } static int pllua_datum_idxlist_pairs(lua_State *L) { struct idxlist *idxlist = pllua_toobject(L, 1, PLLUA_IDXLIST_OBJECT); pllua_datum *d; pllua_typeinfo *t; ExpandedArrayHeader *arr; pllua_get_user_field(L, 1, "datum"); d = pllua_checkanydatum(L, -1, &t); /* stack: ... datum typeinfo */ arr = pllua_datum_array_value(L, d, t); lua_pushvalue(L, -1); lua_pushvalue(L, 1); lua_pushinteger(L, arr->lbound[idxlist->cur_dim]); lua_pushinteger(L, arr->lbound[idxlist->cur_dim] + arr->dims[idxlist->cur_dim]); lua_pushcclosure(L, pllua_datum_array_next, 4); lua_pushnil(L); lua_pushnil(L); return 3; } static struct luaL_Reg idxlist_mt[] = { { "__index", pllua_datum_idxlist_index }, { "__newindex", pllua_datum_idxlist_newindex }, { "__len", pllua_datum_idxlist_len }, { "__pairs", pllua_datum_idxlist_pairs }, { "__ipairs", pllua_datum_idxlist_pairs }, { NULL, NULL } }; int pllua_datum_single(lua_State *L, Datum res, bool isnull, int nt, pllua_typeinfo *t) { pllua_datum *newd; nt = lua_absindex(L, nt); if (isnull) lua_pushnil(L); else if (pllua_value_from_datum(L, res, t->basetype) == LUA_TNONE && pllua_datum_transform_fromsql(L, res, nt, t) == LUA_TNONE) { newd = pllua_newdatum(L, nt, res); pllua_save_one_datum(L, newd, t); } return 1; } /* * __index(self,key) */ static int pllua_datum_array_index(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_typeinfo *t = pllua_totypeinfo(L, lua_upvalueindex(1)); pllua_typeinfo *et = pllua_totypeinfo(L, lua_upvalueindex(2)); struct idxlist d_idxlist; struct idxlist *idxlist = NULL; ExpandedArrayHeader *arr; bool isnull = false; const char *str = NULL; volatile Datum res; if (!t->is_array) luaL_error(L, "datum is not an array type"); if (lua_isinteger(L, 2)) { d_idxlist.idx[0] = lua_tointeger(L, 2); d_idxlist.cur_dim = 1; } else if ((str = lua_tostring(L, 2)) && luaL_getmetafield(L, 1, "__methods") != LUA_TNIL) { lua_getfield(L, -1, str); return 1; } else if (!(idxlist = pllua_toobject(L, 2, PLLUA_IDXLIST_OBJECT))) { luaL_argerror(L, 2, NULL); } arr = pllua_datum_array_value(L, d, t); if (idxlist) { pllua_get_user_field(L, 2, "datum"); if (idxlist->ndim != arr->ndims || idxlist->cur_dim != arr->ndims || !lua_rawequal(L, -1, 1)) luaL_argerror(L, 2, "wrong idxlist"); lua_pop(L, 1); } else if (arr->ndims > 1) { d_idxlist.ndim = arr->ndims; pllua_datum_array_make_idxlist(L, 1, &d_idxlist); return 1; } else idxlist = &d_idxlist; PLLUA_TRY(); { res = array_get_element(d->value, idxlist->cur_dim, idxlist->idx, t->typlen, t->elemtyplen, t->elemtypbyval, t->elemtypalign, &isnull); } PLLUA_CATCH_RETHROW(); pllua_datum_single(L, res, isnull, lua_upvalueindex(2), et); return 1; } /* * __newindex(self,key,val) self[key] = val */ static int pllua_datum_array_newindex(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_typeinfo *t = pllua_totypeinfo(L, lua_upvalueindex(1)); struct idxlist d_idxlist; struct idxlist *idxlist = NULL; ExpandedArrayHeader *arr; pllua_datum *nd; if (!t->is_array) luaL_error(L, "datum is not an array type"); if (lua_isinteger(L, 2)) { d_idxlist.idx[0] = lua_tointeger(L, 2); d_idxlist.cur_dim = 1; idxlist = &d_idxlist; } else { idxlist = pllua_toobject(L, 2, PLLUA_IDXLIST_OBJECT); if (!idxlist) luaL_argerror(L, 2, "integer"); } /* * If we came from a row object's deform, then explode the source row; * otherwise, it would not pick up our changes and the result of * row.arraycol[i] = n would not be reflected in "row" */ if (pllua_get_user_field(L, 1, ".datumref") != LUA_TNIL) { pllua_typeinfo *parent_t; pllua_datum *parent_d = pllua_checkanydatum(L, -1, &parent_t); pllua_datum_explode_tuple(L, -2, parent_d, parent_t); lua_pop(L, 3); } else lua_pop(L, 1); arr = pllua_datum_array_value(L, d, t); if (idxlist->cur_dim < arr->ndims) luaL_error(L, "not enough subscripts for array"); else if (idxlist->cur_dim > arr->ndims && arr->ndims > 0) luaL_error(L, "too many subscripts for array"); lua_pushvalue(L, lua_upvalueindex(2)); lua_pushvalue(L, 3); lua_call(L, 1, 1); if (!lua_isnil(L, -1)) nd = pllua_todatum(L, -1, lua_upvalueindex(2)); else nd = NULL; PLLUA_TRY(); { bool isnull = (nd == NULL); Datum val = nd ? nd->value : (Datum)0; Datum res PG_USED_FOR_ASSERTS_ONLY; res = array_set_element(d->value, idxlist->cur_dim, idxlist->idx, val, isnull, t->typlen, t->elemtyplen, t->elemtypbyval, t->elemtypalign); Assert(res == d->value); } PLLUA_CATCH_RETHROW(); return 0; } /* * __len(self[,idxlist]) */ static int pllua_datum_array_len(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_typeinfo *t = pllua_totypeinfo(L, lua_upvalueindex(1)); struct idxlist *idxlist = pllua_toobject(L, 2, PLLUA_IDXLIST_OBJECT); int reqdim = (idxlist) ? idxlist->cur_dim + 1 : 1; ExpandedArrayHeader *arr; int res; if (!t->is_array) luaL_error(L, "datum is not an array type"); if (!idxlist && !lua_isnoneornil(L, 2) && !lua_rawequal(L, 1, 2)) luaL_argerror(L, 2, "incorrect type"); arr = pllua_datum_array_value(L, d, t); if (arr->ndims < 1 || reqdim > arr->ndims) res = 0; else res = arr->lbound[reqdim - 1] + arr->dims[reqdim - 1] - 1; lua_pushinteger(L, res); return 1; } /* * Not exposed to the user directly, only as a closure over its index var * * upvalues: typeinfo, datum or idxlist, index, ubound */ static int pllua_datum_array_next(lua_State *L) { int idx = lua_tointeger(L, lua_upvalueindex(3)); int ubound = lua_tointeger(L, lua_upvalueindex(4)); if (idx >= ubound) return 0; lua_pushinteger(L, idx+1); lua_replace(L, lua_upvalueindex(3)); lua_pushinteger(L, idx); lua_pushvalue(L, lua_upvalueindex(2)); lua_geti(L, -1, idx); lua_remove(L, -2); return 2; } static int pllua_datum_array_pairs(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_typeinfo *t = pllua_checktypeinfo(L, lua_upvalueindex(1), false); ExpandedArrayHeader *arr; if (!t->is_array) luaL_error(L, "datum is not an array type"); arr = pllua_datum_array_value(L, d, t); lua_pushvalue(L, lua_upvalueindex(1)); lua_pushvalue(L, 1); if (arr->ndims < 1) { lua_pushinteger(L, 0); lua_pushinteger(L, 0); } else { lua_pushinteger(L, arr->lbound[0]); lua_pushinteger(L, arr->lbound[0] + arr->dims[0]); } lua_pushcclosure(L, pllua_datum_array_next, 4); lua_pushnil(L); lua_pushnil(L); return 3; } /* * __call(array) * __call(array,func) * __call(array,nullval) * __call(array,configtable) * * configtable: * mapfunc = function(e,a,i,j,k,...) * noresult = boolean, if true the result of map is discarded * nullvalue = any * * map(array,func) * * Calls func on every element of array and returns the results as a Lua table * (NOT an array). * * mapnull(array,nullval) * table(array) * * Converts array to a Lua table optionally replacing all null values by * "nullval" * * These are actually all the same function, the presence and argument type of * arg 2 determines which. */ static int pllua_datum_array_map(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_typeinfo *t = pllua_totypeinfo(L, lua_upvalueindex(1)); pllua_typeinfo *et = pllua_totypeinfo(L, lua_upvalueindex(2)); struct idxlist idxlist; ExpandedArrayHeader *arr; array_iter iter; int index; int nstack; int ndim; int nelems; int i; int funcidx = 0; int nullvalue = 0; bool noresult = false; PLLUA_CHECK_PG_STACK_DEPTH(); lua_settop(L, 2); if (!t->is_array) luaL_error(L, "datum is not an array type"); switch (lua_type(L, 2)) { case LUA_TTABLE: if (lua_getfield(L, 2, "map") == LUA_TFUNCTION) { funcidx = lua_absindex(L, -1); /* leave on stack */ } else lua_pop(L, 1); if (lua_getfield(L, 2, "discard") && lua_toboolean(L, -1)) noresult = true; lua_pop(L, 1); lua_getfield(L, 2, "null"); nullvalue = lua_absindex(L, -1); break; case LUA_TFUNCTION: funcidx = 2; break; case LUA_TNIL: break; default: nullvalue = 2; break; } arr = pllua_datum_array_value(L, d, t); ndim = arr->ndims; nelems = ArrayGetNItems(ndim, arr->dims); if (ndim < 1 || nelems < 1) { if (!noresult) lua_newtable(L); return noresult ? 0 : 1; } /* * We create a stack of tables per dimension: * * t1 t2 t3 ... * * At each step, we append the current value to the top table on the stack. * When we reach the end of a dimension, the top table is appended to the * next one down, as needed, and then new tables created until we get back * to the right depth. */ array_iter_setup(&iter, (AnyArrayType *) arr); for (nstack = 0, index = 0; index < nelems; ++index) { Datum val; bool isnull = false; /* stack up new tables to the required depth */ while (nstack < ndim) { if (!noresult) lua_createtable(L, arr->dims[nstack], 0); idxlist.idx[nstack] = 0; /* lbound added later */ ++nstack; } val = array_iter_next(&iter, &isnull, index, et->typlen, et->typbyval, et->typalign); pllua_datum_single(L, val, isnull, lua_upvalueindex(2), et); if (nullvalue && lua_isnil(L, -1)) { lua_pop(L, 1); lua_pushvalue(L, nullvalue); } if (funcidx) { lua_pushvalue(L, funcidx); lua_insert(L, -2); lua_pushvalue(L, 1); for (i = 0; i < ndim; ++i) lua_pushinteger(L, idxlist.idx[i] + arr->lbound[i]); lua_call(L, 2+ndim, 1); } if (!noresult) lua_seti(L, -2, idxlist.idx[nstack-1] + arr->lbound[nstack-1]); for (i = nstack - 1; i >= 0; --i) { if ((idxlist.idx[i] = (idxlist.idx[i] + 1) % arr->dims[i])) break; else if (i > 0) { --nstack; if (!noresult) lua_seti(L, -2, idxlist.idx[nstack-1] + arr->lbound[nstack-1]); } } } return noresult ? 0 : 1; } /* * deform a range value and cache the details */ static void pllua_datum_range_deform(lua_State *L, int nd, int nte, pllua_datum *d, pllua_typeinfo *t, pllua_typeinfo *et) { RangeType *r1; TypeCacheEntry *typcache; RangeBound lower; RangeBound upper; bool empty; pllua_datum *ld = NULL; pllua_datum *ud = NULL; nd = lua_absindex(L, nd); nte = lua_absindex(L, nte); PLLUA_TRY(); { r1 = DatumGetRangeTypeP(d->value); typcache = lookup_type_cache(t->typeoid, TYPECACHE_RANGE_INFO); if (typcache->rngelemtype == NULL) elog(ERROR, "type %u is not a range type", t->typeoid); range_deserialize(typcache, r1, &lower, &upper, &empty); } PLLUA_CATCH_RETHROW(); lua_createtable(L, 0, 8); lua_pushboolean(L, empty); lua_setfield(L, -2, "isempty"); if (empty) { lua_pushlightuserdata(L, (void*)0); lua_setfield(L, -2, "lower"); lua_pushlightuserdata(L, (void*)0); lua_setfield(L, -2, "upper"); lua_pushboolean(L, false); lua_setfield(L, -2, "lower_inc"); lua_pushboolean(L, false); lua_setfield(L, -2, "upper_inc"); lua_pushboolean(L, false); lua_setfield(L, -2, "lower_inf"); lua_pushboolean(L, false); lua_setfield(L, -2, "upper_inf"); return; } lua_pushboolean(L, lower.inclusive); lua_setfield(L, -2, "lower_inc"); lua_pushboolean(L, lower.infinite); lua_setfield(L, -2, "lower_inf"); if (lower.infinite) lua_pushlightuserdata(L, (void*)0); else ld = pllua_newdatum(L, nte, lower.val); lua_pushboolean(L, upper.inclusive); lua_setfield(L, -3, "upper_inc"); lua_pushboolean(L, upper.infinite); lua_setfield(L, -3, "upper_inf"); if (upper.infinite) lua_pushlightuserdata(L, (void*)0); else ud = pllua_newdatum(L, nte, upper.val); PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); if (ld) pllua_savedatum(L, ld, et); if (ud) pllua_savedatum(L, ud, et); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); lua_setfield(L, -3, "upper"); lua_setfield(L, -2, "lower"); lua_pushvalue(L, -1); pllua_set_user_field(L, nd, ".deformed"); } /* * __index(range,idx) * * Provide virtual columns .lower, .upper, .isempty, etc. * * Upvalue 1 is the typeinfo, 2 the element typeinfo */ static int pllua_datum_range_index(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_typeinfo *t = pllua_totypeinfo(L, lua_upvalueindex(1)); pllua_typeinfo *et = pllua_totypeinfo(L, lua_upvalueindex(2)); const char *str = luaL_checkstring(L, 2); if (pllua_get_user_field(L, 1, ".deformed") != LUA_TTABLE) { lua_pop(L, 1); pllua_datum_range_deform(L, 1, lua_upvalueindex(2), d, t, et); } switch (lua_getfield(L, -1, str)) { case LUA_TNIL: return 1; /* no such field */ case LUA_TLIGHTUSERDATA: lua_pushnil(L); return 1; /* dummy null */ default: return 1; } } static int pllua_datum_noindex(lua_State *L) { pllua_typeinfo *t = pllua_totypeinfo(L, lua_upvalueindex(1)); if (t->is_anonymous_record) return luaL_error(L, "cannot access fields from a record of unknown structure"); else return luaL_error(L, "datum is not an indexable type"); } static struct luaL_Reg datumobj_base_mt[] = { /* __gc entry is handled separately */ { "__tostring", pllua_datum_tostring }, { "__index", pllua_datum_noindex }, { "_tobinary", pllua_datum_tobinary }, { NULL, NULL } }; static struct luaL_Reg datumobj_unreg_row_mt[] = { { "__tostring", pllua_datum_row_tostring }, { NULL, NULL } }; static struct luaL_Reg datumobj_row_mt[] = { { "__len", pllua_datum_row_len }, { "__index", pllua_datum_row_index }, { "__newindex", pllua_datum_row_newindex }, { "__pairs", pllua_datum_row_pairs }, { "__call", pllua_datum_row_map }, { NULL, NULL } }; static struct luaL_Reg datumobj_range_mt[] = { { "__index", pllua_datum_range_index }, { NULL, NULL } }; static struct luaL_Reg datumobj_array_methods[] = { { "table", pllua_datum_array_map }, { "map", pllua_datum_array_map }, { "mapnull", pllua_datum_array_map }, { NULL, NULL } }; static struct luaL_Reg datumobj_array_mt[] = { { "__len", pllua_datum_array_len }, { "__pairs", pllua_datum_array_pairs }, { "__ipairs", pllua_datum_array_pairs }, { "__index", pllua_datum_array_index }, { "__newindex", pllua_datum_array_newindex }, { "__call", pllua_datum_array_map }, { NULL, NULL } }; /* * This entry point allows constructing a typeinfo for an anonymous tupdesc, if * that turns out to be useful. * * If oid==RECORDOID and typmod==-1 and tupdesc==NULL then we need to construct * a typeinfo that works for all record types but does not allow them to be * indexed into. * * This returns NULL (with an empty typeinfo on stack) without error if the * type doesn't exist; caller beware. */ pllua_typeinfo *pllua_newtypeinfo_raw(lua_State *L, Oid oid, int32 typmod, TupleDesc in_tupdesc) { void **p = pllua_newrefobject(L, PLLUA_TYPEINFO_OBJECT, NULL, true); pllua_typeinfo *t = NULL; pllua_typeinfo *volatile nt; Oid langoid = InvalidOid; ASSERT_LUA_CONTEXT; if (oid != RECORDOID) { lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_LANG_OID); langoid = (Oid) lua_tointeger(L, -1); lua_pop(L, 1); } PLLUA_TRY(); { MemoryContext mcxt; MemoryContext oldcontext; Oid basetype; int32 basetypmod = -1; /* XXX revisit */ Oid elemtype; char typtype; TupleDesc tupdesc = NULL; /* * Break out the RECORDOID case since we can skip a lot of pointless * cache lookups for what needs to be a reasonably fast path (since * we're going to do it for every SPI result set). */ if (oid == RECORDOID) { if (typmod >= 0) { tupdesc = lookup_rowtype_tupdesc_noerror(oid, typmod, true); if (!tupdesc) goto out; /* note: in this case we still hold a pin on tupdesc, see below */ } else tupdesc = in_tupdesc; basetypmod = typmod; basetype = RECORDOID; elemtype = InvalidOid; typtype = TYPTYPE_PSEUDO; } else { if (!SearchSysCacheExists(TYPEOID, ObjectIdGetDatum(oid), 0, 0, 0)) goto out; basetype = getBaseTypeAndTypmod(oid, &basetypmod); elemtype = get_element_type(basetype); typtype = get_typtype(basetype); } mcxt = AllocSetContextCreate(CurrentMemoryContext, "pllua type object", ALLOCSET_SMALL_SIZES); oldcontext = MemoryContextSwitchTo(mcxt); t = palloc0(sizeof(pllua_typeinfo)); t->mcxt = mcxt; t->typeoid = oid; t->typmod = typmod; t->tupdesc = NULL; t->arity = 1; t->natts = -1; t->hasoid = false; t->revalidate = false; t->obsolete = false; t->modified = false; t->reloid = InvalidOid; t->basetype = basetype; t->basetypmod = basetypmod; t->array_meta.element_type = InvalidOid; t->coerce_typmod = false; t->coerce_typmod_element = false; t->typmod_funcid = InvalidOid; t->elemtype = elemtype; t->rangetype = InvalidOid; t->is_enum = (typtype == TYPTYPE_ENUM); t->is_anonymous_record = false; t->nested_unknowns = false; t->nested_composites = false; /* * Must look at the base type for typmod coercions */ if (basetype != RECORDOID) { switch (find_typmod_coercion_function(basetype, &t->typmod_funcid)) { default: case COERCION_PATH_NONE: break; case COERCION_PATH_ARRAYCOERCE: t->coerce_typmod_element = true; /*FALLTHROUGH*/ case COERCION_PATH_FUNC: t->coerce_typmod = true; break; } } if (oid == RECORDOID && typmod >= 0) { /* we looked up the tupdesc above but didn't copy or release it */ t->tupdesc = CreateTupleDescCopy(tupdesc); ReleaseTupleDesc(tupdesc); tupdesc = t->tupdesc; t->natts = tupdesc->natts; t->hasoid = TupleDescHasOids(tupdesc); } else if (oid == RECORDOID && typmod == -1 && tupdesc) { /* input tupdesc is of uncertain lifetime, so we'd better copy it */ t->tupdesc = CreateTupleDescCopy(tupdesc); t->natts = tupdesc->natts; t->hasoid = TupleDescHasOids(tupdesc); } else if (oid == RECORDOID && typmod == -1) { t->is_anonymous_record = true; } else if (typtype == TYPTYPE_COMPOSITE && (tupdesc = lookup_rowtype_tupdesc_noerror(t->basetype, typmod, true))) { t->natts = tupdesc->natts; t->hasoid = TupleDescHasOids(tupdesc); t->tupdesc = CreateTupleDescCopy(tupdesc); t->reloid = get_typ_typrelid(oid); ReleaseTupleDesc(tupdesc); } if (tupdesc) { int arity = 0; int i; for (i = 0; i < t->natts; ++i) { Form_pg_attribute att = TupleDescAttr(tupdesc, i); if (att->attisdropped) continue; ++arity; /* but see below re. propagation of nested_unknowns */ if (att->atttypid == RECORDOID && att->atttypmod < 0) t->nested_unknowns = true; } t->arity = arity; } /* * We intentionally don't look through domains here, so we get * domain_in etc. for a domain type. */ get_type_io_data(oid, IOFunc_output, &t->typlen, &t->typbyval, &t->typalign, &t->typdelim, &t->typioparam, &t->outfuncid); t->infuncid = InvalidOid; t->sendfuncid = InvalidOid; t->recvfuncid = InvalidOid; t->outfunc.fn_oid = InvalidOid; t->infunc.fn_oid = InvalidOid; t->sendfunc.fn_oid = InvalidOid; t->recvfunc.fn_oid = InvalidOid; if (OidIsValid(elemtype)) { get_typlenbyvalalign(elemtype, &t->elemtyplen, &t->elemtypbyval, &t->elemtypalign); t->is_array = true; } else t->is_array = false; if (typtype == TYPTYPE_RANGE) { TypeCacheEntry *tc = lookup_type_cache(oid, TYPECACHE_RANGE_INFO); t->rangetype = tc->rngelemtype->type_id; t->is_range = true; } if (OidIsValid(langoid)) { List *l = list_make1_oid(oid); t->fromsql = get_transform_fromsql(basetype, langoid, l); t->tosql = get_transform_tosql(basetype, langoid, l); } MemoryContextSwitchTo(oldcontext); MemoryContextSetParent(mcxt, pllua_get_memory_cxt(L)); out: nt = t; } PLLUA_CATCH_RETHROW(); *p = t = nt; if (!t) return t; pllua_record_gc_debt(L, 4096); /* somewhat arbitrary */ /* * the table we created for our uservalue is going to be the metatable * for datum objects of this type. We close most of the functions in it * over the typeinfo object itself for easy access. */ lua_getuservalue(L, -1); lua_pushboolean(L, 1); lua_setfield(L, -2, "__metatable"); lua_pushcfunction(L, pllua_datum_gc); lua_setfield(L, -2, "__gc"); lua_pushvalue(L, -2); lua_setfield(L, -2, "typeinfo"); /* stack: self uservalue */ pllua_pgfunc_table_new(L); lua_setfield(L, -2, ".funcs"); pllua_typeconv_register(L, -1, -2); /* stack: self uservalue */ if (t->basetype != t->typeoid) { /* * Note, "basetype" is the base type's typeinfo as of now, it won't be * re-pointed as a result of invalidations. This is important because * when we look at a value of the domain type, we need the base type's * typeinfo as of that value's creation, not any more recent value. * * We require that in the event of an incompatible change to the base * type, that both base and domain typeinfos are replaced (leaving the * old domain typeinfo pointing at the old base). */ lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) t->basetype); lua_call(L, 1, 1); lua_setfield(L, -2, "basetype"); } /* stack: self uservalue */ if (t->tupdesc) { int i; /* * If we're a row type with a tupdesc, then for the same reasons as * given above, we need to look up all our element typeinfos now rather * than deferring it to later (since they may be altered in the * meantime). Note that equalTupleDescs doesn't look into nested * composite values, so it can't detect incompatible changes caused by * altering a column type. Instead, we must revalidate all our column * types as part of our own revalidation. * * We also need to know whether any record type nested inside us has a * column of unknown rowtype, since this may require extra work when we * encounter tuples. * * XXX partial work here needs revisiting once certain issues in PG * proper get fixed. * * Since we're going this far we might as well fill in the attribute * names/positions table at the same time. */ lua_createtable(L, t->natts + 2, t->natts + 2); lua_createtable(L, t->natts, 0); /* stack: self uservalue attrnames attrtypes */ for (i = 0; i < t->natts; ++i) { Form_pg_attribute att = TupleDescAttr(t->tupdesc, i); pllua_typeinfo *et = NULL; if (att->attisdropped) continue; lua_pushinteger(L, i+1); lua_pushstring(L, NameStr(att->attname)); lua_pushvalue(L, -1); lua_pushinteger(L, i+1); lua_rawset(L, -6); lua_rawset(L, -4); lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) att->atttypid); if (att->atttypid != RECORDOID) lua_pushnil(L); else lua_pushinteger(L, (lua_Integer) att->atttypmod); lua_call(L, 2, 1); if (lua_isnil(L, -1)) luaL_error(L, "failed to find attribute type info for column"); et = pllua_checktypeinfo(L, -1, false); if (et->nested_unknowns) t->nested_unknowns = true; if (et->nested_composites || (et->natts >= 0 && et->typeoid != RECORDOID)) t->nested_composites = true; lua_rawseti(L, -2, i+1); } lua_setfield(L, -3, "attrtypes"); #ifdef ObjectIdAttributeNumber if (t->hasoid) { lua_pushinteger(L, ObjectIdAttributeNumber); lua_setfield(L, -2, "oid"); lua_pushstring(L, "oid"); lua_seti(L, -2, ObjectIdAttributeNumber); } #endif lua_setfield(L, -2, "attrs"); } /* stack: self uservalue */ if (t->is_array || t->is_range) { lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, t->is_range ? t->rangetype : t->elemtype); lua_call(L, 1, 1); lua_pushvalue(L, -1); lua_setfield(L, -3, "elemtypeinfo"); } else lua_pushnil(L); /* stack: self uservalue elemtype-or-nil */ lua_insert(L, -2); /* stack: self elemtype-or-nil uservalue */ lua_pushvalue(L, -3); luaL_setfuncs(L, datumobj_base_mt, 1); if (t->is_array) { lua_pushvalue(L, -3); lua_pushvalue(L, -3); luaL_setfuncs(L, datumobj_array_mt, 2); lua_newtable(L); lua_pushvalue(L, -4); lua_pushvalue(L, -4); luaL_setfuncs(L, datumobj_array_methods, 2); lua_setfield(L, -2, "__methods"); } else if (t->is_range) { lua_pushvalue(L, -3); lua_pushvalue(L, -3); luaL_setfuncs(L, datumobj_range_mt, 2); } else if (t->natts >= 0) { lua_pushvalue(L, -3); luaL_setfuncs(L, datumobj_row_mt, 1); /* * if we're an unregistered rowtype with a known tupdesc, record_out * won't work for us leaving no convenient way to print record values * (e.g. for diagnostics). Work around this by replacing the normal * datum __tostring with a specialized one. */ if (t->typmod < 0 && t->tupdesc) { lua_pushvalue(L, -3); luaL_setfuncs(L, datumobj_unreg_row_mt, 1); } } lua_pop(L, 2); return t; } /* * newtypeinfo(oid,typmod) * * does not intern the new object. * * returns nil for nonexistent type */ static int pllua_newtypeinfo(lua_State *L) { Oid oid = luaL_checkinteger(L, 1); lua_Integer typmod = luaL_optinteger(L, 2, -1); pllua_typeinfo *t; t = pllua_newtypeinfo_raw(L, oid, typmod, NULL); if (!t) { lua_pop(L, 1); lua_pushnil(L); } return 1; } static int pllua_typeinfo_eq(lua_State *L) { pllua_typeinfo *obj1 = pllua_checktypeinfo(L, 1, false); pllua_typeinfo *obj2 = pllua_checktypeinfo(L, 2, false); if (obj1 == obj2) return 1; /* * We don't need to compare everything. If all these fields match, we * assume that existing datums aren't affected by any changes to the * remaining values. */ if (obj1->typeoid != obj2->typeoid || obj1->typmod != obj2->typmod || obj1->arity != obj2->arity || obj1->natts != obj2->natts || obj1->hasoid != obj2->hasoid || (obj1->tupdesc && !obj2->tupdesc) || (!obj1->tupdesc && obj2->tupdesc) || (obj1->tupdesc && obj2->tupdesc && !equalTupleDescs(obj1->tupdesc, obj2->tupdesc)) || obj1->reloid != obj2->reloid || obj1->basetype != obj2->basetype || obj1->elemtype != obj2->elemtype || obj1->typlen != obj2->typlen || obj1->typbyval != obj2->typbyval || obj1->typalign != obj2->typalign || obj1->typdelim != obj2->typdelim || obj1->typioparam != obj2->typioparam || obj1->outfuncid != obj2->outfuncid) { lua_pushboolean(L, false); return 1; } else { bool match = true; int natts = obj1->natts; /* * We also need to check that all the element typeinfos are raw-equal * between both. */ if (natts > 0) { int i; pllua_get_user_field(L, 1, "attrtypes"); pllua_get_user_field(L, 2, "attrtypes"); for (i = 1; match && i <= natts; ++i) { lua_rawgeti(L, -2, i); lua_rawgeti(L, -2, i); if (!lua_rawequal(L, -1, -2)) match = false; lua_pop(L, 2); } lua_pop(L, 2); } lua_pushboolean(L, match); return 1; } } int pllua_typeinfo_lookup(lua_State *L) { Oid oid = luaL_checkinteger(L, 1); lua_Integer typmod = luaL_optinteger(L, 2, -1); pllua_typeinfo *obj = NULL; pllua_typeinfo *nobj; lua_settop(L, 1); lua_pushinteger(L, typmod); if (!OidIsValid(oid)) { /* safety check so we never intern an entry for InvalidOid */ lua_pushnil(L); return 1; } else if (oid == RECORDOID && typmod >= 0) { lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_RECORDS); lua_rawgeti(L, -1, typmod); } else { lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TYPES); lua_rawgeti(L, -1, oid); } if (!lua_isnil(L, -1)) { obj = pllua_checktypeinfo(L, -1, false); if (!obj->revalidate) return 1; } /* stack: oid typmod table oldobject-or-nil */ /* obj is missing or needs revalidation */ lua_pushcfunction(L, pllua_newtypeinfo); lua_pushvalue(L, 1); lua_pushvalue(L, 2); lua_call(L, 2, 1); /* stack: oid typmod table oldobject-or-nil newobject */ /* note, newobject might be nil too */ if (lua_isnil(L, -1)) nobj = NULL; else nobj = pllua_checktypeinfo(L, -1, false); if (obj && nobj) { /* * compare old and new object. If they're equal, just drop the new one * and mark the old one valid again. Otherwise we have to intern the * new object in place of the old one. */ lua_pushcfunction(L, pllua_typeinfo_eq); lua_pushvalue(L, -3); lua_pushvalue(L, -3); lua_call(L, 2, 1); if (lua_toboolean(L, -1)) { /* equal. pop the new object after updating anything of interest */ if (obj->fromsql != nobj->fromsql || obj->tosql != nobj->tosql) { pllua_get_user_field(L, -3, ".funcs"); lua_pushnil(L); lua_setfield(L, -2, ".fromsql"); lua_pushnil(L); lua_setfield(L, -2, ".tosql"); lua_pop(L, 1); obj->fromsql = nobj->fromsql; obj->tosql = nobj->tosql; } obj->revalidate = false; lua_pop(L,2); return 1; } /* * We're going to intern the new object in place of the old one */ obj->modified = true; obj->revalidate = false; lua_pop(L,1); } else if (obj) { /* no new object, must have been dropped. */ obj->obsolete = true; obj->revalidate = false; /* new object (nil) is on stack top */ } /* stack: oid typmod table oldobject-or-nil newobject */ lua_remove(L, -2); lua_pushvalue(L, -1); if (oid == RECORDOID && typmod >= 0) lua_rawseti(L, -3, typmod); else lua_rawseti(L, -3, oid); return 1; } /* * invalidate(interp) */ int pllua_typeinfo_invalidate(lua_State *L) { pllua_interpreter *interp = lua_touserdata(L, 1); Oid typoid = interp->inval->inval_typeoid; Oid relid = interp->inval->inval_reloid; lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TYPES); if (interp->inval->inval_type) { if (OidIsValid(typoid)) { pllua_typeinfo *t; if (lua_rawgeti(L, -1, (lua_Integer) typoid) == LUA_TUSERDATA) { t = pllua_totypeinfo(L, -1); t->revalidate = true; } } else { lua_pushnil(L); while (lua_next(L, -2)) { pllua_typeinfo *t = pllua_totypeinfo(L, -1); t->revalidate = true; lua_pop(L,1); } } } if (interp->inval->inval_rel) { lua_pushnil(L); while (lua_next(L, -2)) { pllua_typeinfo *t = pllua_totypeinfo(L, -1); if (t->reloid == relid) t->revalidate = true; lua_pop(L,1); } } return 0; } static int pllua_typeinfo_gc(lua_State *L) { void **p = pllua_torefobject(L, 1, PLLUA_TYPEINFO_OBJECT); pllua_typeinfo *obj = p ? *p : NULL; ASSERT_LUA_CONTEXT; if (!p) return 0; *p = NULL; if (!obj) return 0; PLLUA_TRY(); { /* * typeinfo is allocated in its own memory context (since we expect it * to have stuff dangling off), so free it by destroying that. */ pllua_debug(L, "pllua_typeinfo_gc: %p", obj->mcxt); MemoryContextDelete(obj->mcxt); } PLLUA_CATCH_RETHROW(); return 0; } static int pllua_typeinfo_element(lua_State *L) { pllua_typeinfo *t = pllua_checktypeinfo(L, 1, false); if (t->is_array || t->is_range) { if (!lua_isnone(L, 2)) luaL_error(L, "unexpected argument to :element method"); pllua_get_user_field(L, 1, "elemtypeinfo"); return 1; } else if (t->tupdesc) { lua_Integer attno = 0; lua_settop(L, 2); switch (lua_type(L, 2)) { default: luaL_argerror(L, 2, "expected string or number"); return 1; case LUA_TSTRING: pllua_get_user_field(L, 1, "attrs"); /* stack: attrs{ attname = attno } */ lua_pushvalue(L, 2); if (lua_gettable(L, -2) != LUA_TNUMBER) luaL_error(L, "type has no column \"%s\"", lua_tostring(L, 2)); /*FALLTHROUGH*/ case LUA_TNUMBER: /* column number */ attno = lua_tointeger(L, -1); if (IsObjectIdAttributeNumber(attno)) { if (!t->hasoid) luaL_error(L, "type has no column number %d", attno); } else if ((attno < 1 || attno > t->natts) || TupleDescAttr(t->tupdesc, attno-1)->attisdropped) luaL_error(L, "type has no column number %d", attno); if (IsObjectIdAttributeNumber(attno)) { lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) OIDOID); lua_call(L, 1, 1); } else { pllua_get_user_field(L, 1, "attrtypes"); lua_geti(L, -1, attno); } return 1; } } else return 0; } static int pllua_dump_typeinfo(lua_State *L) { pllua_typeinfo *obj = pllua_checktypeinfo(L, 1, false); luaL_Buffer b; char *buf; luaL_buffinit(L, &b); if (!obj) { luaL_addstring(&b, "(null)"); luaL_pushresult(&b); return 1; } buf = luaL_prepbuffer(&b); snprintf(buf, LUAL_BUFFERSIZE, "oid: %u typmod: %d natts: %d hasoid: %c revalidate: %c " "tupdesc: %p reloid: %u typlen: %d typbyval: %c " "typalign: %c typdelim: %c typioparam: %u outfuncid: %u", obj->typeoid, obj->typmod, obj->natts, obj->hasoid ? 't' : 'f', obj->revalidate ? 't' : 'f', obj->tupdesc, obj->reloid, (int) obj->typlen, obj->typbyval ? 't' : 'f', obj->typalign, obj->typdelim, obj->typioparam, obj->outfuncid); luaL_addsize(&b, strlen(buf)); luaL_pushresult(&b); return 1; } /* given a pg type name, return a typeinfo object */ int pllua_typeinfo_parsetype(lua_State *L) { const char *str = luaL_checkstring(L, 1); volatile Oid ret_oid = InvalidOid; ASSERT_LUA_CONTEXT; PLLUA_TRY(); { Oid oid = InvalidOid; int32 typmod = -1; /* * Don't really want regtypein because it allows things like numeric * oids, '-' and so on. Accept only valid names here. * */ parseTypeString(str, &oid, &typmod, true); ret_oid = oid; } PLLUA_CATCH_RETHROW(); /* we intentionally ignore the typmod here */ lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) ret_oid); lua_call(L, 1, 1); return 1; } /* * Main user-visible entry: * * pgtype(x, [N]) returns the typeinfo object for datum x, or if x is not a * datum object and N is an integer, the typeinfo for argument N (counting from * 1) of the current function if one exists (throwing a lua error if not), or * the return type if N==0. If N is a string, parse it as a pg type string. * */ static int pllua_typeinfo_package_call(lua_State *L) { pllua_datum *d = pllua_toanydatum(L, 2, NULL); if (d) return 1; if (lua_isnoneornil(L, 3)) return 0; if (lua_isinteger(L, 3)) { int idx = lua_tointeger(L, 3); FmgrInfo *flinfo; pllua_func_activation *act; Oid oid = InvalidOid; int32 typmod = -1; if (!pllua_get_cur_act(L)) luaL_error(L, "not in a function"); act = pllua_toobject(L, -1, PLLUA_ACTIVATION_OBJECT); if (idx == 0) { oid = act->rettype; if (oid == RECORDOID && act->tupdesc) typmod = act->tupdesc->tdtypmod; } else if (idx > 0 && idx <= act->nargs) { if (act->argtypes[idx - 1] != ANYOID) oid = act->argtypes[idx - 1]; else if ((flinfo = pllua_get_cur_flinfo(L))) oid = get_fn_expr_argtype(flinfo, idx-1); else oid = ANYOID; } else if (idx > act->nargs && act->func_info->variadic_any && (flinfo = pllua_get_cur_flinfo(L))) { oid = get_fn_expr_argtype(flinfo, idx-1); } if (!OidIsValid(oid)) luaL_error(L, "argument index out of range"); lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) oid); lua_pushinteger(L, (lua_Integer) typmod); lua_call(L, 2, 1); if (lua_isnil(L, -1)) luaL_error(L, "unknown type"); return 1; } if (lua_type(L, 3) == LUA_TSTRING) { lua_pushcfunction(L, pllua_typeinfo_parsetype); lua_pushvalue(L, 3); lua_call(L, 1, 1); if (lua_isnil(L, -1)) luaL_error(L, "unknown type"); return 1; } return luaL_error(L, "invalid argument type"); } static int pllua_typeinfo_package_index(lua_State *L) { if (lua_isinteger(L, 2)) { lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushvalue(L, 2); lua_call(L, 1, 1); return 1; } else if (lua_isstring(L, 2)) { lua_pushcfunction(L, pllua_typeinfo_parsetype); lua_pushvalue(L, 2); lua_call(L, 1, 1); return 1; } else return luaL_error(L, "invalid args for typeinfo lookup"); } static int pllua_typeinfo_package_array_index(lua_State *L) { pllua_typeinfo *et; volatile Oid oid = InvalidOid; lua_pushcfunction(L, pllua_typeinfo_package_index); lua_insert(L, 1); lua_call(L, lua_gettop(L) - 1, 1); if (lua_isnil(L, -1)) return 1; et = pllua_checktypeinfo(L, -1, false); PLLUA_TRY(); { oid = get_array_type(et->typeoid); } PLLUA_CATCH_RETHROW(); if (!OidIsValid(oid)) lua_pushnil(L); else { lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) oid); lua_call(L, 1, 1); } return 1; } static int pllua_typeinfo_name(lua_State *L) { pllua_typeinfo *obj = pllua_checktypeinfo(L, 1, true); lua_Integer typmod = luaL_optinteger(L, 2, -1); bool typmod_given = !lua_isnoneornil(L, 2); const char *volatile name = NULL; ASSERT_LUA_CONTEXT; if (obj->obsolete) luaL_error(L, "type no longer exists"); PLLUA_TRY(); { if (typmod_given && obj->typeoid != RECORDOID) name = format_type_with_typemod(obj->typeoid, typmod); else name = format_type_be(obj->typeoid); } PLLUA_CATCH_RETHROW(); if (!name) luaL_error(L, "type not found when generating name"); lua_pushstring(L, name); return 1; } static bool pllua_typeinfo_iofunc(lua_State *L, pllua_typeinfo *t, IOFuncSelector whichfunc) { HeapTuple typeTuple; Form_pg_type pt; Oid funcoid = InvalidOid; FmgrInfo *flinfo = NULL; ASSERT_PG_CONTEXT; typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(t->typeoid)); if (!HeapTupleIsValid(typeTuple)) elog(ERROR, "cache lookup failed for type %u", t->typeoid); pt = (Form_pg_type) GETSTRUCT(typeTuple); switch (whichfunc) { case IOFunc_input: funcoid = pt->typinput; t->infuncid = funcoid; flinfo = &t->infunc; break; case IOFunc_output: funcoid = pt->typoutput; t->outfuncid = funcoid; flinfo = &t->outfunc; break; case IOFunc_receive: funcoid = pt->typreceive; t->recvfuncid = funcoid; flinfo = &t->recvfunc; break; case IOFunc_send: funcoid = pt->typsend; t->sendfuncid = funcoid; flinfo = &t->sendfunc; break; } ReleaseSysCache(typeTuple); if (!OidIsValid(funcoid)) return false; fmgr_info_cxt(funcoid, flinfo, t->mcxt); return true; } static void pllua_typeinfo_raw_input(lua_State *L, Datum *res, pllua_typeinfo *t, const char *str, int32 typmod) { if ((OidIsValid(t->infuncid) && OidIsValid(t->infunc.fn_oid)) || pllua_typeinfo_iofunc(L, t, IOFunc_input)) { *res = InputFunctionCall(&t->infunc, (char *) str, t->typioparam, typmod); } else elog(ERROR, "failed to find input function for type %u", t->typeoid); } static const char *pllua_typeinfo_raw_output(lua_State *L, Datum value, pllua_typeinfo *t) { const char *volatile res = NULL; if ((OidIsValid(t->outfuncid) && OidIsValid(t->outfunc.fn_oid)) || pllua_typeinfo_iofunc(L, t, IOFunc_output)) { res = OutputFunctionCall(&t->outfunc, value); } else elog(ERROR, "failed to find output function for type %u", t->typeoid); return res; } static void pllua_typeinfo_raw_coerce(lua_State *L, Datum *val, bool *isnull, int nf, Oid fnoid, int32 typmod, bool is_explicit) { LOCAL_FCINFO(fcinfo, 3); FmgrInfo *fn = *(FmgrInfo **) lua_touserdata(L, nf); Assert(OidIsValid(fnoid)); if (!fn || !OidIsValid(fn->fn_oid)) fn = pllua_pgfunc_init(L, nf, fnoid, -1, NULL, InvalidOid); if (*isnull && fn->fn_strict) return; InitFunctionCallInfoData(*fcinfo, fn, 3, InvalidOid, NULL, NULL); LFCI_ARG_VALUE(fcinfo,0) = *val; LFCI_ARGISNULL(fcinfo,0) = *isnull; LFCI_ARG_VALUE(fcinfo,1) = Int32GetDatum(typmod); LFCI_ARGISNULL(fcinfo,1) = false; LFCI_ARG_VALUE(fcinfo,2) = BoolGetDatum(is_explicit); LFCI_ARGISNULL(fcinfo,2) = false; *val = FunctionCallInvoke(fcinfo); *isnull = fcinfo->isnull; } static void pllua_typeinfo_raw_coerce_array(lua_State *L, Datum *val, bool *isnull, CoercionPathType elempath, int nf, Oid fnoid, int nf2, Oid fnoid2, pllua_typeinfo *st, pllua_typeinfo *est, pllua_typeinfo *dt, pllua_typeinfo *edt, int32 typmod, bool is_explicit) { if (!*isnull) { MemoryContext mcxt = AllocSetContextCreate(CurrentMemoryContext, "pllua temporary array context", ALLOCSET_START_SMALL_SIZES); MemoryContext oldcontext = MemoryContextSwitchTo(mcxt); AnyArrayType *arr = DatumGetAnyArrayP(*val); ArrayType *newarr; int ndim = AARR_NDIM(arr); int32 *dims = AARR_DIMS(arr); int nitems = ArrayGetNItems(ndim,dims); Datum *values = palloc(nitems * sizeof(Datum)); bool *isnull = palloc(nitems * sizeof(bool)); LOCAL_FCINFO(fcinfo, 3); LOCAL_FCINFO(fcinfo2, 3); bool separate_typmod = typmod >= 0 && OidIsValid(fnoid2); FmgrInfo *fn = elempath == COERCION_PATH_FUNC ? *(FmgrInfo **) lua_touserdata(L, nf) : NULL; FmgrInfo *fn2 = separate_typmod ? *(FmgrInfo **) lua_touserdata(L, nf2) : NULL; array_iter iter; int idx; Assert(elempath != COERCION_PATH_NONE); Assert(elempath != COERCION_PATH_ARRAYCOERCE); Assert(elempath != COERCION_PATH_COERCEVIAIO || !separate_typmod); Assert(elempath != COERCION_PATH_COERCEVIAIO || (est && edt)); if (OidIsValid(fnoid) && (!fn || !OidIsValid(fn->fn_oid))) fn = pllua_pgfunc_init(L, nf, fnoid, -1, NULL, InvalidOid); if (OidIsValid(fnoid2) && (!fn2 || !OidIsValid(fn->fn_oid))) fn2 = pllua_pgfunc_init(L, nf2, fnoid2, -1, NULL, InvalidOid); array_iter_setup(&iter, arr); if (elempath == COERCION_PATH_FUNC) InitFunctionCallInfoData(*fcinfo, fn, 3, InvalidOid, NULL, NULL); if (separate_typmod) InitFunctionCallInfoData(*fcinfo2, fn2, 3, InvalidOid, NULL, NULL); for (idx = 0; idx < nitems; ++idx) { LFCI_ARG_VALUE(fcinfo,0) = array_iter_next(&iter, &LFCI_ARGISNULL(fcinfo,0), idx, st->elemtyplen, st->elemtypbyval, st->elemtypalign); if (elempath == COERCION_PATH_RELABELTYPE) { values[idx] = LFCI_ARG_VALUE(fcinfo,0); isnull[idx] = LFCI_ARGISNULL(fcinfo,0); } switch (elempath) { default: break; case COERCION_PATH_COERCEVIAIO: /* * Contra pg proper, we don't need to do separate typmod * conversions when doing IO casts; separate_typmod should * not be true. */ if (!LFCI_ARGISNULL(fcinfo,0)) { const char *str = pllua_typeinfo_raw_output(L, LFCI_ARG_VALUE(fcinfo,0), est); pllua_typeinfo_raw_input(L, &values[idx], edt, str, typmod); isnull[idx] = (str == NULL); } else isnull[idx] = true; break; case COERCION_PATH_FUNC: if (!LFCI_ARGISNULL(fcinfo,0) || !fn->fn_strict) { LFCI_ARG_VALUE(fcinfo,1) = Int32GetDatum(separate_typmod ? -1 : typmod); LFCI_ARGISNULL(fcinfo,1) = false; LFCI_ARG_VALUE(fcinfo,2) = BoolGetDatum(is_explicit); LFCI_ARGISNULL(fcinfo,2) = false; fcinfo->isnull = false; values[idx] = FunctionCallInvoke(fcinfo); isnull[idx] = fcinfo->isnull; } else { values[idx] = (Datum)0; isnull[idx] = true; } /*FALLTHROUGH*/ case COERCION_PATH_RELABELTYPE: if (separate_typmod && (!isnull[idx] || !fn2->fn_strict)) { LFCI_ARG_VALUE(fcinfo2,0) = values[idx]; LFCI_ARGISNULL(fcinfo2,0) = isnull[idx]; LFCI_ARG_VALUE(fcinfo2,1) = Int32GetDatum(typmod); LFCI_ARGISNULL(fcinfo2,1) = false; LFCI_ARG_VALUE(fcinfo2,2) = BoolGetDatum(is_explicit); LFCI_ARGISNULL(fcinfo2,2) = false; fcinfo2->isnull = false; values[idx] = FunctionCallInvoke(fcinfo2); isnull[idx] = fcinfo2->isnull; } break; } } MemoryContextSwitchTo(oldcontext); newarr = construct_md_array(values, isnull, ndim, dims, AARR_LBOUND(arr), dt->elemtype, dt->elemtyplen, dt->elemtypbyval, dt->elemtypalign); *val = PointerGetDatum(newarr); *isnull = false; MemoryContextDelete(mcxt); } } /* * "val" is already known to be of t's base type. * * Note that we might replace "val" with a new datum allocated in the current * memory context. * * "typmod" is "val's" existing typmod if known, or -1. */ void pllua_typeinfo_check_domain(lua_State *L, Datum *val, bool *isnull, int32 typmod, int nt, pllua_typeinfo *t) { int oldtop = lua_gettop(L); ASSERT_LUA_CONTEXT; if (t->basetypmod != -1 && typmod != t->basetypmod && t->coerce_typmod) pllua_get_user_subfield(L, nt, ".funcs", ".f_typmod"); PLLUA_TRY(); { /* * Check if we need to do typmod coercion first. This might alter the * value. */ if (t->basetypmod != -1 && typmod != t->basetypmod && t->coerce_typmod) { if (t->coerce_typmod_element) pllua_typeinfo_raw_coerce_array(L, val, isnull, COERCION_PATH_FUNC, -1, t->typmod_funcid, 0, InvalidOid, t, NULL, t, NULL, t->basetypmod, false); else pllua_typeinfo_raw_coerce(L, val, isnull, -1, t->typmod_funcid, t->basetypmod, false); } domain_check(*val, *isnull, t->typeoid, &t->domain_extra, t->mcxt); } PLLUA_CATCH_RETHROW(); lua_settop(L, oldtop); } static Datum pllua_typeinfo_raw_tosql(lua_State *L, pllua_typeinfo *t, bool *isnull) { LOCAL_FCINFO(fcinfo, 1); FmgrInfo *fn = *(void **) lua_touserdata(L, lua_upvalueindex(3)); Datum result; pllua_node node; ASSERT_PG_CONTEXT; if (!fn || !OidIsValid(fn->fn_oid)) fn = pllua_pgfunc_init(L, lua_upvalueindex(3), t->tosql, -1, NULL, InvalidOid); node.type = T_Invalid; node.magic = PLLUA_MAGIC; node.L = L; InitFunctionCallInfoData(*fcinfo, fn, 1, InvalidOid, (struct Node *) &node, NULL); /* actual arg(s) on top of stack */ LFCI_ARG_VALUE(fcinfo,0) = (Datum)0; LFCI_ARGISNULL(fcinfo,0) = true; result = FunctionCallInvoke(fcinfo); if (isnull) *isnull = fcinfo->isnull; return result; } /* * args 1..top are the value to convert * upvalue 1 is the typeinfo * upvalue 2 is the datum to be filled in * upvalue 3 is the pgfunc object * returns the datum or nil */ static int pllua_typeinfo_tosql(lua_State *L) { pllua_typeinfo *t = pllua_totypeinfo(L, lua_upvalueindex(1)); pllua_datum *d; volatile Datum val; bool isnull = false; PLLUA_TRY(); { val = pllua_typeinfo_raw_tosql(L, t, &isnull); } PLLUA_CATCH_RETHROW(); if (isnull) lua_pushnil(L); else { d = pllua_todatum(L, lua_upvalueindex(2), lua_upvalueindex(1)); d->value = val; lua_pushvalue(L, lua_upvalueindex(2)); } return 1; } static bool pllua_typeinfo_raw_fromsql(lua_State *L, Datum val, pllua_typeinfo *t) { LOCAL_FCINFO(fcinfo, 1); FmgrInfo *fn = *(void **) lua_touserdata(L, lua_upvalueindex(3)); pllua_node node; ASSERT_PG_CONTEXT; if (!OidIsValid(t->fromsql)) return false; if (!fn || !OidIsValid(fn->fn_oid)) fn = pllua_pgfunc_init(L, lua_upvalueindex(3), t->fromsql, -1, NULL, InvalidOid); node.type = T_Invalid; node.magic = PLLUA_MAGIC; node.L = L; InitFunctionCallInfoData(*fcinfo, fn, 1, InvalidOid, (struct Node *) &node, NULL); LFCI_ARG_VALUE(fcinfo,0) = val; LFCI_ARGISNULL(fcinfo,0) = false; FunctionCallInvoke(fcinfo); return !fcinfo->isnull; } /* * upvalue 1 is the typeinfo * upvalue 2 is a userdata with the value to convert * upvalue 3 is the pgfunc object * returns the value or nothing */ static int pllua_typeinfo_fromsql(lua_State *L) { pllua_typeinfo *t = pllua_totypeinfo(L, lua_upvalueindex(1)); Datum d = *(Datum *)lua_touserdata(L, lua_upvalueindex(2)); volatile bool done; Assert(lua_gettop(L) == 0); PLLUA_TRY(); { done = pllua_typeinfo_raw_fromsql(L, d, t); } PLLUA_CATCH_RETHROW(); Assert(done ? lua_gettop(L) == 1 : lua_gettop(L) == 0); return done ? 1 : 0; } /* * Note that "typmod" here is the _destination_ typmod */ static void pllua_typeinfo_coerce_typmod(lua_State *L, Datum *val, bool *isnull, int nt, pllua_typeinfo *t, int32 typmod) { if (!t->coerce_typmod || typmod < 0) return; nt = lua_absindex(L, nt); pllua_get_user_subfield(L, nt, ".funcs", ".f_typmod"); PLLUA_TRY(); { if (t->coerce_typmod_element) { Assert(t->is_array); pllua_typeinfo_raw_coerce_array(L, val, isnull, COERCION_PATH_FUNC, -1, t->typmod_funcid, 0, InvalidOid, t, NULL, t, NULL, typmod, false); } else pllua_typeinfo_raw_coerce(L, val, isnull, -1, t->typmod_funcid, typmod, false); } PLLUA_CATCH_RETHROW(); lua_pop(L, 1); } /* * t:fromstring('str') returns a datum object. * * Given a nil input, it returns nil, but might call the input function anyway * (only if it's not strict) */ static int pllua_typeinfo_fromstring(lua_State *L) { pllua_typeinfo *t = pllua_checktypeinfo(L, 1, true); const char *str = lua_isnil(L, 2) ? NULL : luaL_checkstring(L, 2); MemoryContext mcxt = pllua_get_memory_cxt(L); pllua_datum *d = NULL; if (t->obsolete || t->modified) luaL_error(L, "cannot create values for a dropped or modified type"); ASSERT_LUA_CONTEXT; if (str) { pllua_verify_encoding(L, str); d = pllua_newdatum(L, 1, (Datum)0); } else lua_pushnil(L); PLLUA_TRY(); { Datum nv; pllua_typeinfo_raw_input(L, &nv, t, str, t->typmod); if (str) { MemoryContext oldcontext = MemoryContextSwitchTo(mcxt); d->value = nv; pllua_savedatum(L, d, t); MemoryContextSwitchTo(oldcontext); } } PLLUA_CATCH_RETHROW(); return 1; } /* * t:frombinary('str') returns a datum object. * * Given a nil input, it returns nil, but might call the input function anyway * (only if it's not strict) * * CAVEAT: this assumes, for many types, that the binary data is in the * current _client_ encoding, not the server encoding. */ static int pllua_typeinfo_frombinary(lua_State *L) { pllua_typeinfo *t = pllua_checktypeinfo(L, 1, true); size_t len = 0; const char *str = lua_isnil(L, 2) ? NULL : luaL_checklstring(L, 2, &len); MemoryContext mcxt = pllua_get_memory_cxt(L); pllua_datum *d = NULL; volatile bool done = false; if (t->modified || t->obsolete) luaL_error(L, "cannot create values for a dropped or modified type"); ASSERT_LUA_CONTEXT; if (str) d = pllua_newdatum(L, 1, (Datum)0); else lua_pushnil(L); PLLUA_TRY(); { Datum nv; StringInfoData buf; initStringInfo(&buf); if (str) appendBinaryStringInfo(&buf, str, len); if ((OidIsValid(t->recvfuncid) && OidIsValid(t->recvfunc.fn_oid)) || pllua_typeinfo_iofunc(L, t, IOFunc_receive)) { nv = ReceiveFunctionCall(&t->recvfunc, str ? &buf : NULL, t->typioparam, t->typmod); if (str) { MemoryContext oldcontext = MemoryContextSwitchTo(mcxt); d->value = nv; pllua_savedatum(L, d, t); MemoryContextSwitchTo(oldcontext); } done = true; } pfree(buf.data); } PLLUA_CATCH_RETHROW(); if (!done) luaL_error(L, "could not find receive function for type"); return 1; } /* * "nd" indexes a table (or table-like object) * t = target typeinfo * * Number of values pushed should always equal the target type's arity, we push * nils for anything missing */ static int pllua_typeinfo_push_from_table(lua_State *L, int nd, pllua_typeinfo *t) { int attno; int natts = t->natts; int nret = 0; nd = lua_absindex(L, nd); luaL_checkstack(L, 10 + t->arity, NULL); for (attno = 0; attno < natts; ++attno) { Form_pg_attribute att = TupleDescAttr(t->tupdesc, attno); if (att->attisdropped) continue; lua_getfield(L, nd, NameStr(att->attname)); ++nret; } return nret; } static bool pllua_datum_transform_tosql(lua_State *L, int nargs, int argbase, int nt, pllua_typeinfo *t) { int i; nt = lua_absindex(L, nt); argbase = lua_absindex(L, argbase); if (!OidIsValid(t->tosql)) { /* no SQL-level transform? maybe we have a transform in the typeinfo. */ if (pllua_get_user_field(L, nt, "tosql") == LUA_TFUNCTION) { int base = lua_gettop(L) - 1; /* -1 because func is on stack already */ luaL_checkstack(L, 10+nargs, NULL); for (i = 0; i < nargs; ++i) lua_pushvalue(L, argbase+i); lua_call(L, nargs, LUA_MULTRET); /* transform should return 1 value if it liked the input, no value if not */ if (lua_gettop(L) == base) return false; lua_settop(L, base+1); /* clamp to 1 result */ return true; } else lua_pop(L, 1); return false; } luaL_checkstack(L, 10+nargs, NULL); lua_pushvalue(L, nt); pllua_newdatum(L, -1, (Datum)0); pllua_get_user_subfield(L, nt, ".funcs", ".tosql"); lua_pushcclosure(L, pllua_typeinfo_tosql, 3); for (i = 0; i < nargs; ++i) lua_pushvalue(L, argbase+i); lua_call(L, nargs, 1); if (!lua_isnil(L, -1)) return true; lua_pop(L, 1); return false; } int pllua_datum_transform_fromsql(lua_State *L, Datum val, int nidx, pllua_typeinfo *t) { Datum *tmpd; int nd; /* * This would belong in pllua_value_from_datum except that we don't have * the typeinfo available there. */ if (t->is_enum) { const char *volatile str = NULL; PLLUA_TRY(); { str = pllua_typeinfo_raw_output(L, val, t); } PLLUA_CATCH_RETHROW(); lua_pushstring(L, str); return LUA_TSTRING; } if (!OidIsValid(t->fromsql)) return LUA_TNONE; nidx = lua_absindex(L, nidx); nd = lua_gettop(L); lua_pushvalue(L, nidx); tmpd = lua_newuserdata(L, sizeof(Datum)); *tmpd = val; pllua_get_user_subfield(L, nidx, ".funcs", ".fromsql"); lua_pushcclosure(L, pllua_typeinfo_fromsql, 3); lua_call(L, 0, LUA_MULTRET); nd = lua_gettop(L) - nd; if (nd == 0) return LUA_TNONE; else if (nd > 1 || nd < 0) return luaL_error(L, "invalid return from transform function"); else return lua_type(L, -1); } /* * "call" for a typeinfo is to invoke it as a value constructor: * * t(val,val,val,...) * * The number of args must match the arity of the type. Each value must either * be acceptable simple input for the field type, or a string, or a datum of * the correct type - probably this needs extending to allow casts. * * t(table) * * Constructs a rowtype or array value from fields in a table. * * t(table, dim1, dim2, ...) * * Constructs a possibly multi-dimensional array * * t(val) * * Constructs a (deep) copy of the passed-in value of the same type. * * We break this down into several specific cases. */ static int pllua_typeinfo_array_call(lua_State *L); static int pllua_typeinfo_row_call(lua_State *L); static int pllua_typeinfo_scalar_call(lua_State *L); static int pllua_typeinfo_range_call(lua_State *L); /* * scalartype(datum) * arraytype(datum) * * Converting a single Datum of one type to the target type. * * We invoke the typeconv logic to do the conversion, even if source and target * types are the same, since that helps keep the code simpler. */ static int pllua_typeinfo_call_datum(lua_State *L, int nd, int nt, int ndt, pllua_datum *d, pllua_typeinfo *t) { nd = lua_absindex(L, nd); /* source datum */ nt = lua_absindex(L, nt); /* target typeinfo */ /* * All the work here is done by the typeconv system; looking up the type * cast generates the function object. */ pllua_get_user_field(L, ndt, "typeconv"); lua_pushvalue(L, nt); if (lua_gettable(L, -2) != LUA_TFUNCTION) luaL_error(L, "cast lookup error"); lua_pushvalue(L, nd); lua_call(L, 1, 1); return 1; } /* * Try and convert an existing datum to an anonymous record. * * We can do this if: * - the input is already an anonymous record, which we just copy; * - the input is an unmodified row of any type, which we just copy; * - the input is a modified row of any type, which we reform into a * new unmodified row of its own type and then adopt * * Any other input is an error, since without a tupdesc we can't know anything * about the intended structure. */ static int pllua_typeinfo_anonrec_call_datum(lua_State *L, int nd, int nt, int ndt, pllua_typeinfo *t, pllua_datum *d, pllua_typeinfo *dt) { nd = lua_absindex(L, nd); /* source datum */ nt = lua_absindex(L, nt); /* target typeinfo */ ndt = lua_absindex(L, ndt); /* source datum's typeinfo */ if (dt->natts >= 0) { pllua_datum *tmpd; pllua_datum *newd; /* Use the source datum's own typeinfo to make a copy of it; this * ensures we have a new unexploded record which we can just steal the * storage for. */ lua_pushvalue(L, ndt); lua_pushvalue(L, nd); lua_call(L, 1, 1); tmpd = pllua_todatum(L, -1, ndt); Assert(tmpd); Assert(!tmpd->modified); newd = pllua_newdatum(L, nt, tmpd->value); /* transfer ownership of storage to new datum */ tmpd->need_gc = false; newd->need_gc = true; return 1; } else if (dt->is_anonymous_record) { pllua_datum *newd; newd = pllua_newdatum(L, nt, (Datum)0); newd->value = d->value; pllua_save_one_datum(L, newd, t); return 1; } else return luaL_error(L, "anonymous record can only accept input of existing row datum"); } static int pllua_typeinfo_call(lua_State *L) { pllua_typeinfo *t = pllua_checktypeinfo(L, 1, true); int nargs = lua_gettop(L) - 1; pllua_typeinfo *dt; pllua_datum *d = (nargs == 1) ? pllua_toanydatum(L, 2, &dt) : NULL; if (t->modified || t->obsolete) luaL_error(L, "cannot create values for obsolete or modified type"); if (d) { if (t->is_anonymous_record) return pllua_typeinfo_anonrec_call_datum(L, 2, 1, -1, t, d, dt); /* * The condition here is to exclude this case: * destination type is a rowtype * source type is a scalar, * or: destination is arity 1 * and source is not the same type oid * or * destination type is an array * source type is not an array * This is because all of those cases should be treated as * constructing from the first element value */ if (!((t->natts >= 0 && (dt->natts < 0 || (t->arity == 1 && t->typeoid != dt->typeoid))) || (t->is_array && !dt->is_array))) return pllua_typeinfo_call_datum(L, 2, 1, -1, d, t); lua_pop(L, 1); } if (t->is_array) lua_pushcfunction(L, pllua_typeinfo_array_call); else if (t->is_range) lua_pushcfunction(L, pllua_typeinfo_range_call); else if (t->natts >= 0) lua_pushcfunction(L, pllua_typeinfo_row_call); else if (t->is_anonymous_record) luaL_error(L, "anonymous record can only accept input of existing row datum"); else lua_pushcfunction(L, pllua_typeinfo_scalar_call); lua_insert(L, 1); lua_call(L, nargs+1, LUA_MULTRET); return lua_gettop(L); } /* * We only get here for non-Datum input */ static int pllua_typeinfo_scalar_call(lua_State *L) { pllua_typeinfo *t = pllua_totypeinfo(L, 1); pllua_datum *newd = NULL; int nargs = lua_gettop(L) - 1; Datum nvalue = (Datum) 0; bool isnull = false; const char *err = NULL; const char *str = NULL; /* * If there's a transform, it might accept zero or multiple args, so try it * first. */ if (pllua_datum_transform_tosql(L, nargs, 2, 1, t)) { Datum *nvaluep = &nvalue; if (!lua_isnil(L, -1)) { newd = pllua_todatum(L, -1, 1); nvaluep = &newd->value; } else isnull = true; /* must check domain constraints before accepting a null */ /* note this can change the value */ if (t->typeoid != t->basetype) pllua_typeinfo_check_domain(L, nvaluep, &isnull, -1, 1, t); if (isnull) return 1; /* shouldn't happen: */ if (!newd) luaL_error(L, "domain check returned non-null for null input"); } else if (nargs != 1) { luaL_error(L, "incorrect number of arguments for type constructor (expected 1 got %d)", nargs); } else if (pllua_datum_from_value(L, 2, t->basetype, /* accept input for the base type of a domain */ &nvalue, &isnull, &err)) { if (err) luaL_error(L, "could not convert value: %s", err); /* must check domain constraints before accepting a null */ /* note this can change the value */ if (t->typeoid != t->basetype) pllua_typeinfo_check_domain(L, &nvalue, &isnull, -1, 1, t); if (isnull) { lua_pushnil(L); return 1; } newd = pllua_newdatum(L, 1, nvalue); } else if (lua_type(L, 2) == LUA_TSTRING) { str = lua_tostring(L, 2); pllua_verify_encoding(L, str); newd = pllua_newdatum(L, 1, (Datum)0); } else luaL_error(L, "incompatible value type"); PLLUA_TRY(); { MemoryContext oldcontext; if (str) { /* input func is responsible for typmod handling on this path */ pllua_typeinfo_raw_input(L, &nvalue, t, str, t->typmod); newd->value = nvalue; } oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); pllua_savedatum(L, newd, t); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); return 1; } /* * rangetype(lo,hi) * rangetype(lo,hi,bounds) * rangetype() empty range * rangetype(str) goes to the normal scalar call */ static int pllua_typeinfo_range_call(lua_State *L) { pllua_typeinfo *t = pllua_totypeinfo(L, 1); pllua_typeinfo *et PG_USED_FOR_ASSERTS_ONLY; int nargs = lua_gettop(L) - 1; RangeBound lo; RangeBound hi; pllua_datum *d; lua_settop(L, 4); pllua_get_user_field(L, 1, "elemtypeinfo"); et = pllua_checktypeinfo(L, -1, false); Assert(et && et->typeoid == t->rangetype); if (nargs == 1) { lua_settop(L, 2); lua_pushcfunction(L, pllua_typeinfo_scalar_call); lua_insert(L, 1); lua_call(L, 2, 1); return 1; } else if (nargs > 3) luaL_error(L, "incorrect arguments for range constructor"); if (nargs == 3 && !lua_isstring(L, 4)) luaL_argerror(L, 3, "string"); lo.infinite = false; lo.inclusive = true; lo.lower = true; hi.infinite = false; hi.inclusive = false; hi.lower = false; if (nargs >= 2) { if (lua_isnil(L, 2)) lo.infinite = true; else { lua_pushvalue(L, -1); lua_pushvalue(L, 2); lua_call(L, 1, 1); lua_replace(L, 2); d = pllua_checkdatum(L, 2, 5); lo.val = d->value; } if (lua_isnil(L, 3)) hi.infinite = true; else { lua_pushvalue(L, -1); lua_pushvalue(L, 3); lua_call(L, 1, 1); lua_replace(L, 3); d = pllua_checkdatum(L, 3, 5); hi.val = d->value; } } if (nargs == 3) { const char *str = lua_tostring(L, 4); if (!str || (str[0] != '[' && str[0] != '(') || (str[1] != ']' && str[1] != ')') || str[2]) luaL_error(L, "invalid range bounds specifier"); lo.inclusive = (str[0] == '['); hi.inclusive = (str[1] == ']'); } d = pllua_newdatum(L, 1, (Datum)0); PLLUA_TRY(); { TypeCacheEntry *tc = lookup_type_cache(t->typeoid, TYPECACHE_RANGE_INFO); Datum val = PointerGetDatum(make_range(tc, &lo, &hi, (nargs == 0))); MemoryContext oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); d->value = val; pllua_savedatum(L, d, t); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); return 1; } static int pllua_typeinfo_array_fromtable(lua_State *L, int nt, int nte, int nd, int ndim, int *dims, pllua_typeinfo *t, pllua_typeinfo *et); /* assume table is at index nd */ static int pllua_largest_int_key(lua_State *L, int nd) { int n = 0; bool metaloop; nd = lua_absindex(L, nd); metaloop = pllua_pairs_start(L, nd, false); while (metaloop ? pllua_pairs_next(L) : lua_next(L, nd)) { lua_pop(L, 1); if (lua_isnumber(L, -1)) { int isint = 0; lua_Integer k = lua_tointegerx(L, -1, &isint); if (isint && k > 0 && k <= INT_MAX && k > n) n = k; } } return n; } /* * arraytype(val,val,val,...) * arraytype() empty array * arraytype(table) * arraytype(table, dim1, dim2, ...) * * idiom: arraytype(table, #table) * or arraytype(table, (table.n or #table)) * * no support for lower bounds yet. * * Note that arraytype(a) where a is already of the array type never gets here. */ static int pllua_typeinfo_array_call(lua_State *L) { pllua_typeinfo *t = pllua_totypeinfo(L, 1); pllua_typeinfo *et; int nargs = lua_gettop(L) - 1; int ndim = 0; int dims[MAXDIM]; int i; pllua_get_user_field(L, 1, "elemtypeinfo"); et = pllua_checktypeinfo(L, -1, false); Assert(et && !et->is_array && et->typeoid == t->elemtype); if (nargs > 0) { int typ1 = lua_type(L, 2); if (nargs > 1 && (typ1 == LUA_TTABLE || typ1 == LUA_TUSERDATA) && lua_isinteger(L, 3)) { if (nargs > MAXDIM+1) luaL_error(L, "too many dimensions for array (max %d)", MAXDIM); ndim = nargs - 1; for (i = 0; i < ndim; ++i) { dims[i] = lua_tointeger(L, 3+i); if (dims[i] < 0 || (dims[i] == 0 && ndim > 1)) luaL_error(L, "invalid dimension %d (%d) for array", i, dims[i]); } return pllua_typeinfo_array_fromtable(L, 1, -1, 2, ndim, dims, t, et); } else if (nargs == 1 && (typ1 == LUA_TTABLE || (typ1 == LUA_TUSERDATA && !pllua_todatum(L, 2, -1) && pllua_is_container(L, 2) ))) { ndim = 1; dims[0] = pllua_largest_int_key(L, 2); return pllua_typeinfo_array_fromtable(L, 1, -1, 2, ndim, dims, t, et); } } lua_createtable(L, nargs, 0); for (i = 1; i <= nargs; ++i) { lua_pushvalue(L, 1+i); lua_seti(L, -2, i); } return pllua_typeinfo_array_fromtable(L, 1, -2, -1, 1, &nargs, t, et); } static int pllua_typeinfo_array_fromtable(lua_State *L, int nt, int nte, int nd, int ndim, int *dims, pllua_typeinfo *t, pllua_typeinfo *et) { pllua_datum *newd = NULL; Datum *values; bool *isnull; int i; int nelems = 0; int lbs[MAXDIM]; nt = lua_absindex(L, nt); nte = lua_absindex(L, nte); nd = lua_absindex(L, nd); if (ndim > 0) { int64 maxelem = (int64) ((Size) MaxAllocSize / sizeof(Datum)); int64 tnelems = dims[0]; lbs[0] = 1; for (i = 1; i < ndim; ++i) { if (dims[i] > maxelem / tnelems) luaL_error(L, "number of elements in array exceeds limit"); tnelems *= dims[i]; lbs[i] = 1; } if (tnelems > INT_MAX || tnelems > LUA_MAXINTEGER) luaL_error(L, "number of elements in array exceeds limit"); nelems = tnelems; } if (nelems) { int ct; int curidx[MAXDIM]; int topidx; /* construct a flat array of datum objects */ lua_createtable(L, nelems, 0); ct = lua_gettop(L); /* * stack looks like: * ct data data[i] data[i][j] ... * beware that the data elements may be nil! * * topidx is the 0..(ndim-1) index of the topmost item on the stack, * i.e. curidx[topidx] is the current index variable. (Or looked at * another way, topidx = depth - 1) */ lua_pushvalue(L, nd); curidx[0] = 1; /* * The topidx check in the loop condition serves no purpose except * to silence a really annoying gcc warning */ for (topidx = 0, i = 1; i <= nelems && topidx >= 0; ++i) { while (topidx < ndim - 1) { if (!lua_isnil(L, -1)) lua_geti(L, -1, curidx[topidx]); else lua_pushnil(L); curidx[++topidx] = 1; } if (!lua_isnil(L, -1)) lua_geti(L, -1, curidx[topidx]); else lua_pushnil(L); lua_pushvalue(L, nte); lua_insert(L, -2); lua_call(L, 1, 1); lua_seti(L, ct, i); while (topidx >= 0 && (++(curidx[topidx])) > dims[topidx]) { --topidx; lua_pop(L, 1); } } lua_settop(L, ct); } newd = pllua_newdatum(L, nt, (Datum)0); PLLUA_TRY(); { MemoryContext oldcontext; if (nelems == 0) { newd->value = PointerGetDatum(construct_empty_array(t->elemtype)); } else { values = palloc(nelems * sizeof(Datum)); isnull = palloc(nelems * sizeof(bool)); for (i = 0; i < nelems; ++i) { pllua_datum *ed; lua_rawgeti(L, -2, i+1); if (lua_isnil(L, -1)) isnull[i] = true; else { ed = (pllua_datum *) lua_touserdata(L, -1); Assert(ed); values[i] = ed->value; isnull[i] = false; } lua_pop(L, 1); } newd->value = PointerGetDatum(construct_md_array(values, isnull, ndim, dims, lbs, t->elemtype, t->elemtyplen, t->elemtypbyval, t->elemtypalign)); pfree(values); pfree(isnull); } oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); pllua_savedatum(L, newd, t); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); return 1; } /* * */ static int pllua_typeinfo_row_call(lua_State *L) { pllua_typeinfo *t = pllua_totypeinfo(L, 1); pllua_datum *newd; int nargs = lua_gettop(L) - 1; int argbase = 1; /* * this is about 30kbytes of stack space on 64bit, but it's still much * cleaner than messing with dynamic allocations. */ Datum values[MaxTupleAttributeNumber + 1]; bool isnull[MaxTupleAttributeNumber + 1]; int i; int argno = 0; TupleDesc tupdesc = t->tupdesc; Oid newoid = InvalidOid; PLLUA_CHECK_PG_STACK_DEPTH(); if (nargs == 1) { if (lua_type(L, 2) == LUA_TTABLE || lua_type(L, 2) == LUA_TUSERDATA) { if (!pllua_toanydatum(L, 2, NULL)) { /* * If it's not a datum, but it is a table or object, we assume it's * something we can index by field name. (If the caller wants * matching by number, they can do t(table.unpack(val)) instead.) * * We push the source values on the stack in the correct order and * fall out to handle it below. typeinfo_push_from_table checks the * stack depth. */ argbase = lua_gettop(L); nargs = pllua_typeinfo_push_from_table(L, 2, t); } else lua_pop(L, 1); } else if (lua_isnil(L, 2)) { lua_pop(L, 1); nargs = 0; } } if (nargs == 0) { nargs = t->arity; luaL_checkstack(L, 20 + nargs, NULL); for (i = 1; i <= nargs; ++i) lua_pushnil(L); } else if (nargs != t->arity) luaL_error(L, "incorrect number of arguments for type constructor (expected %d got %d)", t->arity, nargs); for (argno = argbase, i = 0; i < nargs; ++i) { Form_pg_attribute att = TupleDescAttr(tupdesc, i); Oid coltype = att->atttypid; int32 coltypmod = att->atttypmod; pllua_datum *d = NULL; pllua_typeinfo *argt; values[i] = (Datum)-1; if (TupleDescAttr(t->tupdesc, i)->attisdropped) { isnull[i] = true; continue; } ++argno; /* look up the element typeinfo in case we need it below */ lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) coltype); if (coltype == RECORDOID) lua_pushinteger(L, (lua_Integer) coltypmod); else lua_pushnil(L); lua_call(L, 2, 1); argt = pllua_checktypeinfo(L, -1, false); /* nil? */ if (lua_isnil(L, argno)) { isnull[i] = true; } else { /* is it already a datum of the correct type? */ d = pllua_todatum(L, argno, -1); if (!d || d->modified) { /* recursively construct an element datum */ /* note that here is where most of the work happens */ lua_pushvalue(L, -1); lua_pushvalue(L, argno); lua_call(L, 1, 1); /* replace result in stack and proceed */ lua_replace(L, argno); d = pllua_todatum(L, argno, -1); } if (!d || d->modified) luaL_error(L, "inconsistency"); values[i] = d->value; isnull[i] = false; } if (coltype != RECORDOID && coltypmod >= 0 && (!d || coltypmod != d->typmod)) pllua_typeinfo_coerce_typmod(L, &values[i], &isnull[i], -1, argt, coltypmod); lua_pop(L,1); } newd = pllua_newdatum(L, 1, (Datum)0); PLLUA_TRY(); { HeapTuple tuple = heap_form_tuple(t->tupdesc, values, isnull); MemoryContext oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); if (t->hasoid) HeapTupleSetOid(tuple, newoid); newd->value = heap_copy_tuple_as_datum(tuple, t->tupdesc); newd->need_gc = true; pfree(tuple); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); return 1; } /* * Type casting stuff * * We want to support these cases: * * scalar -> scalar via assignment cast * array -> array via assignment cast of elements * row -> row with element-wise assignment casts * row -> row with dropped columns or added cols * * The row cases are best handled by having a function to push the deform * elements compensating for added/dropped cols, and then leaving the rest to * the non-row cast cases. * * The typeconv table is indexed as { dest_typeinfo = func }, with func being * a closure over src and dest typeinfos and typically a pgfunc. */ /* * newdatum = f(olddatum) by calling a cast function * * upvalue 1 is src typeinfo * upvalue 2 is dst typeinfo * upvalue 3 is cast function oid (or InvalidOid for binary-compatible) * upvalue 4 is pgfunc * upvalue 5 is nil, or a pgfunc for the typmod cast fn * * If dst type is a domain, then dst typeinfo is the domain type, but the cast * function is to the domain's base type. For this case, we have to know * whether the cast function actually takes a typmod parameter, because if not, * we have to invoke the typmod cast separately. * * We rely on our setup code to supply a non-nil value for upvalue 5 as a flag * to indicate that the extra coercion is needed. */ static int pllua_typeconv_scalar_coerce_func(lua_State *L) { pllua_typeinfo *src_t = pllua_checktypeinfo(L, lua_upvalueindex(1), false); pllua_typeinfo *dst_t = pllua_checktypeinfo(L, lua_upvalueindex(2), true); pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_datum *newd; volatile bool isnull_ret = false; Oid fnoid = (Oid) lua_tointeger(L, lua_upvalueindex(3)); bool need_typmod = !lua_isnil(L, lua_upvalueindex(5)); if (dst_t->modified || dst_t->obsolete) luaL_error(L, "cannot cast value to modified or obsolete type"); newd = pllua_newdatum(L, lua_upvalueindex(2), (Datum)0); PLLUA_TRY(); { Datum val = d->value; bool isnull = false; /* * If it's an RW expanded datum, take the RO value instead to force * making a copy rather than owning the original (which wouldn't help * since we already own it). */ if (src_t->typlen == -1 && VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(val))) { val = EOHPGetRODatum(DatumGetEOHP(val)); } if (OidIsValid(fnoid)) pllua_typeinfo_raw_coerce(L, &val, &isnull, lua_upvalueindex(4), fnoid, (need_typmod) ? -1 : dst_t->basetypmod, false); if (need_typmod) pllua_typeinfo_raw_coerce(L, &val, &isnull, lua_upvalueindex(5), dst_t->typmod_funcid, dst_t->basetypmod, false); if (dst_t->basetype != dst_t->typeoid) domain_check(val, isnull, dst_t->typeoid, &dst_t->domain_extra, dst_t->mcxt); if (!isnull) { MemoryContext oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); newd->value = val; pllua_savedatum(L, newd, dst_t); MemoryContextSwitchTo(oldcontext); } isnull_ret = isnull; } PLLUA_CATCH_RETHROW(); if (isnull_ret) lua_pushnil(L); return 1; } /* * newdatum = f(olddatum) by IO conversions * * upvalue 1 is src typeinfo * upvalue 2 is dst typeinfo * upvalue 3 is dst base typeinfo */ static int pllua_typeconv_scalar_coerce_via_io(lua_State *L) { pllua_typeinfo *src_t = pllua_checktypeinfo(L, lua_upvalueindex(1), false); pllua_typeinfo *dst_t = pllua_checktypeinfo(L, lua_upvalueindex(2), true); pllua_typeinfo *base_t = pllua_checktypeinfo(L, lua_upvalueindex(3), true); pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_datum *newd; volatile bool isnull_ret = false; if (dst_t->modified || dst_t->obsolete || base_t->modified || base_t->obsolete) luaL_error(L, "cannot cast value to modified or obsolete type"); newd = pllua_newdatum(L, lua_upvalueindex(2), (Datum)0); PLLUA_TRY(); { const char *str = pllua_typeinfo_raw_output(L, d->value, src_t); bool isnull = (str == NULL); /* * Contra pg proper, we use the domain's typmod (if there is one) for * the input function rather than a separate typmod coercion, because * we're doing only implicit coercions and not explicit ones. This also * means we don't need to special-case the "interval" type (which * always needs its typmod). */ pllua_typeinfo_raw_input(L, &newd->value, base_t, str, dst_t->basetypmod); if (dst_t->basetype != dst_t->typeoid) domain_check(newd->value, isnull, dst_t->typeoid, &dst_t->domain_extra, dst_t->mcxt); if (str && !isnull) { MemoryContext oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); pllua_savedatum(L, newd, dst_t); MemoryContextSwitchTo(oldcontext); } else isnull_ret = true; } PLLUA_CATCH_RETHROW(); if (isnull_ret) lua_pushnil(L); return 1; } /* * newarraydatum = f(oldarraydatum) by calling a cast function or i/o conv * * upvalue 1 is src typeinfo * upvalue 2 is dst typeinfo * upvalue 3 is cast function oid, InvalidOid for i/o cast, or nil for no cast * upvalue 4 is pgfunc or nil * upvalue 5 is second pgfunc or nil * upvalue 6 is src element typeinfo for IO casts * upvalue 7 is dst element typeinfo for IO casts */ static int pllua_typeconv_array_coerce(lua_State *L) { pllua_typeinfo *src_t = pllua_checktypeinfo(L, lua_upvalueindex(1), false); pllua_typeinfo *dst_t = pllua_checktypeinfo(L, lua_upvalueindex(2), true); pllua_typeinfo *src_et = NULL; pllua_typeinfo *dst_et = NULL; pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); pllua_datum *newd; bool isnull = false; bool binary_compat = lua_isnil(L, lua_upvalueindex(3)); CoercionPathType elempath; Oid fnoid = (Oid) luaL_optinteger(L, lua_upvalueindex(3), InvalidOid); Oid fnoid2 = lua_isnil(L, lua_upvalueindex(5)) ? InvalidOid : dst_t->typmod_funcid; if (dst_t->modified || dst_t->obsolete) luaL_error(L, "cannot cast value to modified or obsolete type"); Assert(src_t->is_array && dst_t->is_array); if (binary_compat) elempath = COERCION_PATH_RELABELTYPE; else if (!OidIsValid(fnoid)) { elempath = COERCION_PATH_COERCEVIAIO; src_et = pllua_checktypeinfo(L, lua_upvalueindex(6), false); dst_et = pllua_checktypeinfo(L, lua_upvalueindex(7), true); if (dst_et->modified || dst_et->obsolete) luaL_error(L, "cannot cast value to modified or obsolete type"); } else elempath = COERCION_PATH_FUNC; newd = pllua_newdatum(L, lua_upvalueindex(2), (Datum)0); PLLUA_TRY(); { Datum val = d->value; pllua_typeinfo_raw_coerce_array(L, &val, &isnull, elempath, lua_upvalueindex(4), fnoid, lua_upvalueindex(5), fnoid2, src_t, src_et, dst_t, dst_et, dst_t->basetypmod, false); if (dst_t->basetype != dst_t->typeoid) domain_check(val, isnull, dst_t->typeoid, &dst_t->domain_extra, dst_t->mcxt); if (!isnull) { MemoryContext oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); newd->value = val; pllua_savedatum(L, newd, dst_t); MemoryContextSwitchTo(oldcontext); } } PLLUA_CATCH_RETHROW(); if (isnull) lua_pushnil(L); return 1; } /* * newrowdatum = f(oldrowdatum) by deform/reform * * upvalue 1 is src typeinfo * upvalue 2 is dst typeinfo * upvalue 3 is string giving dropped att flags (or nil) */ static int pllua_typeconv_row_coerce(lua_State *L) { pllua_typeinfo *src_t = pllua_checktypeinfo(L, lua_upvalueindex(1), false); pllua_typeinfo *dst_t = pllua_checktypeinfo(L, lua_upvalueindex(2), true); pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); size_t sz; const char *droplist = lua_tolstring(L, lua_upvalueindex(3), &sz); int nuv; int nargs; int i; Oid rowoid = InvalidOid; if (dst_t->modified || dst_t->obsolete) luaL_error(L, "cannot cast value to modified or obsolete type"); /* * Push all the exploded parts of the source tuple onto the stack. Watch * out for booleans subbing for nulls/dropped cols though! */ luaL_checkstack(L, 10 + dst_t->arity, NULL); pllua_datum_deform_tuple(L, 1, d, src_t); nuv = lua_absindex(L, -1); lua_pushcfunction(L, pllua_typeinfo_row_call); lua_pushvalue(L, lua_upvalueindex(2)); if (dst_t->hasoid && src_t->hasoid) { lua_getfield(L, nuv, "oid"); rowoid = (Oid) lua_tointeger(L, -1); lua_pop(L, 1); } nargs = 0; for (i = 0; i < src_t->natts; ++i) { if (TupleDescAttr(src_t->tupdesc, i)->attisdropped) continue; if (droplist && droplist[i]) continue; if (lua_geti(L, nuv, i+1) == LUA_TBOOLEAN) { /* we already skipped dropped cols so this must be a null */ lua_pop(L, 1); lua_pushnil(L); } ++nargs; } /* deal with added cols */ Assert(nargs <= dst_t->arity); for (; nargs < dst_t->arity; ++nargs) lua_pushnil(L); lua_call(L, nargs+1, 1); { pllua_datum *newd = pllua_checkdatum(L, -1, lua_upvalueindex(2)); if (dst_t->hasoid && src_t->hasoid) HeapTupleHeaderSetOid((HeapTupleHeader) DatumGetPointer(newd->value), rowoid); if (dst_t->basetype != dst_t->typeoid) domain_check(newd->value, false, dst_t->typeoid, &dst_t->domain_extra, dst_t->mcxt); } return 1; } /* * Raise error for unconvertible type * * upvalue 1 is the name of the source type, 2 the destination type */ static int pllua_typeconv_error(lua_State *L) { const char *srcname = lua_tostring(L, lua_upvalueindex(1)); const char *dstname = lua_tostring(L, lua_upvalueindex(2)); luaL_error(L, "cannot cast from type %s to %s", (srcname ? srcname : "(unknown)"), (dstname ? dstname : "(unknown)")); return 0; } /* * func = create(src_t,dst_t) * * This does the whole work of determining what type of conversion to apply, * creating the necessary closure, and entering it into the table. * * Returns no result if no cast possible */ static int pllua_typeconv_create(lua_State *L) { pllua_typeinfo *src_t = pllua_checktypeinfo(L, 1, false); pllua_typeinfo *dst_t = pllua_checktypeinfo(L, 2, true); Oid srctype = src_t->basetype; Oid dsttype = dst_t->basetype; if (dst_t->modified || dst_t->obsolete) luaL_error(L, "cannot cast value to modified or obsolete type"); /* * Don't look for cast functions for record->x or x->record, or for * any source type which is modified or obsolete (since the cast will * expect the newer form) */ if (src_t->natts < 0 && dst_t->natts < 0 && !src_t->modified && !src_t->obsolete) { volatile CoercionPathType pathtype; volatile CoercionPathType elempathtype = COERCION_PATH_NONE; volatile Oid funcid; volatile bool typmod_arg = false; PLLUA_TRY(); { Oid fnoid = InvalidOid; pathtype = find_coercion_pathway(dsttype, srctype, COERCION_ASSIGNMENT, &fnoid); if (pathtype == COERCION_PATH_ARRAYCOERCE) { Assert(dst_t->is_array && src_t->is_array); elempathtype = find_coercion_pathway(dst_t->elemtype, src_t->elemtype, COERCION_ASSIGNMENT, &fnoid); Assert(elempathtype != COERCION_PATH_NONE); } funcid = fnoid; if (OidIsValid(fnoid) && get_func_nargs(fnoid) > 1) typmod_arg = true; } PLLUA_CATCH_RETHROW(); switch (pathtype) { case COERCION_PATH_NONE: break; case COERCION_PATH_RELABELTYPE: funcid = InvalidOid; /*FALLTHROUGH*/ case COERCION_PATH_FUNC: case COERCION_PATH_ARRAYCOERCE: lua_pushvalue(L, 1); lua_pushvalue(L, 2); switch (elempathtype) { default: break; case COERCION_PATH_NONE: /* (non-array case) */ lua_pushinteger(L, (lua_Integer) funcid); break; case COERCION_PATH_RELABELTYPE: lua_pushnil(L); break; case COERCION_PATH_FUNC: lua_pushinteger(L, (lua_Integer) funcid); break; case COERCION_PATH_COERCEVIAIO: lua_pushinteger(L, (lua_Integer) InvalidOid); break; } if (OidIsValid(funcid)) pllua_pgfunc_new(L); else lua_pushnil(L); if (!typmod_arg && dst_t->basetypmod >= 0) pllua_pgfunc_new(L); else lua_pushnil(L); if (elempathtype == COERCION_PATH_COERCEVIAIO) { pllua_get_user_field(L, 1, "elemtypeinfo"); pllua_get_user_field(L, 2, "elemtypeinfo"); } else { lua_pushnil(L); lua_pushnil(L); } lua_pushcclosure(L, ((pathtype == COERCION_PATH_ARRAYCOERCE) ? pllua_typeconv_array_coerce : pllua_typeconv_scalar_coerce_func), 7); return 1; case COERCION_PATH_COERCEVIAIO: lua_pushvalue(L, 1); lua_pushvalue(L, 2); if (dst_t->typeoid != dst_t->basetype) pllua_get_user_field(L, 2, "basetype"); else lua_pushvalue(L, 2); lua_pushcclosure(L, pllua_typeconv_scalar_coerce_via_io, 3); return 1; lua_pushvalue(L, 1); lua_pushvalue(L, 2); if (!typmod_arg && dst_t->basetypmod >= 0) pllua_pgfunc_new(L); else lua_pushnil(L); lua_pushcclosure(L, pllua_typeconv_array_coerce, 5); return 1; } } /* * if not found, try a row cast * * We don't expect all cases to work. */ if (src_t->natts >= 0 && dst_t->natts >= 0) { int i,j; int arity = 0; bool sametype = (src_t->basetype != RECORDOID && src_t->basetype == dst_t->basetype); char droplist[MaxTupleAttributeNumber + 1]; bool need_droplist = false; memset(droplist, 0, src_t->natts * sizeof(char)); for (i = 0, j = 0; i < src_t->natts && j < dst_t->natts; ++i) { Form_pg_attribute s_att = TupleDescAttr(src_t->tupdesc, i); Form_pg_attribute d_att = TupleDescAttr(dst_t->tupdesc, j); /* * How we match up columns depends on the relationship between the * two types. * * If both are RECORD, neither will have dropped cols, so we just * line up the fields and expect it to work. * * If both are the same named type, they must be different * versions, and the dest type must be the newer one (since we * don't allow casting to old versions). So we expect dropped cols * to match up except that the newer version might drop more cols, * and it might also have new cols at the end. * * If they are unrelated types, we just line everything up. */ if (s_att->attisdropped) { /* * If the source col is dropped, skip the dest col if it's also * dropped, but only in the same-type case. */ if (sametype && d_att->attisdropped) ++j; continue; } /* * If the dest col is dropped, skip the source col in the sametype * case; otherwise just skip the desc col (retrying the main loop * with the same source col). */ if (d_att->attisdropped) { ++j; if (sametype) { need_droplist = true; droplist[i] = 1; continue; } else { --i; continue; } } ++arity; } /* * If we didn't exhaust the source cols, then the source has higher * arity, which we don't allow. Otherwise, make the closure. */ if (i == src_t->natts) { lua_pushvalue(L, 1); lua_pushvalue(L, 2); if (need_droplist) lua_pushlstring(L, droplist, src_t->natts); else lua_pushnil(L); lua_pushcclosure(L, pllua_typeconv_row_coerce, 3); return 1; } } /* * If we didn't construct a cast, then create an error closure as a * negative cache entry. Use the names of the types in the error. */ lua_getfield(L, 1, "name"); lua_pushvalue(L, 1); lua_call(L, 1, 1); lua_getfield(L, 2, "name"); lua_pushvalue(L, 2); lua_call(L, 1, 1); lua_pushcclosure(L, pllua_typeconv_error, 2); return 1; } /* * func = __index(tab,key) where key is dest typeinfo * * source typeinfo is in upvalue 1 */ static int pllua_typeconv_index(lua_State *L) { lua_settop(L, 2); luaL_checktype(L, 1, LUA_TTABLE); lua_pushcfunction(L, pllua_typeconv_create); lua_pushvalue(L, lua_upvalueindex(1)); lua_pushvalue(L, 2); lua_call(L, 2, 1); if (!lua_isfunction(L, -1)) luaL_error(L, "could not construct cast"); /* stack: tab key val */ lua_pushvalue(L, -1); lua_insert(L, -3); /* stack: tab val key val */ lua_rawset(L, -4); return 1; } /* * Create a typeinfo table and store it in the table at "tabidx" * (under the key "typeinfo"). typeidx denotes the typeinfo object * over which we will close the index method. */ static void pllua_typeconv_newtable(lua_State *L, int tabidx, int typeidx) { pllua_new_weak_table(L, "k", "typeconv table"); lua_pushvalue(L, typeidx); lua_pushcclosure(L, pllua_typeconv_index, 1); lua_setfield(L, -2, "__index"); lua_pop(L, 1); lua_setfield(L, tabidx, "typeconv"); } static void pllua_typeconv_register(lua_State *L, int tabidx, int typeidx) { tabidx = lua_absindex(L, tabidx); typeidx = lua_absindex(L, typeidx); pllua_typeconv_newtable(L, tabidx, typeidx); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TYPECONV_REGISTRY); lua_pushvalue(L, tabidx); lua_pushvalue(L, typeidx); lua_rawset(L, -3); lua_pop(L, 1); } /* * invalidate(interp) */ int pllua_typeconv_invalidate(lua_State *L) { lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TYPECONV_REGISTRY); lua_pushnil(L); while (lua_next(L, -2)) { pllua_typeconv_newtable(L, lua_absindex(L, -2), lua_absindex(L, -1)); lua_pop(L, 1); } return 0; } static struct luaL_Reg typeinfo_mt[] = { { "__eq", pllua_typeinfo_eq }, { "__gc", pllua_typeinfo_gc }, { "__tostring", pllua_dump_typeinfo }, { "__call", pllua_typeinfo_call }, { NULL, NULL } }; static struct luaL_Reg typeinfo_methods[] = { { "fromstring", pllua_typeinfo_fromstring }, { "frombinary", pllua_typeinfo_frombinary }, { "element", pllua_typeinfo_element }, { "dump", pllua_dump_typeinfo }, { "name", pllua_typeinfo_name }, { NULL, NULL } }; static struct luaL_Reg typeinfo_funcs[] = { { NULL, NULL } }; static struct luaL_Reg typeinfo_package_mt[] = { { "__index", pllua_typeinfo_package_index }, { "__call", pllua_typeinfo_package_call }, { NULL, NULL } }; static struct luaL_Reg typeinfo_package_array_mt[] = { { "__index", pllua_typeinfo_package_array_index }, { NULL, NULL } }; int pllua_open_pgtype(lua_State *L) { #if LUAJIT_VERSION_NUM > 0 && !defined(NO_LUAJIT) if (luaL_loadstring(L, luajit_lua) == LUA_OK) { lua_call(L, 0, 2); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_INT8HACK_OUTFUNC); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_INT8HACK_INFUNC); } else lua_error(L); #endif lua_newtable(L); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_TYPES); lua_newtable(L); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_RECORDS); /* * key-weak table for registering typeconv tables. This actually references * the table _containing_ the typeconv, so that we can just replace the * table with an empty one rather than having to delete its keys. * * The values are the source typeinfos, so those can be weak too. */ pllua_new_weak_table(L, "kv", "typeconv registry table"); lua_pop(L, 1); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_TYPECONV_REGISTRY); pllua_newmetatable(L, PLLUA_IDXLIST_OBJECT, idxlist_mt); lua_pop(L, 1); pllua_newmetatable(L, PLLUA_TYPEINFO_OBJECT, typeinfo_mt); lua_newtable(L); luaL_setfuncs(L, typeinfo_methods, 0); lua_setfield(L, -2, "__index"); lua_pop(L, 1); lua_newtable(L); pllua_newmetatable(L, PLLUA_TYPEINFO_PACKAGE_OBJECT, typeinfo_package_mt); lua_setmetatable(L, -2); lua_newtable(L); pllua_newmetatable(L, PLLUA_TYPEINFO_PACKAGE_ARRAY_OBJECT, typeinfo_package_array_mt); lua_setmetatable(L, -2); lua_setfield(L, -2, "array"); luaL_setfuncs(L, typeinfo_funcs, 0); return 1; } pllua-ng-REL_2_0_4/src/elog.c000066400000000000000000000320451347047754200160020ustar00rootroot00000000000000/* elog.c */ #include "pllua.h" /* * pllua_elog * * Calling ereport with dynamic information is ugly. This needs a catch block * even for severity less than ERROR, because it could throw a memory error * while building the error object. */ static void pllua_elog(lua_State *L, int elevel, bool hidecontext, int e_code, const char *e_message, const char *e_detail, const char *e_hint, const char *e_column, const char *e_constraint, const char *e_datatype, const char *e_table, const char *e_schema) { PLLUA_TRY(); { ereport(elevel, (e_code ? errcode(e_code) : 0, (hidecontext ? errhidecontext(true) : 0), errmsg_internal("%s", e_message), (e_detail != NULL) ? errdetail_internal("%s", e_detail) : 0, (e_hint != NULL) ? errhint("%s", e_hint) : 0, (e_column != NULL) ? err_generic_string(PG_DIAG_COLUMN_NAME, e_column) : 0, (e_constraint != NULL) ? err_generic_string(PG_DIAG_CONSTRAINT_NAME, e_constraint) : 0, (e_datatype != NULL) ? err_generic_string(PG_DIAG_DATATYPE_NAME, e_datatype) : 0, (e_table != NULL) ? err_generic_string(PG_DIAG_TABLE_NAME, e_table) : 0, (e_schema != NULL) ? err_generic_string(PG_DIAG_SCHEMA_NAME, e_schema) : 0)); } PLLUA_CATCH_RETHROW(); } /* * Internal support for pllua_debug. */ void pllua_debug_lua(lua_State *L, const char *msg, ...) { luaL_Buffer b; char *buf; va_list va; luaL_buffinit(L, &b); buf = luaL_prepbuffer(&b); va_start(va, msg); vsnprintf(buf, LUAL_BUFFERSIZE, msg, va); va_end(va); luaL_addsize(&b, strlen(buf)); luaL_pushresult(&b); msg = lua_tostring(L, -1); pllua_elog(L, DEBUG1, true, 0, msg, NULL, NULL, NULL, NULL, NULL, NULL, NULL); lua_pop(L, 1); } void pllua_warning(lua_State *L, const char *msg, ...) { luaL_Buffer b; char *buf; va_list va; luaL_buffinit(L, &b); buf = luaL_prepbuffer(&b); va_start(va, msg); vsnprintf(buf, LUAL_BUFFERSIZE, msg, va); va_end(va); luaL_addsize(&b, strlen(buf)); luaL_pushresult(&b); msg = lua_tostring(L, -1); pllua_elog(L, WARNING, true, 0, msg, NULL, NULL, NULL, NULL, NULL, NULL, NULL); lua_pop(L, 1); } static void pllua_where(lua_State *L, int level) { lua_Debug ar; lua_CFunction fn; luaL_checkstack(L, 3, NULL); while (lua_getstack(L, level, &ar)) { lua_getinfo(L, "Slf", &ar); fn = lua_tocfunction(L, -1); lua_pop(L, 1); /* * Treat these functions (all the possible args of * pllua_initial_protected_call) as being barriers to error traceback. */ if (fn == pllua_resume_function || fn == pllua_call_function || fn == pllua_call_trigger || fn == pllua_call_event_trigger || fn == pllua_validate || fn == pllua_call_inline) break; if (ar.currentline > 0) { lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline); return; } ++level; } lua_pushfstring(L, ""); /* else, no information available... */ } void pllua_error (lua_State *L, const char *fmt, ...) { va_list argp; va_start(argp, fmt); pllua_where(L, 1); lua_pushvfstring(L, fmt, argp); va_end(argp); lua_concat(L, 2); lua_error(L); pg_unreachable(); } /* * The use of errdepth is a bit subtle. The main lua stack is shared between * all non-SRF function calls, so if a lua function calls another via SPI, then * we want to scan only a small part of the stack each time. * * So errdepth starts out at 0 (it can only be left non-zero if something * errors out of the error context stack, and in that case the topmost catch * block will clean it up), and each traverse of the main stack stops when it * hits a top-level call and stores the current level back in errdepth for the * next context callback to use. However, if the activation we're looking at is * for a running SRF, then it's on its own private stack, so we ignore all this * and start at the stack top (the update_errdepth=false case). * * This does mean that the scan of the stack must not stop until it reaches a * barrier or runs out of stack; stopping when we find the frame of interest * for the error wouldn't leave the right errdepth for the next call. */ int pllua_error_callback_location(lua_State *L) { pllua_interpreter *interp = lua_touserdata(L, 1); lua_Debug *ar = &interp->ar; lua_CFunction fn; int level = interp->update_errdepth ? interp->errdepth : 1; bool found = false; while (lua_getstack(L, level, ar)) { lua_getinfo(L, found ? "f" : "Slf", ar); fn = lua_tocfunction(L, -1); lua_pop(L, 1); /* * Treat these functions (all the possible args of * pllua_initial_protected_call) as being barriers to error traceback. */ if (fn == pllua_resume_function || fn == pllua_call_function || fn == pllua_call_trigger || fn == pllua_call_event_trigger || fn == pllua_validate || fn == pllua_call_inline) { if (interp->update_errdepth) { /* * If the barrier is actually the bottom frame on the stack, * then we completed the traverse. Otherwise the next scan will * start just below it. */ ++level; if (!lua_getstack(L, level, ar)) interp->errdepth = 0; else interp->errdepth = level; } return 0; } if (!found && ar->currentline > 0) { found = true; } ++level; } if (!found) ar->currentline = 0; if (interp->update_errdepth) interp->errdepth = 0; return 0; } void pllua_error_callback(void *arg) { pllua_activation_record *act = arg; lua_State *thr = NULL; FunctionCallInfo fcinfo; int rc; if (!act) return; if (!act->interp) { errcontext("during PL/Lua interpreter setup"); return; } if (pllua_context != PLLUA_CONTEXT_PG) return; /* dangerous elog call! */ thr = act->interp->L; fcinfo = act->fcinfo; if (fcinfo && fcinfo->flinfo && fcinfo->flinfo->fn_extra && ((pllua_func_activation *)(fcinfo->flinfo->fn_extra))->onstack) { thr = ((pllua_func_activation *)(fcinfo->flinfo->fn_extra))->thread; act->interp->update_errdepth = false; } else act->interp->update_errdepth = true; rc = pllua_cpcall(thr, pllua_error_callback_location, act->interp); if (rc == 0 && act->interp->ar.currentline > 0) errcontext("Lua function %s at line %d", act->interp->ar.short_src, act->interp->ar.currentline); } static int pllua_get_sqlstate(lua_State *L, int tidx, const char *str) { if (strlen(str) == 5 && strspn(str, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") == 5) { return MAKE_SQLSTATE(str[0], str[1], str[2], str[3], str[4]); } else { int code; lua_getfield(L, tidx, str); code = lua_tointeger(L, -1); lua_pop(L, 1); return code; } } #if LUA_VERSION_NUM == 501 const char *luaL_tolstring(lua_State *L, int idx, size_t *len) { if (!luaL_callmeta(L, idx, "__tostring")) { int t = lua_type(L, idx), tt = 0; char const* name = NULL; switch (t) { case LUA_TNIL: lua_pushliteral(L, "nil"); break; case LUA_TSTRING: case LUA_TNUMBER: lua_pushvalue(L, idx); break; case LUA_TBOOLEAN: if (lua_toboolean(L, idx)) lua_pushliteral(L, "true"); else lua_pushliteral(L, "false"); break; default: tt = luaL_getmetafield(L, idx, "__name"); name = (tt == LUA_TSTRING) ? lua_tostring(L, -1) : lua_typename(L, t); lua_pushfstring(L, "%s: %p", name, lua_topointer(L, idx)); if (tt != LUA_TNIL) lua_replace(L, -2); break; } } else { if (!lua_isstring(L, -1)) luaL_error(L, "'__tostring' must return a string"); } return lua_tolstring(L, -1, len); } #endif /* * User-visible global "print" function. * * This is installed in place of _G.print */ int pllua_p_print(lua_State *L) { int nargs = lua_gettop(L); /* nargs */ int elevel = LOG; const char *s; luaL_Buffer b; int i; if (lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_PRINT_SEVERITY) == LUA_TNUMBER) { elevel = lua_tointeger(L, -1); if (elevel > WARNING || elevel < DEBUG5) elevel = LOG; } lua_pop(L, 1); luaL_buffinit(L, &b); for (i = 1; i <= nargs; i++) { if (i > 1) luaL_addchar(&b, '\t'); luaL_tolstring(L, i, NULL); luaL_addvalue(&b); } luaL_pushresult(&b); s = lua_tostring(L, -1); pllua_elog(L, elevel, true, 0, s, NULL, NULL, NULL, NULL, NULL, NULL, NULL); return 0; } int pllua_open_print(lua_State *L) { lua_pushinteger(L, LOG); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_PRINT_SEVERITY); lua_newtable(L); lua_pushcfunction(L, pllua_p_print); lua_pushvalue(L, -1); lua_setglobal(L, "print"); lua_setfield(L, -2, "print"); return 1; } /* * we accept: * * error("message") * error("sqlstate", "message", ["detail", ["hint"]]) * error{ sqlstate = ?, message = ?, detail = ?, hint = ?, * column = ?, constraint = ?, datatype = ?, * table = ?, schema = ? } * * elog("error", ...) * * pllua_p_elog exists under multiple closures, with upvalues: * * 1: elevel integer, or nil for the elog() version * 2: table mapping "error", "notice" etc. to elevels * 3: table mapping error names to sqlstates (lazy init) */ static int pllua_p_elog(lua_State *L) { bool is_elog = lua_isnil(L, lua_upvalueindex(1)); int elevel; int e_code = 0; const char *e_message = NULL; const char *e_detail = NULL; const char *e_hint = NULL; const char *e_column = NULL; const char *e_constraint = NULL; const char *e_datatype = NULL; const char *e_table = NULL; const char *e_schema = NULL; if (is_elog) { lua_getfield(L, lua_upvalueindex(2), luaL_tolstring(L, 1, NULL)); if (!lua_isinteger(L, -1)) luaL_error(L, "unknown elevel for elog()"); elevel = lua_tointeger(L, -1); lua_pop(L, 2); lua_remove(L, 1); } else elevel = lua_tointeger(L, lua_upvalueindex(1)); if (lua_gettop(L) == 1 && lua_istable(L, 1)) { int ss = lua_gettop(L); luaL_checkstack(L, 30, NULL); lua_getfield(L, 1, "sqlstate"); if (!lua_isnil(L, -1)) e_code = pllua_get_sqlstate(L, lua_upvalueindex(3), luaL_tolstring(L, -1, NULL)); lua_getfield(L, 1, "message"); if (!lua_isnil(L, -1)) e_message = luaL_tolstring(L, -1, NULL); lua_getfield(L, 1, "detail"); if (!lua_isnil(L, -1)) e_detail = luaL_tolstring(L, -1, NULL); lua_getfield(L, 1, "hint"); if (!lua_isnil(L, -1)) e_hint = luaL_tolstring(L, -1, NULL); lua_getfield(L, 1, "column"); if (!lua_isnil(L, -1)) e_column = luaL_tolstring(L, -1, NULL); lua_getfield(L, 1, "constraint"); if (!lua_isnil(L, -1)) e_constraint = luaL_tolstring(L, -1, NULL); lua_getfield(L, 1, "datatype"); if (!lua_isnil(L, -1)) e_datatype = luaL_tolstring(L, -1, NULL); lua_getfield(L, 1, "table"); if (!lua_isnil(L, -1)) e_table = luaL_tolstring(L, -1, NULL); lua_getfield(L, 1, "schema"); if (!lua_isnil(L, -1)) e_schema = luaL_tolstring(L, -1, NULL); lua_settop(L, ss); } else { switch (lua_gettop(L)) { case 1: e_message = luaL_tolstring(L, 1, NULL); break; case 4: e_hint = luaL_tolstring(L, 4, NULL); /*FALLTHROUGH*/ case 3: e_detail = luaL_tolstring(L, 3, NULL); /*FALLTHROUGH*/ case 2: e_message = luaL_tolstring(L, 2, NULL); e_code = pllua_get_sqlstate(L, lua_upvalueindex(3), luaL_tolstring(L, 1, NULL)); break; default: luaL_error(L, "wrong number of parameters to elog"); } } if (!e_message) e_message = "(no message given)"; /* * Demand consistency between elevel and sqlstate (ignore the sqlstate if * mismatch). Categories 00, 01 and 02 are not errors, anything else is an * error. */ switch (ERRCODE_TO_CATEGORY(e_code)) { case MAKE_SQLSTATE('0','0','0','0','0'): case MAKE_SQLSTATE('0','1','0','0','0'): case MAKE_SQLSTATE('0','2','0','0','0'): if (elevel >= ERROR) e_code = 0; break; default: if (elevel < ERROR) e_code = 0; break; } pllua_elog(L, elevel, false, e_code, e_message, e_detail, e_hint, e_column, e_constraint, e_datatype, e_table, e_schema); return 0; } static struct { const char *str; int val; } elevels[] = { { "debug", DEBUG1 }, { "log", LOG }, { "info", INFO }, { "notice", NOTICE }, { "warning", WARNING }, { "error", ERROR } }; int pllua_open_elog(lua_State *L) { int i; int nlevels = sizeof(elevels)/sizeof(elevels[0]); lua_newtable(L); lua_pushnil(L); lua_createtable(L, 0, nlevels); for (i = 0; i < nlevels; ++i) { lua_pushinteger(L, elevels[i].val); lua_setfield(L, -2, elevels[i].str); } lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_ERRCODES_TABLE); for (i = 0; i < nlevels; ++i) { lua_pushinteger(L, elevels[i].val); lua_pushvalue(L, -3); lua_pushvalue(L, -3); lua_pushcclosure(L, pllua_p_elog, 3); lua_setfield(L, -5, elevels[i].str); } lua_pushcclosure(L, pllua_p_elog, 3); lua_pushvalue(L, -1); lua_setfield(L, -3, "elog"); /* * if we're in the postmaster, then preload the error table, by * outputting a log message. */ if (!IsUnderPostmaster) { const char *ident = NULL; lua_pushstring(L, "log"); lua_pushstring(L, "successful_completion"); lua_pushstring(L, "PL/Lua preloaded in postmaster"); lua_getglobal(L, "_PL_IDENT"); ident = lua_tostring(L, -1); lua_pushfstring(L, "_PL_IDENT value is %s", (ident && *ident) ? ident : "empty"); lua_remove(L, -2); lua_call(L, 4, 0); } else lua_pop(L, 1); return 1; } pllua-ng-REL_2_0_4/src/error.c000066400000000000000000001132401347047754200162020ustar00rootroot00000000000000/* error.c */ #include "pllua.h" #include "access/xact.h" #include "utils/resowner.h" /* * Only used in interpreter startup. */ int pllua_panic(lua_State *L) { elog(pllua_context == PLLUA_CONTEXT_PG ? ERROR : PANIC, "Uncaught Lua error: %s", (lua_type(L, -1) == LUA_TSTRING ? lua_tostring(L, -1) : "(not a string)")); return 0; } /* * Some places will downgrade errors in very rare circumstances, but we need to * know if this happens. */ void pllua_poperror(lua_State *L) { pllua_warning(L, "Ignored Lua error: %s", (lua_type(L, -1) == LUA_TSTRING ? lua_tostring(L, -1) : "(not a string)")); lua_pop(L, 1); } /* * Register an error object as being active. This can throw. * */ int pllua_register_error(lua_State *L) { pllua_interpreter *interp = pllua_getinterpreter(L); if (interp) { int oref = interp->cur_activation.active_error; /* * do the ref call before the unref, so that if luaL_ref throws, the * old value is unchanged and still valid */ lua_settop(L, 1); /* if we're in recursive error handling, then don't overwrite that. */ if (oref == LUA_NOREF) return 0; /* * if we're trying to register the current error, skip the luaL_ref * call (which can throw in extreme cases) */ if (oref != LUA_REFNIL) { lua_rawgeti(L, LUA_REGISTRYINDEX, oref); if (lua_rawequal(L, -1, -2)) return 0; } interp->cur_activation.active_error = luaL_ref(L, LUA_REGISTRYINDEX); luaL_unref(L, LUA_REGISTRYINDEX, oref); } return 0; } static void pllua_register_recursive_error(lua_State *L) { pllua_interpreter *interp = pllua_getinterpreter(L); if (interp) { luaL_unref(L, LUA_REGISTRYINDEX, interp->cur_activation.active_error); interp->cur_activation.active_error = LUA_NOREF; } lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_RECURSIVE_ERROR); } static void pllua_deregister_error(lua_State *L) { pllua_interpreter *interp = pllua_getinterpreter(L); if (interp) { luaL_unref(L, LUA_REGISTRYINDEX, interp->cur_activation.active_error); interp->cur_activation.active_error = LUA_REFNIL; } } static bool pllua_get_active_error(lua_State *L) { pllua_interpreter *interp = pllua_getinterpreter(L); if (interp && interp->cur_activation.active_error != LUA_REFNIL) { if (interp->cur_activation.active_error == LUA_NOREF) lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_RECURSIVE_ERROR); else lua_rawgeti(L, LUA_REGISTRYINDEX, interp->cur_activation.active_error); return true; } return false; } /* * This is called as the last thing on the error path before rethrowing the * error completely back to pg (but note that we might still have catch blocks * on the stack from an outer nested activation, e.g. as a result of SPI * calls). Resetting errdepth keeps the error context traversal stuff working * in the event that the context traversal itself raises an error; we can also * safely remove any currently active PG error from the Lua registry because it * must have been copied into the PG error handling. (However, we have to do * this in a way that can't possibly throw a Lua error.) */ void pllua_error_cleanup(pllua_interpreter *interp, pllua_activation_record *act) { interp->errdepth = 0; if (act->active_error != LUA_REFNIL) { /* luaL_unref is guaranteed to not throw */ luaL_unref(interp->L, LUA_REGISTRYINDEX, act->active_error); act->active_error = LUA_REFNIL; } } /* * Create a new error object and record it as entering the error system. * * lightuserdata param is expected to be an ErrorData. */ int pllua_newerror(lua_State *L) { void *p = lua_touserdata(L, 1); pllua_newrefobject(L, PLLUA_ERROR_OBJECT, p, false); lua_pushcfunction(L, pllua_register_error); lua_pushvalue(L, -2); lua_call(L, 1, 0); return 1; } /* * Create an ErrorData (allocated in the caller's memory context) for our * special "recursive error in error handling" error. * * This might itself throw a PG error, which is why it's essentially isolated * from the rest of the module; init.c calls it at a safe point before actually * entering Lua. Hence it doesn't even get a lua_State param. */ ErrorData * pllua_make_recursive_error(void) { ErrorData *volatile edata = NULL; /* * The sole reason for a catch block is to ensure that even if we're called * from postmaster, we don't promote the error to FATAL on account of * having an empty catch stack. */ PG_TRY(); { MemoryContext oldcontext = CurrentMemoryContext; if (!errstart(ERROR, __FILE__, __LINE__, PG_FUNCNAME_MACRO, TEXTDOMAIN)) elog(ERROR, "errstart tried to ignore ERROR"); /* populate error data */ errcode(ERRCODE_INTERNAL_ERROR); errmsg("Unexpected error in error handling"); /* errstart switched to error context. switch back */ MemoryContextSwitchTo(oldcontext); /* grab copy of error */ edata = CopyErrorData(); /* and flush it back out of the error subsystem */ FlushErrorState(); } PG_CATCH(); { PG_RE_THROW(); } PG_END_TRY(); return edata; } /* * Execute a Lua protected call without rethrowing any error caught. The caller * must arrange the rethrow themselves; this is used when some cleanup is * needed between catch and rethrow. */ int pllua_pcall_nothrow(lua_State *L, int nargs, int nresults, int msgh) { pllua_context_type oldctx = pllua_setcontext(PLLUA_CONTEXT_LUA); int rc; rc = lua_pcall(L, nargs, nresults, msgh); /* check for violation of protocol */ Assert(pllua_context == PLLUA_CONTEXT_LUA); pllua_setcontext(oldctx); return rc; } /* * To be called in an ereport() parameter list; if the top of the lua stack is * a string, then pass it to errmsg_internal (which will copy it); if not, * supply a default message. Either way, pop the lua stack. */ static int pllua_errmsg(lua_State *L) { if (lua_type(L, -1) == LUA_TSTRING) errmsg_internal("pllua: %s", lua_tostring(L, -1)); else errmsg_internal("pllua: (error is not a string: type=%d)", lua_type(L, -1)); lua_pop(L, 1); return 0; /* ignored */ } /* * Having caught an error in lua, which is now on top of the stack (as returned * by pcall), rethrow it either back into lua or into pg according to what * context we're now in. */ void pllua_rethrow_from_lua(lua_State *L, int rc) { if (pllua_context == PLLUA_CONTEXT_LUA) lua_error(L); /* * If out of memory, avoid doing anything even slightly fancy. */ if (rc == LUA_ERRMEM) { lua_pop(L, -1); elog(ERROR, "pllua: out of memory"); } /* * The thing on top of the stack is either a lua object with a pg error, a * string, or something else. If it's a pg error, ensure it is registered * and rethrow it. */ if (pllua_isobject(L, -1, PLLUA_ERROR_OBJECT)) { ErrorData **p = lua_touserdata(L, -1); ErrorData *edata = *p; pllua_pushcfunction(L, pllua_register_error); lua_insert(L, -2); if (pllua_pcall_nothrow(L, 1, 0, 0) != 0) { pllua_poperror(L); pllua_register_recursive_error(L); p = lua_touserdata(L, -1); if (p && *p) edata = *p; /* safe to pop since the value is in the registry */ lua_pop(L, 1); } if (edata) ReThrowError(edata); else elog(ERROR, "recursive error in Lua error handling"); } ereport(ERROR, (pllua_errmsg(L))); } /* * Having caught an error in PG_CATCH, rethrow it either into lua or back into * pg according to what context we're now in. Note, we must have restored the * previous context; the idiom is: * * PLLUA_TRY(); * { * } * PLLUA_CATCH_RETHROW(); * * There had better be space on the Lua stack for a couple of objects - it's * the caller's responsibility to deal with that. * * Remember the correct protocol for volatile variables! Any variable modified * in the try block and used in the catch block should be declared volatile, * but for pointer variables remember that it's the _variable_ which is * volatile, not the data pointed at. So declarations typically look like * * ErrorData *volatile edata; * * (and NOT like volatile ErrorData *edata) * * (This code is cautious about volatility issues and tends to use it much more * often than actually needed.) */ /* * Absorb a PG error leaving it on the Lua stack. Switches memory contexts! */ static ErrorData * pllua_absorb_pg_error(lua_State *L) { ErrorData *volatile edata = NULL; MemoryContext emcxt; lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_ERRORCONTEXT); emcxt = lua_touserdata(L, -1); lua_pop(L, 1); MemoryContextSwitchTo(emcxt); /* * absorb the error and exit pg's error handling. * * Unfortunately, CopyErrorData can fail, and we can't afford to rethrow * via pg since we're in lua context, so we have to catch that. * FlushErrorState can fail too, but only if things are really badly * screwed up (memory corruption in ErrorContext) so we can legitimately * die if things get that far. (Honestly, the chances that elog won't * already have recursed itself to death in that case are slim.) */ PG_TRY(); { edata = CopyErrorData(); } PG_CATCH(); { /* * recursive error, don't bother trying to figure out what. */ edata = NULL; } PG_END_TRY(); PG_TRY(); { FlushErrorState(); } PG_CATCH(); { elog(PANIC, "error recursion trouble: FlushErrorState failed"); } PG_END_TRY(); /* * make a lua object to hold the error. This can throw an out of memory * error from lua, but we don't want to let that supersede a pg error, * because we sometimes want to let the user-supplied lua code catch lua * errors but not pg errors. So if that happens, replace it with our * special prebuilt "recursive error" object. */ if (edata) { pllua_pushcfunction(L, pllua_newerror); lua_pushlightuserdata(L, (void *) edata); if (pllua_pcall_nothrow(L, 1, 1, 0) != 0) { pllua_poperror(L); pllua_register_recursive_error(L); } } else pllua_register_recursive_error(L); return edata; } void pllua_rethrow_from_pg(lua_State *L, MemoryContext mcxt) { if (pllua_context == PLLUA_CONTEXT_PG) PG_RE_THROW(); pllua_absorb_pg_error(L); MemoryContextSwitchTo(mcxt); lua_error(L); } /* * Various workarounds for how to get into Lua without throwing error. * * We need these for initial entry into lua context, and also we use them when * entering lua context from callbacks from PG (e.g. cache management or memory * context reset). * * We can only safely pass 1 arg, which will be a light userdata. * * Annoyingly, the code needed for this changes with Lua version, since which * functions can throw errors changes. Specifically, on 5.1, neither * lua_checkstack nor lua_pushcfunction are safe, but lua_cpcall is; on 5.2 or * later, lua_cpcall may be missing, but lua_checkstack and lua_pushcfunction * are supposed to be safe. Unfortunately a bug in (at least) 5.3.4 allows * lua_pushcfunction to throw error anyway; so we currently take the worst-case * assumption and rely on lua_rawgetp being safe instead. (See definition of * macro pllua_pushcfunction in pllua.h.) */ #if LUA_VERSION_NUM > 501 int pllua_cpcall(lua_State *L, lua_CFunction func, void* arg) { pllua_context_type oldctx; int rc; if (pllua_context == PLLUA_CONTEXT_PG) { if (!lua_checkstack(L, 3)) elog(ERROR, "failed to extend Lua stack"); } else luaL_checkstack(L, 3, NULL); oldctx = pllua_setcontext(PLLUA_CONTEXT_LUA); pllua_pushcfunction(L, func); /* can't throw */ lua_pushlightuserdata(L, arg); /* can't throw */ rc = lua_pcall(L, 1, 0, 0); /* check for violation of protocol */ Assert(pllua_context == PLLUA_CONTEXT_LUA); pllua_setcontext(oldctx); return rc; } #else int pllua_cpcall(lua_State *L, lua_CFunction func, void* arg) { pllua_context_type oldctx = pllua_setcontext(PLLUA_CONTEXT_LUA); int rc; rc = lua_cpcall(L, func, arg); /* check for violation of protocol */ Assert(pllua_context == PLLUA_CONTEXT_LUA); pllua_setcontext(oldctx); return rc; } #endif /* * Wrap lua_pcall to maintain our context tracking and to rethrow all errors * caught. This is the usual way to call lua-context code from within a PG * catch block. */ void pllua_pcall(lua_State *L, int nargs, int nresults, int msgh) { pllua_context_type oldctx = pllua_setcontext(PLLUA_CONTEXT_LUA); int rc; rc = lua_pcall(L, nargs, nresults, msgh); /* check for violation of protocol */ Assert(pllua_context == PLLUA_CONTEXT_LUA); pllua_setcontext(oldctx); if (rc) pllua_rethrow_from_lua(L, rc); } /* * Supplementary modules like transforms need pllua_pcall functionality, but * have no way to register their addresses for pllua_pushcfunction. So provide * this trampoline so that they can use pushlightuserdata instead. */ /* DO NOT REMOVE THIS, IT'S NEEDED FOR pllua_functable.h */ int pllua_register_cfunc(L, pllua_trampoline)(lua_State *L); int pllua_trampoline(lua_State *L) { lua_CFunction f = (lua_CFunction) lua_touserdata(L, 1); lua_pushcfunction(L, f); lua_replace(L, 1); lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); return lua_gettop(L); } /* * We store a lot of our state inside lua for convenience, but that means * we have to consider possible lua errors (e.g. out of memory) happening * even outside the actual function call. So, arrange to do all the work in * a protected environment to catch any such errors. */ void pllua_initial_protected_call(pllua_interpreter *interp, lua_CFunction func, pllua_activation_record *arg) { sigjmp_buf *cur_catch_block PG_USED_FOR_ASSERTS_ONLY = PG_exception_stack; pllua_activation_record save_activation = interp->cur_activation; int rc; Assert(pllua_context == PLLUA_CONTEXT_PG); #if LUA_VERSION_NUM > 501 if (!lua_checkstack(interp->L, 5)) elog(ERROR, "pllua: out of memory error on stack setup"); #endif interp->cur_activation = *arg; /* copies content not pointer */ rc = pllua_cpcall(interp->L, func, &interp->cur_activation); /* * We better not have longjmp'd past any pg catch blocks. */ Assert(cur_catch_block == PG_exception_stack); *arg = interp->cur_activation; /* copies content not pointer */ interp->cur_activation = save_activation; if (rc) pllua_rethrow_from_lua(interp->L, rc); Assert(arg->active_error == LUA_REFNIL); } /* * Finalizer for error objects. */ static int pllua_errobject_gc(lua_State *L) { void **p = pllua_torefobject(L, 1, PLLUA_ERROR_OBJECT); void *obj = p ? *p : NULL; *p = NULL; if (obj) { PLLUA_TRY(); { FreeErrorData(obj); } PLLUA_CATCH_RETHROW(); } return 0; } /* * Replace the user-visible coroutine.resume function with a version that * propagates PG errors from the coroutine. */ static int pllua_t_coresume (lua_State *L) { lua_State *co = lua_tothread(L, 1); int narg = lua_gettop(L) - 1; int nret; int rc; luaL_argcheck(L, co, 1, "thread expected"); if (!lua_checkstack(co, narg)) { lua_pushboolean(L, 0); lua_pushliteral(L, "too many arguments to resume"); return 2; /* error flag */ } if (lua_status(co) == LUA_OK && lua_gettop(co) == 0) { lua_pushboolean(L, 0); lua_pushliteral(L, "cannot resume dead coroutine"); return 2; /* error flag */ } lua_xmove(L, co, narg); rc = lua_resume(co, L, narg, &nret); if (rc == LUA_OK || rc == LUA_YIELD) { if (!lua_checkstack(L, nret + 1)) { lua_pop(co, nret); /* remove results anyway */ lua_pushboolean(L, 0); lua_pushliteral(L, "too many results to resume"); return 2; /* error flag */ } lua_pushboolean(L, 1); lua_xmove(co, L, nret); /* move yielded values */ return nret + 1; } else { lua_pushboolean(L, 0); lua_xmove(co, L, 1); /* move error message */ if (pllua_isobject(L, -1, PLLUA_ERROR_OBJECT)) pllua_rethrow_from_lua(L, rc); return 2; /* error flag */ } } /* * Replace the user-visible "pcall" and "xpcall" functions with * versions that catch lua errors but not pg errors. * * For now, we don't try and catch lua errors that got promoted to pg errors by * being thrown through a PG_CATCH block, though perhaps we could. Eventually * subtransaction handling will have to go here so leave that for now. (But * even with subxacts, there might be a place for a "light" pcall that doesn't * block yields, whereas the subxact handling obviously must do that.) * * These are lightly tweaked versions of the luaB_ functions. */ #if LUA_VERSION_NUM >= 502 /* ** Continuation function for 'pcall' and 'xpcall'. Both functions ** already pushed a 'true' before doing the call, so in case of success ** 'finishpcall' only has to return everything in the stack minus ** 'extra' values (where 'extra' is exactly the number of items to be ** ignored). */ static int finishpcall (lua_State *L, int status, lua_KContext extra) { if (status != LUA_OK && status != LUA_YIELD) { /* error? */ lua_pushboolean(L, 0); /* first result (false) */ lua_pushvalue(L, -2); /* error message */ if (pllua_isobject(L, -1, PLLUA_ERROR_OBJECT)) pllua_rethrow_from_lua(L, status); /* * To plug the lxpcall hole (lxpcall's error handler throws a pg error, * which lua transforms into "error in error handling"), we substitute the * pg error in the registry if any. */ if (pllua_get_active_error(L)) pllua_rethrow_from_lua(L, LUA_ERRERR); return 2; /* return false, msg */ } else return lua_gettop(L) - (int)extra; /* return all results */ } int pllua_t_lpcall (lua_State *L) { int status; PLLUA_CHECK_PG_STACK_DEPTH(); luaL_checkany(L, 1); lua_pushboolean(L, 1); /* first result if no errors */ lua_insert(L, 1); /* put it in place */ status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 0, 0, finishpcall); return finishpcall(L, status, 0); } /* ** Do a protected call with error handling. After 'lua_rotate', the ** stack will have ; so, the function passes ** 2 to 'finishpcall' to skip the 2 first values when returning results. ** ** XXX XXX we need to intercept the error handling function somehow to ** ensure it doesn't mess with a pg error. */ int pllua_t_lxpcall (lua_State *L) { int status; int n = lua_gettop(L); PLLUA_CHECK_PG_STACK_DEPTH(); luaL_checktype(L, 2, LUA_TFUNCTION); /* check error function */ lua_pushboolean(L, 1); /* first result */ lua_pushvalue(L, 1); /* function */ lua_rotate(L, 3, 2); /* move them below function's arguments */ status = lua_pcallk(L, n - 2, LUA_MULTRET, 2, 2, finishpcall); return finishpcall(L, status, 2); } #else int pllua_t_lpcall (lua_State *L) { int status; PLLUA_CHECK_PG_STACK_DEPTH(); luaL_checkany(L, 1); lua_pushboolean(L, 1); /* first result if no errors */ lua_insert(L, 1); /* put it in place */ status = lua_pcall(L, lua_gettop(L) - 2, LUA_MULTRET, 0); if (status) { /* error? */ lua_pushboolean(L, 0); /* first result (false) */ lua_pushvalue(L, -2); /* error message */ if (pllua_isobject(L, -1, PLLUA_ERROR_OBJECT)) pllua_rethrow_from_lua(L, status); return 2; /* return false, msg */ } else return lua_gettop(L); /* return all results */ } /* ** XXX XXX we need to intercept the error handling function somehow to ** ensure it doesn't mess with a pg error. */ int pllua_t_lxpcall (lua_State *L) { int status; int n = lua_gettop(L); PLLUA_CHECK_PG_STACK_DEPTH(); luaL_checktype(L, 2, LUA_TFUNCTION); /* check error function */ lua_pushboolean(L, 1); /* first result */ lua_insert(L, 3); lua_pushvalue(L, 1); /* function */ lua_insert(L, 4); status = lua_pcall(L, n - 2, LUA_MULTRET, 2); if (status) { /* error? */ lua_pushboolean(L, 0); /* first result (false) */ lua_pushvalue(L, -2); /* error message */ if (pllua_isobject(L, -1, PLLUA_ERROR_OBJECT)) pllua_rethrow_from_lua(L, status); if (pllua_get_active_error(L)) pllua_rethrow_from_lua(L, LUA_ERRERR); return 2; /* return false, msg */ } else return lua_gettop(L) - 2; /* return all results */ } #endif /* * Wrap pcall and xpcall for subtransaction handling. * * pcall func,args... * xpcall func,errfunc,args... * * In the case of xpcall, the subxact is aborted and released before the * user-supplied error function is called, though the stack isn't unwound. If * the error function itself throws a Lua error, Lua would replace that with * its own "error in error handling" error; if this happens while catching a PG * error, then we replace it in turn with our own recursive error object which * we rethrow. If the error handling function throws a PG error, we rethrow * that into the outer context. * * This is all aimed at preserving the following invariant: we can only run the * user's Lua code inside an error-free subtransaction. */ typedef struct pllua_subxact { volatile struct pllua_subxact *prev; bool onstack; ResourceOwner resowner; MemoryContext mcontext; ResourceOwner own_resowner; } pllua_subxact; static volatile pllua_subxact *subxact_stack_top = NULL; static void pllua_subxact_abort(lua_State *L) { PLLUA_TRY(); { volatile pllua_subxact *xa = subxact_stack_top; Assert(xa->onstack); xa->onstack = false; subxact_stack_top = xa->prev; RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(xa->mcontext); CurrentResourceOwner = xa->resowner; } PLLUA_CATCH_RETHROW(); } /* * Wrap the user-supplied error function (which is in upvalue 1) * Upvalue 2 is a recursion flag. * * We don't provide the user with another subxact, though they can * do that themselves. We do have to guarantee that if they throw * their own pg error (and don't catch it in a subxact) then it * gets rethrown outwards eventually. */ static int pllua_intercept_error(lua_State *L) { int rc; if (!lua_toboolean(L, lua_upvalueindex(2))) { lua_pushboolean(L, 1); lua_replace(L, lua_upvalueindex(2)); /* * It's possible to get here with a non-pg error as the current error * value while there's a pg error in the registry. But if we're * catching a pg error, it should be the most recent one thrown. * * However, if the user did error(e) using an old caught error object * (which is now deregistered), there might not be a pending registered * pg error at all; this is a valid case. */ if (pllua_isobject(L, 1, PLLUA_ERROR_OBJECT)) { if (pllua_get_active_error(L)) { Assert(lua_rawequal(L, 1, -1)); lua_pop(L, 1); } } /* * At this point, the error (which could be either a lua error or a PG * error) has been caught and is on stack as our first arg; if it's a * pg error, it's been flushed from PG's error handling but is still * referenced from the registry. The current subxact is not aborted * yet. * * If it's a lua error, we don't actually have to abort the subxact now * to preserve our invariant, but it would be deeply inconsistent not * to. * * We are still within the subtransaction's pcall at this stage; any * error will recurse here unless we establish another pcall (which we * do below). * * Abort the subxact and pop it. */ pllua_subxact_abort(L); /* * The original pg error if any is now only of interest to the error * handler function and/or the caller of pcall. It's the error * handler's privilege to throw it away and replace it if they choose * to. So we deregister it from the registry at this point. */ pllua_deregister_error(L); } /* * Call the user's error function, with itself (unwrapped) as the error * handler. */ lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, 1); lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, 1); /* stack: errfunc errfunc errobj */ rc = pllua_pcall_nothrow(L, 1, 1, 1); if (rc == LUA_ERRRUN && pllua_isobject(L, -1, PLLUA_ERROR_OBJECT)) { /* * PG error from within the error handler. This means that we just * broke the subtransaction that was the parent of the one we're in * now. The new error should already be in the registry. */ if (pllua_get_active_error(L)) { Assert(lua_rawequal(L, -2, -1)); lua_pop(L, 1); } /* * the pcall wrapper itself will rethrow if need be, since we can't * rethrow from here without recursing */ return 1; } /* stack: errfunc newerrobj */ return 1; } /* * pcall(func,args...) * xpcall(func,errfunc,args...) * * returns either true, ... on success * or false, errobj on error * * We don't check "func" except for existence - if it's not a function then the * error will happen inside the catch. errfunc must be a function though. * * Subtlety: we could get here with a PG error in flight if we're called from a * user's error handler for lxpcall. If we then catch another pg error inside * the subxact, that would result in deregistering the original error. * * We could preserve the original error for rethrow, but there's a worse * problem: there is no way we can be sure that extended interaction with pg * would be even safe considering that we are supposed to be in error handling. * * So the right thing to do would seem to be to refuse to do anything in the * presence of a pending error; but because this same argument applies to a lot * of other functions too, lxpcall is disabled completely now. If it gets * re-enabled again, a lot of places will need checks added, including here. */ static int pllua_t_pcall_guts(lua_State *L, bool is_xpcall) { volatile pllua_subxact xa; MemoryContext oldcontext = CurrentMemoryContext; volatile int rc; volatile bool rethrow = false; PLLUA_CHECK_PG_STACK_DEPTH(); luaL_checkany(L, 1); if (is_xpcall) { luaL_checktype(L, 2, LUA_TFUNCTION); /* intercept the error func */ lua_pushvalue(L, 2); lua_pushboolean(L, 0); lua_pushcclosure(L, pllua_intercept_error, 2); lua_replace(L, 2); /* set up stack for return */ lua_pushboolean(L, 1); lua_pushvalue(L, 1); /* func errfunc args... true func */ lua_insert(L, 3); /* func errfunc func args... true */ lua_insert(L, 3); /* func errfunc true func args... */ } else { lua_pushboolean(L, 1); lua_insert(L, 1); /* true func args ... */ } ASSERT_LUA_CONTEXT; pllua_setcontext(PLLUA_CONTEXT_PG); PG_TRY(); { xa.resowner = CurrentResourceOwner; xa.mcontext = oldcontext; xa.onstack = false; xa.prev = subxact_stack_top; xa.own_resowner = NULL; BeginInternalSubTransaction(NULL); xa.onstack = true; xa.own_resowner = CurrentResourceOwner; subxact_stack_top = &xa; rc = pllua_pcall_nothrow(L, lua_gettop(L) - (is_xpcall ? 4 : 2), LUA_MULTRET, (is_xpcall ? 2 : 0)); if (rc == LUA_OK) { /* Commit the inner transaction, return to outer xact context */ ReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldcontext); CurrentResourceOwner = xa.resowner; Assert(subxact_stack_top == &xa); subxact_stack_top = xa.prev; } else if (xa.onstack) pllua_subxact_abort(L); else { /* * error handler must have intercepted and done the abort already. * But this implies that we need to check the registry for a * rethrow, rather than clearing it out. */ rethrow = true; } } PG_CATCH(); { pllua_setcontext(PLLUA_CONTEXT_LUA); /* absorb the error and get out of pg's error handling */ pllua_absorb_pg_error(L); if (xa.onstack) pllua_subxact_abort(L); /* * Can only get here if begin or release of the subxact threw an error. * (We assume that release of a subxact can only result in aborting it * instead.) Treat this as an error within the parent context. */ MemoryContextSwitchTo(oldcontext); lua_error(L); } PG_END_TRY(); pllua_setcontext(PLLUA_CONTEXT_LUA); if (rc == LUA_OK) { /* * Normal return. * * For pcall, everything on the stack is the return value. * * For xpcall, ignore two stack slots. */ /* * something is wrong if there's a pg error still in the registry at * this point. */ if (pllua_get_active_error(L)) { Assert(false); lua_pop(L, 1); } return lua_gettop(L) - (is_xpcall ? 2 : 0); } if (rethrow) { if (pllua_get_active_error(L)) lua_error(L); } else { pllua_deregister_error(L); } lua_pushboolean(L, 0); lua_insert(L, -2); return 2; } int pllua_t_pcall(lua_State *L) { if (pllua_getinterpreter(L)) return pllua_t_pcall_guts(L, false); else return pllua_t_lpcall(L); } int pllua_t_xpcall(lua_State *L) { if (pllua_getinterpreter(L)) return pllua_t_pcall_guts(L, true); else return pllua_t_lxpcall(L); } /* * local rc,... = subtransaction(func) * * We explicitly reserve multiple args, or table args, for future use * (e.g. for providing a table of error handlers) */ static int pllua_subtransaction(lua_State *L) { lua_settop(L, 1); if (!pllua_getinterpreter(L)) luaL_error(L, "cannot create subtransaction inside on_init string"); return pllua_t_pcall_guts(L, false); } /* * Functions to access data from the error object * * keys: * * These match up with the ones in elog.c: * "message" * "detail" * "hint" * "column" * "constraint" * "datatype" * "table" * "schema" * * "severity" -- error, warning, etc. (downcased) * "sqlstate" -- always the 5-char code * "errcode" -- the long name or the sqlstate * "context" * "pg_source_file" * "pg_source_line" * "pg_source_function" * "position" * "internal_query" * "internal_position" * */ static bool pllua_decode_sqlstate(char *buf, lua_Integer errcode) { int i; for (i = 0; i < 5; ++i) { buf[i] = PGUNSIXBIT(errcode); errcode = errcode >> 6; } buf[5] = 0; return (errcode == 0 && strspn(buf, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") == 5); } static void pllua_push_sqlstate(lua_State *L, lua_Integer errcode) { char buf[8]; pllua_decode_sqlstate(buf, errcode); lua_pushstring(L, buf); } static void pllua_push_errcode(lua_State *L, int errcode) { if (lua_geti(L, lua_upvalueindex(1), errcode) == LUA_TNIL) { char buf[8]; lua_pop(L, 1); /* * Should only get here if the errcode is somehow invalid; return it * anyway */ pllua_decode_sqlstate(buf, errcode); lua_pushstring(L, buf); } } static void pllua_push_severity(lua_State *L, int elevel, bool uppercase) { switch (elevel) { default: lua_pushnil(L); break; case DEBUG1: case DEBUG2: case DEBUG3: case DEBUG4: case DEBUG5: lua_pushstring(L, uppercase ? "DEBUG" : "debug"); break; case LOG: case COMMERROR: #if defined(LOG_SERVER_ONLY) && LOG_SERVER_ONLY != COMMERROR case LOG_SERVER_ONLY: #endif lua_pushstring(L, uppercase ? "LOG" : "log"); break; case INFO: lua_pushstring(L, uppercase ? "INFO" : "info"); break; case NOTICE: lua_pushstring(L, uppercase ? "NOTICE" : "notice"); break; case WARNING: lua_pushstring(L, uppercase ? "WARNING" : "warning"); break; case ERROR: lua_pushstring(L, uppercase ? "ERROR" : "error"); break; /* below cases can't actually be seen here */ case FATAL: lua_pushstring(L, uppercase ? "FATAL" : "fatal"); break; case PANIC: lua_pushstring(L, uppercase ? "PANIC" : "panic"); break; } } static int pllua_errobject_index(lua_State *L) { ErrorData *e = *pllua_checkrefobject(L, 1, PLLUA_ERROR_OBJECT); const char *key = luaL_checkstring(L, 2); #define PUSHINT(s_) do { lua_pushinteger(L, (s_)); } while(0) #define PUSHSTR(s_) do { if (s_) lua_pushstring(L, (s_)); else lua_pushnil(L); } while(0) switch (key[0]) { case 'c': if (strcmp(key,"category") == 0) pllua_push_errcode(L, ERRCODE_TO_CATEGORY(e->sqlerrcode)); else if (strcmp(key,"context") == 0) PUSHSTR(e->context); else if (strcmp(key,"column") == 0) PUSHSTR(e->column_name); else if (strcmp(key,"constraint") == 0) PUSHSTR(e->constraint_name); else lua_pushnil(L); break; case 'd': if (strcmp(key,"datatype") == 0) PUSHSTR(e->datatype_name); else if (strcmp(key,"detail") == 0) PUSHSTR(e->detail); else lua_pushnil(L); break; case 'e': if (strcmp(key,"errcode") == 0) pllua_push_errcode(L, e->sqlerrcode); else lua_pushnil(L); break; case 'h': if (strcmp(key,"hint") == 0) PUSHSTR(e->hint); else lua_pushnil(L); break; case 'i': if (strcmp(key,"internal_position") == 0) PUSHINT(e->internalpos); else if (strcmp(key,"internal_query") == 0) PUSHSTR(e->internalquery); else lua_pushnil(L); break; case 'm': if (strcmp(key,"message") == 0) PUSHSTR(e->message); #if PG_VERSION_NUM >= 90600 else if (strcmp(key,"message_id") == 0) PUSHSTR(e->message_id); #endif else lua_pushnil(L); break; case 'p': if (strcmp(key,"pg_source_file") == 0) PUSHSTR(e->filename); else if (strcmp(key,"pg_source_function") == 0) PUSHSTR(e->funcname); else if (strcmp(key,"pg_source_line") == 0) PUSHINT(e->lineno); else if (strcmp(key,"position") == 0) PUSHINT(e->cursorpos); else lua_pushnil(L); break; case 's': if (strcmp(key,"schema") == 0) PUSHSTR(e->schema_name); else if (strcmp(key,"severity") == 0) pllua_push_severity(L, e->elevel, false); else if (strcmp(key,"sqlstate") == 0) pllua_push_sqlstate(L, e->sqlerrcode); else lua_pushnil(L); break; case 't': if (strcmp(key,"table") == 0) PUSHSTR(e->table_name); else lua_pushnil(L); break; default: lua_pushnil(L); break; } #undef PUSHINT #undef PUSHSTR return 1; } static int pllua_errobject_tostring(lua_State *L) { ErrorData *e = *pllua_checkrefobject(L, 1, PLLUA_ERROR_OBJECT); luaL_Buffer b; char buf[8]; luaL_buffinit(L, &b); pllua_push_severity(L, e->elevel, true); luaL_addvalue(&b); luaL_addstring(&b, ": "); pllua_decode_sqlstate(buf, e->sqlerrcode); luaL_addstring(&b, buf); luaL_addstring(&b, " "); luaL_addstring(&b, e->message ? e->message : "(no message)"); luaL_pushresult(&b); return 1; } static int pllua_errobject_errcode(lua_State *L) { void **p = pllua_torefobject(L, 1, PLLUA_ERROR_OBJECT); if (p && *p) { ErrorData *e = *p; pllua_push_errcode(L, e->sqlerrcode); return 1; } else return 0; } static int pllua_errobject_category(lua_State *L) { void **p = pllua_torefobject(L, 1, PLLUA_ERROR_OBJECT); if (p && *p) { ErrorData *e = *p; pllua_push_errcode(L, ERRCODE_TO_CATEGORY(e->sqlerrcode)); return 1; } else return 0; } static int pllua_errobject_type(lua_State *L) { if (pllua_isobject(L, 1, PLLUA_ERROR_OBJECT)) lua_pushstring(L, "error"); else lua_pushnil(L); return 1; } static struct { const char *str; int val; } ecodes[] = { #include "plerrcodes.h" { NULL, 0 } }; static void pllua_get_errcodes(lua_State *L, int nidx) { int ncodes = sizeof(ecodes)/sizeof(ecodes[0]) - 1; int i; nidx = lua_absindex(L, nidx); for (i = 0; i < ncodes; ++i) { lua_pushstring(L, ecodes[i].str); lua_pushvalue(L, -1); lua_rawseti(L, nidx, ecodes[i].val); lua_pushinteger(L, ecodes[i].val); lua_rawset(L, nidx); } } /* * __index(tab,key) */ static int pllua_errcodes_index(lua_State *L) { lua_settop(L, 2); if (!lua_toboolean(L, lua_upvalueindex(1))) { pllua_get_errcodes(L, 1); lua_pushboolean(L, 1); lua_replace(L, lua_upvalueindex(1)); lua_pushvalue(L, 2); if (lua_rawget(L, 1) != LUA_TNIL) return 1; } switch (lua_type(L, 2)) { default: return 0; case LUA_TSTRING: { const char *str = lua_tostring(L, 2); if (strlen(str) == 5 && strspn(str, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") == 5) { lua_pushinteger(L, MAKE_SQLSTATE(str[0], str[1], str[2], str[3], str[4])); return 1; } return 0; } case LUA_TNUMBER: { int isint = 0; lua_Integer errcode = lua_tointegerx(L, 2, &isint); char buf[8]; if (!isint) return 0; if (!pllua_decode_sqlstate(buf, errcode)) return 0; lua_pushstring(L, buf); return 1; } } } /* * __newindex(tab,key,val) */ static int pllua_errcodes_newindex(lua_State *L) { return luaL_error(L, "errcodes table is immutable"); } static struct luaL_Reg errtab_mt[] = { { "__index", pllua_errcodes_index }, { "__newindex", pllua_errcodes_newindex }, { NULL, NULL } }; /* * module init */ static struct luaL_Reg errobj_mt[] = { { "__gc", pllua_errobject_gc }, { "__tostring", pllua_errobject_tostring }, { NULL, NULL } }; static struct luaL_Reg errfuncs[] = { { "pcall", pllua_t_pcall }, { "xpcall", pllua_t_xpcall }, { "spcall", pllua_t_pcall }, { "sxpcall", pllua_t_xpcall }, { "lpcall", pllua_t_lpcall }, #if 0 /* unsafe, see comment on pcall */ { "lxpcall", pllua_t_lxpcall }, #endif { "subtransaction", pllua_subtransaction }, { "type", pllua_errobject_type }, { NULL, NULL } }; static struct luaL_Reg errfuncs2[] = { { "errcode", pllua_errobject_errcode }, { "category", pllua_errobject_category }, { NULL, NULL } }; static struct luaL_Reg glob_errfuncs[] = { { "pcall", pllua_t_pcall }, { "xpcall", pllua_t_xpcall }, { "lpcall", pllua_t_lpcall }, #if 0 /* unsafe, see comment on pcall */ { "lxpcall", pllua_t_lxpcall }, #endif { NULL, NULL } }; static struct luaL_Reg co_errfuncs[] = { { "resume", pllua_t_coresume }, { NULL, NULL } }; int pllua_open_error(lua_State *L) { int ncodes = sizeof(ecodes)/sizeof(ecodes[0]) - 1; int i; int refs[30]; lua_settop(L, 0); /* * Create and drop a few registry reference entries so that there's a * freelist; this reduces the chance that we have to extend the registry * table (with the possibility of error) while actually doing error * handling. */ for (i = 0; i < sizeof(refs)/sizeof(int); ++i) { lua_pushboolean(L, 1); refs[i] = luaL_ref(L, LUA_REGISTRYINDEX); } while (--i >= 0) luaL_unref(L, LUA_REGISTRYINDEX, refs[i]); lua_createtable(L, 0, 2 * ncodes); /* index 1 */ lua_newtable(L); lua_pushboolean(L, 0); luaL_setfuncs(L, errtab_mt, 1); lua_pushboolean(L, 1); lua_setfield(L, -2, "__metatable"); lua_setmetatable(L, -2); lua_pushvalue(L, -1); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_ERRCODES_TABLE); pllua_newmetatable(L, PLLUA_ERROR_OBJECT, errobj_mt); lua_pushvalue(L, 1); lua_pushcclosure(L, pllua_errobject_index, 1); lua_setfield(L, -2, "__index"); lua_pop(L, 1); /* * init.c stored a lightuserdata pointer to the pre-built recursive error * object; replace it with a full userdata */ lua_pushcfunction(L, pllua_newerror); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_RECURSIVE_ERROR); lua_call(L, 1, 1); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_RECURSIVE_ERROR); lua_pushglobaltable(L); luaL_setfuncs(L, glob_errfuncs, 0); luaL_getsubtable(L, -1, "coroutine"); luaL_setfuncs(L, co_errfuncs, 0); lua_pop(L,2); lua_newtable(L); luaL_setfuncs(L, errfuncs, 0); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_ERRCODES_TABLE); luaL_setfuncs(L, errfuncs2, 1); return 1; } pllua-ng-REL_2_0_4/src/exec.c000066400000000000000000000353401347047754200160010ustar00rootroot00000000000000/* exec.c */ #include "pllua.h" #include "access/htup_details.h" #include "catalog/pg_type.h" #include "commands/trigger.h" #include "commands/event_trigger.h" #include "utils/datum.h" static void pllua_common_lua_init(lua_State *L, FunctionCallInfo fcinfo) { Assert(pllua_context == PLLUA_CONTEXT_LUA); luaL_checkstack(L, 40, NULL); } static void pllua_common_lua_exit(lua_State *L) { lua_settop(L, 0); if (pllua_track_gc_debt) { pllua_interpreter *interp = pllua_getinterpreter(L); unsigned long gc_debt = interp->gc_debt; interp->gc_debt = 0; pllua_run_extra_gc(L, gc_debt); } } /* * Given that the top "nret" items on the stack are the return value, convert * to Datum/isnull. * * Note that this is not used for triggers, which have their own function. * * The case of nret==0 from the initial call of an SRF does not reach here: * that's treated as returning 0 rows. nret==0 from yield() within an SRF do * come through here. * * If nret==0, or nret==1 with a nil value and the return type is not * composite, then we're trying to return NULL (which is not quite the same as * calling the explicit type constructor with the same values). We can't simply * return the null because the return type might be a domain type, so we check * that. * * Otherwise we simply pass the whole list of values to the type constructor * for the return type, which does all the work. We then copy the result to the * current memory context (presumed to be the caller's), in order to avoid any * uncertainty regarding garbage collection. */ static Datum pllua_return_result(lua_State *L, int nret, pllua_func_activation *act, bool *isnull) { pllua_typeinfo *ti; pllua_datum *d; int nt; bool isnil = (nret == 0) || (nret == 1 && lua_isnil(L, -1)); if (act->rettype == VOIDOID) { *isnull = true; return (Datum)0; } if (!act->retdomain && isnil) { /* * PG 9.6+ lets us just return plain NULL from SRFs for a row of null * values, but 9.5 chokes on that. So only do this for 9.5 if we're * a non-SRF or returning a scalar. For 9.5 composite SRF, drop all * the way through to the return type constructor call when nret==0. */ if (nret == 0) { #if PG_VERSION_NUM < 90600 if (act->typefuncclass==TYPEFUNC_SCALAR || !act->retset) { *isnull = true; return (Datum)0; } else isnil = false; #else *isnull = true; return (Datum)0; #endif } else if (act->typefuncclass==TYPEFUNC_SCALAR) { *isnull = true; return (Datum)0; } } lua_pushcfunction(L, pllua_typeinfo_lookup); if (!act->tupdesc) { lua_pushinteger(L, (lua_Integer)(act->rettype)); lua_call(L, 1, 1); } else { lua_pushinteger(L, (lua_Integer)(act->tupdesc->tdtypeid)); lua_pushinteger(L, (lua_Integer)(act->tupdesc->tdtypmod)); lua_call(L, 2, 1); } /* stick two copies of the typeinfo below the args */ lua_pushvalue(L, -1); lua_insert(L, -(nret+2)); lua_insert(L, -(nret+2)); nt = lua_absindex(L, -(nret+2)); ti = pllua_checktypeinfo(L, nt, true); if (ti->obsolete || ti->modified) luaL_error(L, "cannot create values for a dropped or modified type"); if (isnil) { Datum d_value = (Datum)0; bool d_isnull = true; pllua_typeinfo_check_domain(L, &d_value, &d_isnull, ti->typmod, nt, ti); *isnull = true; return (Datum)0; } /* actually call the type constructor */ lua_call(L, nret, 1); if (lua_type(L, -1) == LUA_TNIL) { *isnull = true; return (Datum)0; } else { volatile Datum d_value; d = pllua_checkdatum(L, -1, nt); *isnull = false; PLLUA_TRY(); { d_value = datumCopy(d->value, ti->typbyval, ti->typlen); } PLLUA_CATCH_RETHROW(); return d_value; } } /* * If an argument is a record type with a non-NULL value, get the actual * typeid/typmod from the record header. */ static void pllua_get_record_argtype(lua_State *L, Datum *value, Oid *argtype, int32 *argtypmod) { /* * this may detoast, so we need a catch block * * we detoast in the current memory context, assumed to be transient, * because we're going to datumCopy the result after anyway */ PLLUA_TRY(); { HeapTupleHeader arg = DatumGetHeapTupleHeader(*value); *value = PointerGetDatum(arg); *argtype = HeapTupleHeaderGetTypeId(arg); *argtypmod = HeapTupleHeaderGetTypMod(arg); } PLLUA_CATCH_RETHROW(); } /* * args are on stack at -nargs .. -1 * * Perform savedatum on the list of args to ensure they are all copied into our * memory context. */ static void pllua_save_args(lua_State *L, int nargs, pllua_typeinfo **argtypes) { ASSERT_LUA_CONTEXT; if (nargs == 0) return; PLLUA_TRY(); { int i; int arg0 = lua_absindex(L, -nargs); MemoryContext oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); for (i = 0; i < nargs; ++i) { if (lua_type(L, arg0+i) == LUA_TUSERDATA && argtypes[i]) { pllua_datum *d = lua_touserdata(L, arg0+i); pllua_savedatum(L, d, argtypes[i]); } } MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); } /* * Push all the arguments from fcinfo onto the lua stack with all necessary * conversions. */ static int pllua_push_args(lua_State *L, FunctionCallInfo fcinfo, pllua_func_activation *act) { int i; int nargs = PG_NARGS(); /* _actual_ args in call */ pllua_typeinfo *argtinfo[FUNC_MAX_ARGS]; /* * If we're variadic, pg has collected the variadic args into an array, * _unless_ we're doing variadic_any in which case the extra arguments are * still separate (but there can't be more than FUNC_MAX_ARGS of them). */ if (nargs != act->nargs && !act->func_info->variadic_any) luaL_error(L, "wrong number of args: expected %d got %d", act->nargs, nargs); luaL_checkstack(L, 40 + nargs, NULL); for (i = 0; i < nargs; ++i) { Datum value = PG_GETARG_DATUM(i); Oid argtype = InvalidOid; int32 argtypmod = -1; if (i < act->nargs && act->argtypes[i] != ANYOID) { argtype = act->argtypes[i]; } else { /* arg is ANYOID, so resolve what type the caller thinks it is. */ /* we rely on this not throwing! */ argtype = get_fn_expr_argtype(fcinfo->flinfo, i); if (!OidIsValid(argtype)) luaL_error(L, "cannot determine type of argument %d", i); } if (argtype == RECORDOID && !PG_ARGISNULL(i)) { /* * RECORD type with a non-null value - prefer to take the type * from the real record */ pllua_get_record_argtype(L, &value, &argtype, &argtypmod); } argtinfo[i] = NULL; /* * Try pushing the value as a simple lua value first, and only push a * datum object if that failed. */ if (PG_ARGISNULL(i)) { lua_pushnil(L); } else if (pllua_value_from_datum(L, value, argtype) == LUA_TNONE) { pllua_typeinfo *t; lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) argtype); lua_pushinteger(L, (lua_Integer) argtypmod); lua_call(L, 2, 1); if (lua_isnil(L, -1)) luaL_error(L, "failed to find typeinfo"); t = *pllua_checkrefobject(L, -1, PLLUA_TYPEINFO_OBJECT); /* * arg might be a domain, in which case give pllua_value_from_datum * another chance with the base type. If not, give the transform a * shot at it. If that doesn't like it, then make a datum object. */ if ((t->basetype == t->typeoid || (pllua_value_from_datum(L, value, t->basetype) == LUA_TNONE)) && (pllua_datum_transform_fromsql(L, value, -1, t) == LUA_TNONE)) { pllua_newdatum(L, -1, value); /* * needs savedatum; the datum object on the stack will ensure * this isn't GC'd even when we drop the typeinfo below */ argtinfo[i] = t; } /* drop the typeinfo off the stack */ lua_remove(L, -2); } } /* * Now, we have the arg datums at index -nargs .. -1, but we need to * run savedatum on all of them to get them copied safely. */ pllua_save_args(L, nargs, argtinfo); return nargs; } /* * Resume an SRF in value-per-call mode (second and subsequent calls come here) */ int pllua_resume_function(lua_State *L) { pllua_activation_record *act = lua_touserdata(L, 1); FunctionCallInfo fcinfo = act->fcinfo; ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; pllua_func_activation *fact = fcinfo->flinfo->fn_extra; lua_State *thr = fact->thread; int rc; int nret; Assert(thr != NULL); Assert(lua_gettop(L) == 1); fact->onstack = true; rc = lua_resume(thr, L, 0, &nret); fact->onstack = false; if (rc == LUA_OK) { /* results from function are ignored in this case */ lua_pop(thr, nret); pllua_deactivate_thread(L, fact, rsi->econtext); rsi->isDone = ExprEndResult; act->retval = (Datum)0; fcinfo->isnull = true; return 0; } else if (rc == LUA_YIELD) { luaL_checkstack(L, nret + 10, "in return from set-returning function"); lua_xmove(thr, L, nret); /* leave thread active */ rsi->isDone = ExprMultipleResult; /* drop out to normal result processing */ } else { lua_xmove(thr, L, 1); pllua_deactivate_thread(L, fact, rsi->econtext); pllua_rethrow_from_lua(L, rc); } act->retval = pllua_return_result(L, nret, fact, &fcinfo->isnull); pllua_common_lua_exit(L); return 0; } /* * Main entry point for function calls */ int pllua_call_function(lua_State *L) { pllua_activation_record *act = lua_touserdata(L, 1); FunctionCallInfo fcinfo = act->fcinfo; ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; pllua_func_activation *fact; int nstack; int nargs; int nret; int rc; pllua_common_lua_init(L, fcinfo); /* pushes the activation on the stack */ fact = pllua_validate_and_push(L, fcinfo, act->trusted); /* stack mark for result processing */ nstack = lua_gettop(L); Assert(nstack == 2); /* get the function object from the activation and push that */ pllua_activation_getfunc(L); /* func should be the only thing on the stack after the act */ Assert(lua_gettop(L) == nstack + 1); nargs = pllua_push_args(L, fcinfo, fact); if (fact->retset) { /* * This is the initial call into a SRF. Activate a new thread (which * also handles registering into the ExprContext), move the func and * parameters over to the new thread and resume it. */ lua_State *thr = pllua_activate_thread(L, nstack, rsi->econtext); lua_xmove(L, thr, nargs + 1); /* args plus function */ fact->onstack = true; rc = lua_resume(thr, L, nargs, &nret); fact->onstack = false; /* * If we got LUA_OK, the function returned without yielding. If it * returned a result, then we treat it exactly as if it had been a * non-SRF call. If it returned no result, then we treat it as 0 rows. * * If we get LUA_YIELD, we expect a result on the "thr" stack, and we * notify the caller that this is a multiple result (further rows are * handled in pllua_resume_func). * * If we got anything else, the function threw an error, which we * propagate. */ if (rc == LUA_OK) { luaL_checkstack(L, 10 + nret, NULL); lua_xmove(thr, L, nret); pllua_deactivate_thread(L, fcinfo->flinfo->fn_extra, rsi->econtext); if (nret == 0) { rsi->isDone = ExprEndResult; act->retval = (Datum)0; fcinfo->isnull = true; return 0; } /* drop out to normal result processing */ } else if (rc == LUA_YIELD) { luaL_checkstack(L, 10 + nret, NULL); lua_xmove(thr, L, nret); /* leave thread active */ rsi->isDone = ExprMultipleResult; /* drop out to normal result processing */ } else { lua_xmove(thr, L, 1); pllua_deactivate_thread(L, fcinfo->flinfo->fn_extra, rsi->econtext); pllua_rethrow_from_lua(L, rc); } } else { lua_call(L, nargs, LUA_MULTRET); luaL_checkstack(L, 10, NULL); } /* * func and args are popped by the call, so everything left is a function * result. the func_info is not on the stack any more, but we know it must * be referenced from the activation */ act->retval = pllua_return_result(L, lua_gettop(L) - nstack, fact, &fcinfo->isnull); pllua_common_lua_exit(L); return 0; } /* * Entry point for trigger invocations */ int pllua_call_trigger(lua_State *L) { pllua_activation_record *act = lua_touserdata(L, 1); FunctionCallInfo fcinfo = act->fcinfo; TriggerData *td = (TriggerData *) fcinfo->context; int nstack; int nargs; pllua_common_lua_init(L, fcinfo); /* push a trigger object on the stack */ pllua_trigger_begin(L, td); /* pushes the activation on the stack */ pllua_validate_and_push(L, fcinfo, act->trusted); /* stack mark for result processing */ nstack = lua_gettop(L); Assert(nstack == 3); /* get the function object from the activation and push that */ pllua_activation_getfunc(L); /* * Triggers have three fixed args: the trigger object, old and new tuples * plus a variable number of string args from tg_args. These don't * correspond in any way to the arguments declared in the funcinfo (which * will specify that there are no args). */ lua_pushvalue(L, 2); lua_getfield(L, -1, "old"); lua_getfield(L, -2, "new"); nargs = 3 + pllua_push_trigger_args(L, td); lua_call(L, nargs, LUA_MULTRET); luaL_checkstack(L, 10, NULL); act->retval = pllua_return_trigger_result(L, lua_gettop(L) - nstack, 2); /* mark the trigger object dead */ pllua_trigger_end(L, 2); pllua_common_lua_exit(L); return 0; } /* * Entry point for event triggers. * * TODO */ int pllua_call_event_trigger(lua_State *L) { pllua_activation_record *act = lua_touserdata(L, 1); FunctionCallInfo fcinfo = act->fcinfo; EventTriggerData *etd = (EventTriggerData *) fcinfo->context; pllua_common_lua_init(L, fcinfo); /* push a trigger object on the stack (index 2) */ pllua_evtrigger_begin(L, etd); /* pushes the activation on the stack */ pllua_validate_and_push(L, fcinfo, act->trusted); /* get the function object from the activation and push that */ pllua_activation_getfunc(L); /* * Event triggers have one fixed arg: the trigger object. There are * no additional args. The funcinfo will specify no args. * * There is also no result. */ lua_pushvalue(L, 2); lua_call(L, 1, 0); act->retval = PointerGetDatum(NULL); /* mark the trigger object dead */ pllua_evtrigger_end(L, 2); pllua_common_lua_exit(L); return 0; } /* * Entry point for inline code blocks (DO). * * Very little needs doing here. */ int pllua_call_inline(lua_State *L) { pllua_activation_record *act = lua_touserdata(L, 1); FunctionCallInfo fcinfo = act->fcinfo; pllua_common_lua_init(L, fcinfo); pllua_compile_inline(L, act->cblock->source_text, act->trusted); lua_call(L, 1, 0); pllua_common_lua_exit(L); return 0; } /* * Entry point for function validator. Guts of this are in compile.c * * No return values; is expected to throw error on failure. */ int pllua_validate(lua_State *L) { pllua_activation_record *act = lua_touserdata(L, 1); Oid func_oid = act->validate_func; pllua_common_lua_init(L, NULL); pllua_validate_function(L, func_oid, act->trusted); pllua_common_lua_exit(L); return 0; } pllua-ng-REL_2_0_4/src/exports.x000066400000000000000000000007741347047754200166110ustar00rootroot00000000000000{ global: Pg_magic_func; _PG_init; pllua_validator; pg_finfo_pllua_validator; pllua_call_handler; pg_finfo_pllua_call_handler; pllua_inline_handler; pg_finfo_pllua_inline_handler; plluau_validator; pg_finfo_plluau_validator; plluau_call_handler; pg_finfo_plluau_call_handler; plluau_inline_handler; pg_finfo_plluau_inline_handler; pllua_rethrow_from_pg; pllua_pcall_nothrow; pllua_cpcall; pllua_pcall; pllua_trampoline; local: *; }; pllua-ng-REL_2_0_4/src/globals.c000066400000000000000000000071031347047754200164740ustar00rootroot00000000000000/* globals.c */ #include "pllua.h" bool pllua_ending = false; pllua_context_type pllua_context = PLLUA_CONTEXT_PG; /* * Addresses used as lua registry or table keys * * Note the key is the address, not the string; the string is only for * diagnostic purposes. */ char PLLUA_MEMORYCONTEXT[] = "memory context"; char PLLUA_ERRORCONTEXT[] = "error memory context"; char PLLUA_INTERP[] = "interp"; char PLLUA_FUNCS[] = "funcs"; char PLLUA_ACTIVATIONS[] = "activations"; char PLLUA_TYPES[] = "types"; char PLLUA_RECORDS[] = "records"; char PLLUA_PORTALS[] = "cursors"; char PLLUA_TRUSTED[] = "trusted"; char PLLUA_USERID[] = "userid"; char PLLUA_LANG_OID[] = "language oid"; char PLLUA_FUNCTION_OBJECT[] = "function object"; char PLLUA_ERROR_OBJECT[] = "error object"; char PLLUA_IDXLIST_OBJECT[] = "idxlist object"; char PLLUA_ACTIVATION_OBJECT[] = "activation object"; char PLLUA_MCONTEXT_OBJECT[] = "memory context object"; char PLLUA_TYPEINFO_OBJECT[] = "typeinfo object"; char PLLUA_TYPEINFO_PACKAGE_OBJECT[] = "typeinfo package object"; char PLLUA_TYPEINFO_PACKAGE_ARRAY_OBJECT[] = "typeinfo package array object"; char PLLUA_TUPCONV_OBJECT[] = "tupconv object"; char PLLUA_TRIGGER_OBJECT[] = "trigger object"; char PLLUA_EVENT_TRIGGER_OBJECT[] = "event trigger object"; char PLLUA_SPI_STMT_OBJECT[] = "SPI statement object"; char PLLUA_SPI_CURSOR_OBJECT[] = "SPI cursor object"; char PLLUA_LAST_ERROR[] = "last error"; char PLLUA_RECURSIVE_ERROR[] = "recursive error"; char PLLUA_FUNCTION_MEMBER[] = "function element"; char PLLUA_MCONTEXT_MEMBER[] = "memory context element"; char PLLUA_THREAD_MEMBER[] = "thread element"; char PLLUA_TRUSTED_SANDBOX[] = "sandbox"; char PLLUA_TRUSTED_SANDBOX_LOADED[] = "sandbox loaded modules"; char PLLUA_TRUSTED_SANDBOX_ALLOW[] = "sandbox allowed modules"; char PLLUA_PGFUNC_TABLE_OBJECT[] = "pgfunc table object"; char PLLUA_TYPECONV_REGISTRY[] = "typeconv registry table"; char PLLUA_ERRCODES_TABLE[] = "errcodes table"; char PLLUA_PRINT_SEVERITY[] = "severity level for print() output"; char PLLUA_GLOBAL_META[] = "global table proxy metatable"; char PLLUA_SANDBOX_META[] = "sandbox table proxy metatable"; #if LUA_VERSION_NUM == 501 int pllua_getsubtable(lua_State *L, int i, const char *name) { int abs_i = lua_absindex(L, i); luaL_checkstack(L, 3, "not enough stack slots"); lua_pushstring(L, name); lua_gettable(L, abs_i); if (lua_istable(L, -1)) return 1; lua_pop(L, 1); lua_newtable(L); lua_pushstring(L, name); lua_pushvalue(L, -2); lua_settable(L, abs_i); return 0; } #if LUAJIT_VERSION_NUM < 20100 /* * Lua compat funcs */ void pllua_setfuncs(lua_State *L, const luaL_Reg *l, int nup) { luaL_checkstack(L, nup+1, "too many upvalues"); for (; l->name != NULL; l++) { /* fill the table with given functions */ int i; lua_pushstring(L, l->name); for (i = 0; i < nup; i++) /* copy upvalues to the top */ lua_pushvalue(L, -(nup + 1)); lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ lua_settable(L, -(nup + 3)); /* table must be below the upvalues, the name and the closure */ } lua_pop(L, nup); /* remove upvalues */ } #endif void pllua_requiref(lua_State *L, const char *modname, lua_CFunction openf, int glb) { luaL_checkstack(L, 3, "not enough stack slots available"); luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); if (lua_getfield(L, -1, modname) == LUA_TNIL) { lua_pop(L, 1); lua_pushcfunction(L, openf); lua_pushstring(L, modname); lua_call(L, 1, 1); lua_pushvalue(L, -1); lua_setfield(L, -3, modname); } if (glb) { lua_pushvalue(L, -1); lua_setglobal(L, modname); } lua_replace(L, -2); } #endif pllua-ng-REL_2_0_4/src/init.c000066400000000000000000000712001347047754200160130ustar00rootroot00000000000000/* init.c */ /* * Overall module initialization, and also per-interpreter initialization and * maintenance of the interpreter hashtable. */ #include "pllua.h" #include "access/htup_details.h" #include "catalog/pg_proc.h" #include "nodes/pg_list.h" #include "storage/ipc.h" #include "utils/inval.h" #include "utils/syscache.h" #include PGDLLEXPORT void _PG_init(void); #define PLLUA_ERROR_CONTEXT_SIZES 8*1024, 8*1024, 8*1024 static bool simulate_memory_failure = false; static HTAB *pllua_interp_hash = NULL; static List *held_states = NIL; static char *pllua_on_init = NULL; static char *pllua_on_trusted_init = NULL; static char *pllua_on_untrusted_init = NULL; static char *pllua_on_common_init = NULL; static bool pllua_do_check_for_interrupts = true; /* trusted.c also needs this */ bool pllua_do_install_globals = true; static int pllua_num_held_interpreters = 1; static char *pllua_reload_ident = NULL; static double pllua_gc_threshold = 0; static double pllua_gc_multiplier = 0; static const char *pllua_pg_version_str = NULL; static const char *pllua_pg_version_num = NULL; bool pllua_track_gc_debt = false; static lua_State *pllua_newstate_phase1(const char *ident); static void pllua_newstate_phase2(lua_State *L, bool trusted, Oid user_id, pllua_interpreter *interp_desc, pllua_activation_record *act); static void pllua_fini(int code, Datum arg); static void *pllua_alloc (void *ud, void *ptr, size_t osize, size_t nsize); /* * pllua_getstate * * Returns the interpreter structure to be used for the current call. */ pllua_interpreter * pllua_getstate(bool trusted, pllua_activation_record *act) { Oid user_id = trusted ? GetUserId() : InvalidOid; pllua_interpreter *interp_desc; bool found; Assert(pllua_context == PLLUA_CONTEXT_PG); interp_desc = hash_search(pllua_interp_hash, &user_id, HASH_ENTER, &found); if (found && interp_desc->L) { if (interp_desc->new_ident) { lua_State *L = interp_desc->L; int rc = pllua_cpcall(L, pllua_set_new_ident, interp_desc); if (rc) pllua_rethrow_from_lua(L, rc); /* unlikely, but be safe */ } return interp_desc; } if (!found) { interp_desc->L = NULL; interp_desc->trusted = trusted; interp_desc->new_ident = false; interp_desc->gc_debt = 0; interp_desc->cur_activation.fcinfo = NULL; interp_desc->cur_activation.retval = (Datum) 0; interp_desc->cur_activation.trusted = trusted; interp_desc->cur_activation.cblock = NULL; interp_desc->cur_activation.validate_func = InvalidOid; interp_desc->cur_activation.interp = NULL; interp_desc->cur_activation.err_text = NULL; } /* * this can throw a pg error, but is required to ensure the interpreter is * removed from interp_desc first if it does. */ if (held_states != NIL) { lua_State *L = linitial(held_states); held_states = list_delete_first(held_states); pllua_newstate_phase2(L, trusted, user_id, interp_desc, act); } else { lua_State *L = pllua_newstate_phase1(pllua_reload_ident); if (!L) elog(ERROR, "PL/Lua: interpreter creation failed"); pllua_newstate_phase2(L, trusted, user_id, interp_desc, act); } return interp_desc; } /* * careful, mustn't throw */ pllua_interpreter * pllua_getinterpreter(lua_State *L) { void *p; lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_INTERP); p = lua_touserdata(L, -1); lua_pop(L, 1); return p; } static void pllua_create_held_states(const char *ident) { MemoryContext oldcontext = MemoryContextSwitchTo(TopMemoryContext); int i; for (i = 0; i < pllua_num_held_interpreters; ++i) { lua_State *L = pllua_newstate_phase1(ident); if (!L) { elog(WARNING, "PL/Lua: interpreter creation failed"); break; } held_states = lcons(L, held_states); } MemoryContextSwitchTo(oldcontext); } static void pllua_destroy_held_states(void) { /* zap a "held" interp if any */ while (held_states != NIL) { lua_State *L = linitial(held_states); held_states = list_delete_first(held_states); /* * We intentionally do not worry about trying to rethrow any errors * happening here; we're trying to shut down, and ignoring an error * is probably less likely to crash us than rethrowing it. */ pllua_setcontext(PLLUA_CONTEXT_LUA); lua_close(L); /* can't throw, but has internal lua catch blocks */ pllua_setcontext(PLLUA_CONTEXT_PG); } } static void pllua_assign_on_init(const char *newval, void *extra) { /* if we're not this far into initialization, do nothing */ if (!pllua_interp_hash) return; /* changed? */ if (newval == pllua_on_init || (newval && pllua_on_init && strcmp(newval,pllua_on_init) == 0)) return; if ((pllua_reload_ident && *pllua_reload_ident) || IsUnderPostmaster) { pllua_destroy_held_states(); if (!IsUnderPostmaster) { pllua_on_init = (char *) newval; pllua_create_held_states(pllua_reload_ident); } } } static void pllua_assign_reload_ident(const char *newval, void *extra) { /* if we're not this far into initialization, do nothing */ if (!pllua_interp_hash) return; /* changed? */ if (newval == pllua_reload_ident || (newval && pllua_reload_ident && strcmp(newval,pllua_reload_ident) == 0)) return; /* make sure we don't reload if turning off reloading. */ if (newval && *newval) { pllua_destroy_held_states(); if (!IsUnderPostmaster) pllua_create_held_states(newval); else if (pllua_interp_hash) { HASH_SEQ_STATUS hash_seq; pllua_interpreter *interp; hash_seq_init(&hash_seq, pllua_interp_hash); while ((interp = hash_seq_search(&hash_seq)) != NULL) interp->new_ident = true; } } } int pllua_set_new_ident(lua_State *L) { pllua_interpreter *interp_desc = lua_touserdata(L, 1); lua_pushglobaltable(L); lua_pushliteral(L, "_PL_IDENT_NEW"); lua_pushstring(L, pllua_reload_ident); lua_rawset(L, -3); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX); lua_pushliteral(L, "_PL_IDENT_NEW"); lua_pushstring(L, pllua_reload_ident); lua_rawset(L, -3); interp_desc->new_ident = false; return 0; } static void pllua_assign_gc_multiplier(double newval, void *extra) { if (newval > 0.0) pllua_track_gc_debt = true; else pllua_track_gc_debt = false; } void pllua_run_extra_gc(lua_State *L, unsigned long gc_debt) { double val; if (pllua_gc_multiplier == 0.0) return; val = (gc_debt / 1024.0); if (val < pllua_gc_threshold) return; if (pllua_gc_multiplier > 999999.0) { pllua_debug(L, "pllua_run_extra_gc: full collect"); lua_gc(L, LUA_GCCOLLECT, 0); } else { int ival; val *= pllua_gc_multiplier; if (val >= (double) INT_MAX) ival = INT_MAX; else ival = (int) val; pllua_debug(L, "pllua_run_extra_gc: step %d", ival); lua_gc(L, LUA_GCSTEP, ival); } } static const char * pllua_get_config_value(const char *name) { #if PG_VERSION_NUM >= 90600 return MemoryContextStrdup(TopMemoryContext, GetConfigOptionByName(name, NULL, false)); #else return MemoryContextStrdup(TopMemoryContext, GetConfigOptionByName(name, NULL)); #endif } /* * _PG_init * * Called by the function manager on module load. * * This should be kept postmaster-safe in case someone wants to preload the * module in shared_preload_libraries. */ void _PG_init(void) { static bool init_done = false; HASHCTL hash_ctl; if (init_done) return; pllua_pg_version_str = pllua_get_config_value("server_version"); pllua_pg_version_num = pllua_get_config_value("server_version_num"); /* * Initialize GUCs. These are mostly SUSET or SIGHUP for security reasons! */ DefineCustomStringVariable("pllua.on_init", gettext_noop("Code to execute early when a Lua interpreter is initialized."), NULL, &pllua_on_init, NULL, PGC_SUSET, 0, NULL, pllua_assign_on_init, NULL); DefineCustomStringVariable("pllua.on_trusted_init", gettext_noop("Code to execute when a trusted Lua interpreter is initialized."), NULL, &pllua_on_trusted_init, NULL, PGC_SUSET, 0, NULL, NULL, NULL); DefineCustomStringVariable("pllua.on_untrusted_init", gettext_noop("Code to execute when an untrusted Lua interpreter is initialized."), NULL, &pllua_on_untrusted_init, NULL, PGC_SUSET, 0, NULL, NULL, NULL); DefineCustomStringVariable("pllua.on_common_init", gettext_noop("Code to execute when any Lua interpreter is initialized."), NULL, &pllua_on_common_init, NULL, PGC_SUSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("pllua.install_globals", gettext_noop("Install key modules as global tables."), NULL, &pllua_do_install_globals, true, PGC_SUSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("pllua.check_for_interrupts", gettext_noop("Check for query cancels while running the Lua interpreter."), NULL, &pllua_do_check_for_interrupts, true, PGC_SUSET, 0, NULL, NULL, NULL); DefineCustomIntVariable("pllua.prebuilt_interpreters", gettext_noop("Number of interpreters to prebuild if preloaded"), NULL, &pllua_num_held_interpreters, 1, 0, 10, PGC_SIGHUP, 0, NULL, NULL, NULL); DefineCustomStringVariable("pllua.interpreter_reload_ident", gettext_noop("Altering this id reloads any held interpreters"), NULL, &pllua_reload_ident, NULL, PGC_SIGHUP, 0, NULL, pllua_assign_reload_ident, NULL); /* * These don't need to be SUSET because we're not concerned about resource * attacks, and we expose the collectgarbage() function to the user anyway, so nothing * done with these has any real security consequence. */ DefineCustomRealVariable("pllua.extra_gc_multiplier", gettext_noop("Multiplier for additional GC calls"), NULL, &pllua_gc_multiplier, 0, 0, 1000000, PGC_USERSET, 0, NULL, pllua_assign_gc_multiplier, NULL); DefineCustomRealVariable("pllua.extra_gc_threshold", gettext_noop("Threshold for additional GC calls in kbytes"), NULL, &pllua_gc_threshold, 0, 0, LONG_MAX / 1024.0, PGC_USERSET, 0, NULL, NULL, NULL); EmitWarningsOnPlaceholders("pllua"); /* * Create hash table for interpreters. */ memset(&hash_ctl, 0, sizeof(hash_ctl)); hash_ctl.keysize = sizeof(Oid); hash_ctl.entrysize = sizeof(pllua_interpreter); pllua_interp_hash = hash_create("PLLua interpreters", 8, &hash_ctl, HASH_ELEM | HASH_BLOBS); if (!IsUnderPostmaster) pllua_create_held_states(pllua_reload_ident); init_done = true; } /* * Cleanup interpreters. * Does not fully undo the actions of _PG_init() nor make it callable again. */ static void pllua_fini(int code, Datum arg) { HASH_SEQ_STATUS hash_seq; pllua_interpreter *interp_desc; elog(DEBUG2, "pllua_fini"); if (pllua_ending) return; pllua_ending = true; /* Only perform cleanup if we're exiting cleanly */ if (code) { elog(DEBUG2, "pllua_fini: skipped"); return; } pllua_destroy_held_states(); /* Zap any fully-initialized interpreters */ hash_seq_init(&hash_seq, pllua_interp_hash); while ((interp_desc = hash_seq_search(&hash_seq)) != NULL) { if (interp_desc->L) { lua_State *L = interp_desc->L; interp_desc->L = NULL; /* * We intentionally do not worry about trying to rethrow any errors * happening here; we're trying to shut down, and ignoring an error * is probably less likely to crash us than rethrowing it. */ pllua_setcontext(PLLUA_CONTEXT_LUA); lua_close(L); /* can't throw, but has internal lua catch blocks */ pllua_setcontext(PLLUA_CONTEXT_PG); } /* * we intentionally do not worry about deleting the memory contexts * here; we're about to die anyway. */ } elog(DEBUG2, "pllua_fini: done"); } /* * Broadcast an invalidation to all interpreters (if arg==0) or the specified * interpreter. */ static void pllua_callback_broadcast(Datum arg, lua_CFunction cfunc, pllua_cache_inval *inval) { HASH_SEQ_STATUS hash_seq; pllua_interpreter *interp_desc; hash_seq_init(&hash_seq, pllua_interp_hash); while ((interp_desc = hash_seq_search(&hash_seq)) != NULL) { lua_State *L = interp_desc->L; if (L && (arg == (Datum)0 || arg == PointerGetDatum(interp_desc))) { int rc; interp_desc->inval = inval; rc = pllua_cpcall(L, /* keep line split to avoid functable hack */ cfunc, interp_desc); if (rc) pllua_poperror(L); } } } static void pllua_relcache_callback(Datum arg, Oid relid) { pllua_cache_inval inval; memset(&inval, 0, sizeof(inval)); inval.inval_rel = true; inval.inval_reloid = InvalidOid; pllua_callback_broadcast(arg, pllua_register_cfunc(L, pllua_typeinfo_invalidate), &inval); } static void pllua_syscache_typeoid_callback(Datum arg, int cacheid, uint32 hashvalue) { pllua_cache_inval inval; memset(&inval, 0, sizeof(inval)); inval.inval_type = true; inval.inval_typeoid = InvalidOid; pllua_callback_broadcast(arg, pllua_register_cfunc(L, pllua_typeinfo_invalidate), &inval); } static void pllua_syscache_cast_callback(Datum arg, int cacheid, uint32 hashvalue) { pllua_cache_inval inval; memset(&inval, 0, sizeof(inval)); inval.inval_cast = true; pllua_callback_broadcast(arg, pllua_register_cfunc(L, pllua_typeconv_invalidate), &inval); } /* * Would be nice to be able to use repalloc, but at present there is no flag to * have that return null rather than throwing. So for now, we keep the actual * lua data in the malloc heap (lua handles its own garbage collection), while * associated objects (referenced by userdata values) go in the context * associated with the interpreter. Lua's memory usage can be queried within * lua if one needs to monitor usage. */ static void * pllua_alloc(void *ud, void *ptr, size_t osize, size_t nsize) { void *nptr; (void)ud; /* not used */ if (nsize == 0) { free(ptr); /* free(NULL) is explicitly safe */ simulate_memory_failure = false; return NULL; } if (simulate_memory_failure) nptr = NULL; else nptr = realloc(ptr, nsize); if (ptr && nsize < osize) { if (!nptr) { elog(WARNING, "pllua: failed to shrink a block of size %lu to %lu", (unsigned long) osize, (unsigned long) nsize); return ptr; } } return nptr; } /* * Hook function to check for interrupts. We have lua call this for every * function return or set number of opcodes executed. */ static void pllua_hook(lua_State *L, lua_Debug *ar) { PLLUA_TRY(); { CHECK_FOR_INTERRUPTS(); } PLLUA_CATCH_RETHROW(); } /* * Simple bare-bones execution of a single string. */ static void pllua_runstring(lua_State *L, const char *chunkname, const char *str, bool use_sandbox) { if (str) { int rc = luaL_loadbufferx(L, str, strlen(str), chunkname, "t"); if (rc) lua_error(L); if (use_sandbox) { lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX); pllua_set_environment(L, -2); } lua_call(L, 0, 0); } } /* * Wrapper for stack depth checks of dangerous Lua functions. * * Original function is in upvalue 1. Unfortunately we can't tail call, but in * fact this has the slight benefit that we add another layer of increments to * nCcalls, halving the maximum possible recursion depth for the wrapped * funcs. */ static int pllua_stack_check_wrapper(lua_State *L) { int nargs = lua_gettop(L); PLLUA_CHECK_PG_STACK_DEPTH(); lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, 1); lua_call(L, nargs, LUA_MULTRET); return lua_gettop(L); } /* * List of functions we have to add extra stack checks to. */ struct wrapper_info { const char *name; const char *libname; }; static struct wrapper_info stack_wrap_list[] = { /* no functions in base lib; pcall/xpcall handled in error.c */ { NULL, "string" }, { "format", NULL }, /* invokes metamethods */ { "gsub", NULL }, /* invokes a passed-in function value */ { NULL, "table" }, { "concat", NULL }, /* invokes metamethods */ { NULL, NULL } }; static void pllua_wrap_stack_checks(lua_State *L) { struct wrapper_info *p; lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); lua_pushglobaltable(L); for (p = stack_wrap_list; p->name || p->libname; ++p) { if (p->libname) { lua_getfield(L, -2, p->libname); lua_replace(L, -2); } if (p->name) { lua_getfield(L, -1, p->name); lua_pushcclosure(L, pllua_stack_check_wrapper, 1); lua_setfield(L, -2, p->name); } } lua_pop(L, 2); } /* * Lua-environment part of interpreter setup. * * Phase 1 might be executed pre-fork; it can't know whether the interpreter * will be trusted, what the language oid is, what the user id is or anything * related to the database state. Nor does it have a pointer to the * pllua_interpreter structure. */ static int pllua_init_state_phase1(lua_State *L) { MemoryContext *mcxt = lua_touserdata(L, 1); MemoryContext *emcxt = lua_touserdata(L, 2); ErrorData *edata = lua_touserdata(L, 3); const char *ident = lua_touserdata(L, 4); lua_pushliteral(L, PLLUA_VERSION_STR); lua_setglobal(L, "_PLVERSION"); lua_pushliteral(L, PLLUA_REVISION_STR); lua_setglobal(L, "_PLREVISION"); lua_pushstring(L, pllua_pg_version_str); lua_setglobal(L, "_PG_VERSION"); lua_pushstring(L, pllua_pg_version_num); lua_pushinteger(L, lua_tointeger(L, -1)); lua_setglobal(L, "_PG_VERSION_NUM"); lua_pop(L, 1); lua_pushstring(L, ident ? ident : ""); lua_setglobal(L, "_PL_IDENT"); lua_pushinteger(L, (lua_Integer) time(NULL)); lua_setglobal(L, "_PL_LOAD_TIME"); lua_pushlightuserdata(L, mcxt); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_MEMORYCONTEXT); lua_pushlightuserdata(L, emcxt); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_ERRORCONTEXT); lua_pushlightuserdata(L, edata); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_RECURSIVE_ERROR); lua_pushlightuserdata(L, 0); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_INTERP); #if LUA_VERSION_NUM < 504 /* install our hack to push C functions without throwing error */ #define PLLUA_DECL_CFUNC(f_) lua_pushcfunction(L, f_); lua_rawsetp(L, LUA_REGISTRYINDEX, f_); #include "pllua_functable.h" #undef PLLUA_DECL_CFUNC #endif luaL_openlibs(L); /* * Apply stack-checking wrappers to standard library functions that have * excessive stack use. Must be done after openlibs but before trusted * setup. */ pllua_wrap_stack_checks(L); /* * Initialize our error handling, which replaces many base functions * (pcall, xpcall, etc.). Must be done after openlibs but before anything * might throw a pg error. */ luaL_requiref(L, "pllua.error", pllua_open_error, 0); /* * Default print() is not useful (can't rely on stdout/stderr going * anywhere safe). Replace with our version, and direct output initially to * the log. */ luaL_requiref(L, "pllua.print", pllua_open_print, 0); /* * Paths module does no db access and is safe in postmaster, and on_init * strings have a legitimate need for it. */ luaL_requiref(L, "pllua.paths", pllua_open_paths, 0); /* * Early init of the trusted sandbox, so that on_init can do trusted setup * (even though we don't know in on_init whether we're trusted or not). * * Note that this relies on the fact that the pllua.trusted module doesn't * offer any interaction with postgres, so it's postmaster-safe. */ luaL_requiref(L, "pllua.trusted", pllua_open_trusted, 0); /* * Actually run the phase1 init string. */ pllua_runstring(L, "on_init", pllua_on_init, false); /* * Do this _after_ the init string so that the init string can't get at * server.error, which would kill the postmaster messily if done when * reloading preloaded states. (But don't defer it to phase2 to allow it * to pre-generate the error tables.) */ luaL_requiref(L, "pllua.elog", pllua_open_elog, 0); lua_settop(L, 0); if (!IsUnderPostmaster) lua_gc(L, LUA_GCCOLLECT, 0); return 0; } static int pllua_init_state_phase2(lua_State *L) { bool trusted = lua_toboolean(L, 1); lua_Integer user_id = lua_tointeger(L, 2); lua_Integer lang_oid = lua_tointeger(L, 3); pllua_interpreter *interp = lua_touserdata(L, 4); lua_pushlightuserdata(L, interp); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_INTERP); lua_pushinteger(L, user_id); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_USERID); lua_pushinteger(L, lang_oid); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_LANG_OID); lua_pushboolean(L, trusted); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED); /* Treat these as actual modules */ luaL_requiref(L, "pllua.funcmgr", pllua_open_funcmgr, 0); luaL_requiref(L, "pllua.pgtype", pllua_open_pgtype, 0); if (pllua_do_install_globals) lua_setglobal(L, "pgtype"); luaL_requiref(L, "pllua.spi", pllua_open_spi, 0); if (pllua_do_install_globals) lua_setglobal(L, "spi"); luaL_requiref(L, "pllua.trigger", pllua_open_trigger, 0); luaL_requiref(L, "pllua.numeric", pllua_open_numeric, 0); luaL_requiref(L, "pllua.jsonb", pllua_open_jsonb, 0); luaL_requiref(L, "pllua.time", pllua_open_time, 0); /* * complete the initialization of the trusted-mode sandbox. * We do this in untrusted interps too, but for those, we don't * store the module table in a global. * * This must be last, after all module loads, so that it can copy * appropriate modules into the sandbox. */ luaL_requiref(L, "pllua.trusted.late", pllua_open_trusted_late, 0); if (trusted && pllua_do_install_globals) lua_setglobal(L, "trusted"); lua_settop(L, 0); /* set up the compat module for preload */ if (trusted) { lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX); lua_getfield(L, -1, "package"); lua_getfield(L, -1, "preload"); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_SANDBOX_META); } else { lua_getglobal(L, "package"); lua_getfield(L, -1, "preload"); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_GLOBAL_META); } lua_pushcclosure(L, pllua_preload_compat, 1); lua_setfield(L, -2, "pllua.compat"); lua_settop(L, 0); /* enable interrupt checks */ if (pllua_do_check_for_interrupts) lua_sethook(L, pllua_hook, LUA_MASKRET | LUA_MASKCOUNT, 100000); /* don't run user code yet */ return 0; } /* * Execute the post-fork init strings. Note that they are executed outside any * trusted sandbox, except on_common_init which executes inside. */ static int pllua_run_init_strings(lua_State *L) { bool trusted; /* sheer paranoia */ if (lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED) != LUA_TBOOLEAN) luaL_error(L, "inconsistency in interpreter setup"); trusted = lua_toboolean(L, -1); if (trusted) pllua_runstring(L, "on_trusted_init", pllua_on_trusted_init, false); else pllua_runstring(L, "on_untrusted_init", pllua_on_untrusted_init, false); pllua_runstring(L, "on_common_init", pllua_on_common_init, trusted); /* * Redirect print() output to the client now. */ lua_pushinteger(L, INFO); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_PRINT_SEVERITY); return 0; } /* * PG-environment part of interpreter setup. * * Phase 1 can run in postmaster, before we know anything about what the * interp will be used for or by whom. */ static lua_State * pllua_newstate_phase1(const char *ident) { MemoryContext mcxt = NULL; MemoryContext emcxt = NULL; MemoryContext oldcontext = CurrentMemoryContext; ErrorData *edata; lua_State *L = NULL; int rc; ASSERT_PG_CONTEXT; mcxt = AllocSetContextCreate(CurrentMemoryContext, "PL/Lua context", ALLOCSET_DEFAULT_SIZES); emcxt = AllocSetContextCreate(mcxt, "PL/Lua error context", PLLUA_ERROR_CONTEXT_SIZES ); MemoryContextSwitchTo(mcxt); edata = pllua_make_recursive_error(); #if LUA_VERSION_NUM == 501 L = luaL_newstate(); #else L = lua_newstate(pllua_alloc, NULL); #endif if (!L) elog(ERROR, "Out of memory creating Lua interpreter"); lua_atpanic(L, pllua_panic); /* can't throw */ /* * Since we just created this interpreter, we know we're not in any * protected environment yet, so Lua errors outside of pcall will * become pg errors via pllua_panic. The only possible errors here * should be out-of-memory errors. */ /* note that pllua_pushcfunction is not available yet. */ lua_pushcfunction(L, pllua_init_state_phase1); lua_pushlightuserdata(L, mcxt); lua_pushlightuserdata(L, emcxt); lua_pushlightuserdata(L, edata); lua_pushlightuserdata(L, (void *) ident); rc = pllua_pcall_nothrow(L, 4, 0, 0); /* * We don't allow phase1 init to do anything that interacts with pg in any * way other than memory allocation. So if we got an error, we can be * pretty sure it's not actually a pg error (the one exception being * out-of-memory errors). * * So, we avoid trying to rethrow any error here, because we might be in * the postmaster and that would be fatal. Leave it to the caller to decide * what to do. */ if (rc) { elog(WARNING, "PL/Lua initialization error: %s", (lua_type(L,-1) == LUA_TSTRING) ? lua_tostring(L,-1) : "(not a string)"); pllua_setcontext(PLLUA_CONTEXT_LUA); lua_close(L); /* can't throw, but has internal lua catch blocks */ pllua_setcontext(PLLUA_CONTEXT_PG); MemoryContextSwitchTo(oldcontext); MemoryContextDelete(mcxt); L = NULL; } else { MemoryContextSwitchTo(oldcontext); MemoryContextSetParent(mcxt, TopMemoryContext); } return L; } /* * PG-environment part of interpreter setup. */ static void pllua_newstate_phase2(lua_State *L, bool trusted, Oid user_id, pllua_interpreter *interp_desc, pllua_activation_record *act) { static bool first_time = true; MemoryContext oldcontext = CurrentMemoryContext; volatile MemoryContext mcxt = NULL; /* L's mcxt if known */ ASSERT_PG_CONTEXT; PG_TRY(); { Oid langoid; /* can't throw */ lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_MEMORYCONTEXT); mcxt = lua_touserdata(L, -1); lua_pop(L, 1); /* * Get our own language oid; this is somewhat unnecessarily hard. */ if (act->cblock) langoid = act->cblock->langOid; else { Oid procoid = (act->fcinfo) ? act->fcinfo->flinfo->fn_oid : act->validate_func; HeapTuple procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procoid)); if (!HeapTupleIsValid(procTup)) elog(ERROR, "cache lookup failed for function %u", procoid); langoid = ((Form_pg_proc) GETSTRUCT(procTup))->prolang; ReleaseSysCache(procTup); } /* * Since we just created this interpreter, we know we're not in any * protected environment yet, so Lua errors outside of pcall will * become pg errors via pllua_panic. In other contexts we must be more * cautious about Lua errors, because of this scenario: if a Lua * function calls into SPI which invokes another Lua function, then any * Lua error thrown in the nested invocation might longjmp back to the * outer interpreter... */ lua_pushcfunction(L, pllua_init_state_phase2); lua_pushboolean(L, trusted); lua_pushinteger(L, (lua_Integer) user_id); lua_pushinteger(L, (lua_Integer) langoid); lua_pushlightuserdata(L, interp_desc); pllua_pcall(L, 4, 0, 0); if (first_time) { on_proc_exit(pllua_fini, (Datum)0); CacheRegisterRelcacheCallback(pllua_relcache_callback, (Datum)0); CacheRegisterSyscacheCallback(TYPEOID, pllua_syscache_typeoid_callback, (Datum)0); CacheRegisterSyscacheCallback(TRFTYPELANG, pllua_syscache_typeoid_callback, (Datum)0); CacheRegisterSyscacheCallback(CASTSOURCETARGET, pllua_syscache_cast_callback, (Datum)0); first_time = false; } interp_desc->L = L; /* * force invalidation of the caches now anyway, since we might have * missed something (prior to the assignment above the invalidation * callbacks will ignore us); but for this interpreter only, no need to * involve any others. */ pllua_relcache_callback(PointerGetDatum(interp_desc), InvalidOid); pllua_syscache_typeoid_callback(PointerGetDatum(interp_desc), TYPEOID, 0); pllua_syscache_cast_callback(PointerGetDatum(interp_desc), CASTSOURCETARGET, 0); /* * Now that we have everything set up, it should finally be safe to run * some arbitrary code that might access the db. */ lua_pushcfunction(L, pllua_run_init_strings); pllua_pcall(L, 0, 0, 0); } PG_CATCH(); { ErrorData *e; Assert(pllua_context == PLLUA_CONTEXT_PG); interp_desc->L = NULL; /* * If we got a lua error (which could be a caught pg error) during the * protected part of interpreter initialization, then we need to kill * the interpreter; but that could provoke further errors, so we exit * pg's error handling first. Since we need to kill off the lua memory * contexts too, we temporarily use the caller's memory (which should * be a transient context) to store the error data. */ MemoryContextSwitchTo(oldcontext); e = CopyErrorData(); FlushErrorState(); pllua_setcontext(PLLUA_CONTEXT_LUA); pllua_ending = true; /* we're ending _this_ interpreter at least */ lua_close(L); /* can't throw, but has internal lua catch blocks */ pllua_ending = false; pllua_setcontext(PLLUA_CONTEXT_PG); MemoryContextDelete(mcxt); ReThrowError(e); } PG_END_TRY(); } pllua-ng-REL_2_0_4/src/jsonb.c000066400000000000000000000560411347047754200161710ustar00rootroot00000000000000/* jsonb.c */ #include "pllua.h" #include "catalog/pg_type.h" #include "mb/pg_wchar.h" #include "nodes/makefuncs.h" #include "utils/datum.h" #include "utils/builtins.h" #if PG_VERSION_NUM >= 100000 #include "utils/fmgrprotos.h" #endif #include "utils/jsonb.h" #if PG_VERSION_NUM < 110000 #define DatumGetJsonbP(d_) DatumGetJsonb(d_) #endif /* * called with the container value on top of the stack * * Must push keytable, prevkey, index(=1) * where prevkey is nil for objects and 0 for arrays * * For objects, keytable is a sequence of string keys (we must ensure they are * LUA_TSTRING values). For arrays, keytable is a sequence of integers in * ascending order giving the "present" keys. * * We already checked that this is a container (defined as a Lua table or a * value with a __pairs metamethod). * */ static JsonbIteratorToken pllua_jsonb_pushkeys(lua_State *L, bool empty_object, int array_thresh, int array_frac) { lua_Integer min_intkey = LUA_MAXINTEGER; lua_Integer max_intkey = 0; int numintkeys = 0; int numkeys = 0; int isint; lua_Integer intval; bool metaloop; int tabidx = lua_absindex(L, -1); int keytabidx; int numkeytabidx; bool known_object = false; bool known_array = false; switch (luaL_getmetafield(L, -1, "__jsonb_object")) { case LUA_TBOOLEAN: if (lua_toboolean(L, -1)) known_object = true; else known_array = true; /* FALLTHROUGH */ default: lua_pop(L, 1); break; case LUA_TNIL: break; } lua_newtable(L); keytabidx = lua_absindex(L, -1); lua_newtable(L); numkeytabidx = lua_absindex(L, -1); metaloop = pllua_pairs_start(L, tabidx, true); /* stack: keytable, numkeytab, [iter, state,] key */ while (metaloop ? pllua_pairs_next(L) : lua_next(L, tabidx)) { lua_pop(L, 1); /* don't need the value */ lua_pushvalue(L, -1); /* keytable numkeytab [iter state] key key */ ++numkeys; /* * this is the input table's key: here, we accept strings containing * integer values as integers */ intval = lua_tointegerx(L, -1, &isint); if (isint) { if (intval > max_intkey) max_intkey = intval; if (intval < min_intkey) min_intkey = intval; ++numintkeys; lua_pushvalue(L, -1); lua_rawseti(L, numkeytabidx, numintkeys); } switch (lua_type(L, -1)) { case LUA_TUSERDATA: case LUA_TTABLE: /* * Don't try conversions that might fail if this is an array, * since we're going to ignore non-integer keys if so */ if (!known_array) { if (luaL_getmetafield(L, -1, "__tostring") == LUA_TNIL) luaL_error(L, "cannot serialize userdata or table which lacks __tostring as a key"); lua_insert(L, -2); lua_call(L, 1, 1); if (lua_type(L, -1) != LUA_TSTRING) luaL_error(L, "tostring on table or userdata object did not return a string"); } break; case LUA_TSTRING: break; case LUA_TNUMBER: lua_tostring(L, -1); /* alters stack value as side effect */ break; default: luaL_error(L, "cannot serialize scalar value of type %s as key", luaL_typename(L, -1)); } lua_rawseti(L, keytabidx, numkeys); } /* stack: keytable numkeytab */ if (known_object || (!known_array && ((empty_object && numkeys == 0) || (numkeys != numintkeys) || (min_intkey < 1) || (numintkeys > 0 && (min_intkey > array_thresh)) || (numintkeys > 0 && (max_intkey > (array_frac * numkeys)))))) { /* it's an object. Use the string key table */ lua_pop(L, 1); lua_pushnil(L); lua_pushinteger(L, 1); return WJB_BEGIN_OBJECT; } else { /* it's an array */ lua_remove(L, -2); /* need to sort the array */ lua_getfield(L, lua_upvalueindex(1), "sort"); lua_pushvalue(L, -2); lua_call(L, 1, 0); lua_pushinteger(L, 0); lua_pushinteger(L, 1); return WJB_BEGIN_ARRAY; } } /* * Given a datum input, which might be json or jsonb or have a cast, figure out * what to put into JsonbValue. We're already in pg context in the temporary * memory context, and the value at -1 on the lua stack is the .f_to_jsonb * pgfunc object from the typeinfo. */ static void pllua_jsonb_from_datum(lua_State *L, JsonbValue *pval, pllua_datum *d, pllua_typeinfo *dt) { LOCAL_FCINFO(fcinfo, 1); FmgrInfo *fn = *(void **) lua_touserdata(L, -1); Datum res; if (!fn || !OidIsValid(fn->fn_oid)) { Oid fnoid = DatumGetObjectId( DirectFunctionCall1(regprocedurein, CStringGetDatum("pg_catalog.to_jsonb(anyelement)"))); fn = pllua_pgfunc_init(L, -1, fnoid, 1, &dt->typeoid, JSONBOID); } InitFunctionCallInfoData(*fcinfo, fn, 1, InvalidOid, NULL, NULL); LFCI_ARG_VALUE(fcinfo,0) = d->value; LFCI_ARGISNULL(fcinfo,0) = false; res = FunctionCallInvoke(fcinfo); if (fcinfo->isnull) pval->type = jbvNull; else { Jsonb *jb = DatumGetJsonbP(res); if (JB_ROOT_IS_SCALAR(jb)) { JsonbValue dummy; JsonbIterator *it = JsonbIteratorInit(&jb->root); if (JsonbIteratorNext(&it, &dummy, false) != WJB_BEGIN_ARRAY || JsonbIteratorNext(&it, pval, false) != WJB_ELEM || JsonbIteratorNext(&it, &dummy, false) != WJB_END_ARRAY || JsonbIteratorNext(&it, &dummy, false) != WJB_DONE) elog(ERROR, "unexpected return from jsonb iterator"); } else { pval->type = jbvBinary; pval->val.binary.len = VARSIZE(jb); pval->val.binary.data = &jb->root; } } } /* * Called with the scalar value on top of the stack, which it is allowed to * change if need be * * Must fill in the JsonbValue with data allocated in tmpcxt. * * Upvalue 2 is the typeinfo pgtype.numeric. * */ static void pllua_jsonb_toscalar(lua_State *L, JsonbValue *pval, MemoryContext tmpcxt) { pllua_typeinfo *dt; pllua_datum *d; switch (lua_type(L, -1)) { case LUA_TNIL: pval->type = jbvNull; return; case LUA_TBOOLEAN: pval->type = jbvBool; pval->val.boolean = lua_toboolean(L, -1); return; case LUA_TNUMBER: /* must convert to numeric */ lua_pushvalue(L, lua_upvalueindex(3)); lua_insert(L, -2); lua_call(L, 1, 1); /* FALLTHROUGH */ case LUA_TUSERDATA: if ((d = pllua_todatum(L, -1, lua_upvalueindex(3)))) { pllua_typeinfo *dt = *pllua_torefobject(L, lua_upvalueindex(3), PLLUA_TYPEINFO_OBJECT); pval->type = jbvNumeric; PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(tmpcxt); pval->val.numeric = DatumGetNumeric(datumCopy(d->value, dt->typbyval, dt->typlen)); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); return; } else if ((d = pllua_toanydatum(L, -1, &dt))) { pllua_get_user_subfield(L, -1, ".funcs", "to_jsonb"); Assert(lua_type(L,-1) == LUA_TUSERDATA); PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(tmpcxt); pllua_jsonb_from_datum(L, pval, d, dt); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); lua_pop(L, 2); return; } if (luaL_getmetafield(L, -1, "__tostring") == LUA_TNIL) luaL_error(L, "cannot serialize userdata which lacks both __pairs and __tostring"); lua_insert(L, -2); lua_call(L, 1, 1); if (lua_type(L, -1) != LUA_TSTRING) luaL_error(L, "tostring on userdata object did not return a string"); /* FALLTHROUGH */ case LUA_TSTRING: PLLUA_TRY(); { size_t len = 0; const char *ptr = lua_tolstring(L, -1, &len); MemoryContext oldcontext = MemoryContextSwitchTo(tmpcxt); char *newstr = palloc(len); memcpy(newstr, ptr, len); pg_verifymbstr(newstr, len, false); pval->type = jbvString; pval->val.string.val = newstr; pval->val.string.len = len; MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); return; default: luaL_error(L, "cannot serialize scalar value of type %s", luaL_typename(L, -1)); } } /* * Called as tosql(table, config) * * config keys: * - mapfunc * - empty_object = (boolean) * - nullvalue = (any value) * * Anything raw-equal to the nullvalue is taken as being a json null. */ static int pllua_jsonb_tosql(lua_State *L) { pllua_typeinfo *t = *pllua_torefobject(L, lua_upvalueindex(2), PLLUA_TYPEINFO_OBJECT); int nargs = lua_gettop(L); bool empty_object = false; /* by default assume {} is an array */ int nullvalue = 2; int funcidx = 0; int array_thresh = 1000; int array_frac = 1000; JsonbParseState *pstate = NULL; JsonbValue nullval; JsonbValue curval; MemoryContext tmpcxt; JsonbValue *volatile result; volatile Datum datum; pllua_datum *nd; PLLUA_CHECK_PG_STACK_DEPTH(); nullval.type = jbvNull; /* * If we only have one arg and it's not a table or userdata, decline and go * back to the normal main line. We only construct jsonb values with * top-level scalars if called with an explicit second arg. Note that we * don't reach this code if the original __call arg was a single Datum, so * we assume that a passed-in userdata is something we can index into (it * must support __pairs to work). */ if (nargs < 2 && lua_type(L, 1) != LUA_TTABLE && lua_type(L, 1) != LUA_TUSERDATA) return 0; /* if there's a second arg, it must be a config table. */ lua_settop(L, 2); if (!lua_isnil(L, 2)) { if (lua_getfield(L, 2, "map") == LUA_TFUNCTION) { funcidx = lua_absindex(L, -1); /* leave on stack */ } else lua_pop(L, 1); if (lua_getfield(L, 2, "empty_object") && lua_toboolean(L, -1)) empty_object = true; lua_pop(L, 1); lua_getfield(L, 2, "array_thresh"); if (lua_isinteger(L, -1)) array_thresh = lua_tointeger(L, -1); lua_pop(L, 1); lua_getfield(L, 2, "array_frac"); if (lua_isinteger(L, -1)) array_frac = lua_tointeger(L, -1); lua_pop(L, 1); lua_getfield(L, 2, "null"); nullvalue = lua_absindex(L, -1); } tmpcxt = pllua_newmemcontext(L, "pllua jsonb temp context", ALLOCSET_START_SMALL_SIZES); if (lua_rawequal(L, 1, nullvalue)) { lua_pushnil(L); lua_replace(L, 1); } if (funcidx) { lua_pushvalue(L, funcidx); lua_pushvalue(L, 1); lua_call(L, 1, 1); lua_replace(L, 1); } if (!pllua_is_container(L, 1)) { JsonbValue sval; lua_pushvalue(L, 1); pllua_jsonb_toscalar(L, &sval, tmpcxt); PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(tmpcxt); datum = PointerGetDatum(JsonbValueToJsonb(&sval)); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); } else { JsonbIteratorToken tok; int depth = 1; lua_pushvalue(L, 1); tok = pllua_jsonb_pushkeys(L, empty_object, array_thresh, array_frac); /* stack: ... value=newcontainer newkeylist newprevkey newindex */ luaL_checkstack(L, 20, NULL); PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(tmpcxt); pushJsonbValue(&pstate, tok, NULL); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); /* * stack at loop top: * [container keylist prevkey index]... * (prevkey is nil for objects) * * do while depth: * - if index beyond end of keylist: * - push array/object end into value * - pop stack * - else * - push container[keylist[index]] on stack * - if isobj, push keylist[key] into value * else if keylist[key] != prevkey+1 * - push as many nulls as needed into value * - increment index * - if scalar * - convert and push into value * - else * - push keylist, prevkey, index * - increment depth * - push new container start into value */ while (depth > 0) { int idx = lua_tointeger(L, -1); lua_pushinteger(L, idx+1); lua_replace(L, -2); if (lua_rawgeti(L, -3, idx) == LUA_TNIL) { lua_pop(L, 1); tok = lua_isnil(L, -2) ? WJB_END_OBJECT : WJB_END_ARRAY; PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(tmpcxt); result = pushJsonbValue(&pstate, tok, NULL); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); lua_pop(L, 4); --depth; } else { JsonbValue *pval = NULL; lua_pushvalue(L, -1); lua_gettable(L, -6); /* stack: container keylist prevkey index key value */ PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(tmpcxt); if (!lua_isnil(L, -4)) { int key = lua_tointeger(L, -2); int prevkey = lua_tointeger(L, -4); while (++prevkey != key) { pushJsonbValue(&pstate, WJB_ELEM, &nullval); } lua_pushinteger(L, key); lua_replace(L, -5); tok = WJB_ELEM; } else { size_t len = 0; const char *ptr = lua_tolstring(L, -2, &len); Assert(lua_type(L, -2) == LUA_TSTRING); curval.type = jbvString; curval.val.string.val = palloc(len); curval.val.string.len = len; memcpy(curval.val.string.val, ptr, len); pg_verifymbstr(curval.val.string.val, len, false); pushJsonbValue(&pstate, WJB_KEY, &curval); tok = WJB_VALUE; } MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); lua_remove(L, -2); /* stack: container keylist prevkey index value */ if (lua_rawequal(L, -1, nullvalue)) { lua_pushnil(L); lua_replace(L, -2); } if (funcidx) { lua_pushvalue(L, funcidx); lua_insert(L, -2); lua_call(L, 1, 1); } if (pllua_is_container(L, -1)) { tok = pllua_jsonb_pushkeys(L, empty_object, array_thresh, array_frac); /* stack: ... value=newcontainer newkeylist newprevkey newindex */ luaL_checkstack(L, 20, NULL); ++depth; } else { pval = &curval; pllua_jsonb_toscalar(L, pval, tmpcxt); } PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(tmpcxt); pushJsonbValue(&pstate, tok, pval); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); if (tok != WJB_BEGIN_OBJECT && tok != WJB_BEGIN_ARRAY) lua_pop(L, 1); } } PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(tmpcxt); datum = PointerGetDatum(JsonbValueToJsonb(result)); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); } nd = pllua_newdatum(L, lua_upvalueindex(2), datum); PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); pllua_savedatum(L, nd, t); MemoryContextReset(tmpcxt); MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); return 1; } static int pllua_jsonb_map(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(2)); pllua_typeinfo *t = *pllua_torefobject(L, lua_upvalueindex(2), PLLUA_TYPEINFO_OBJECT); pllua_typeinfo *numt = *pllua_torefobject(L, lua_upvalueindex(3), PLLUA_TYPEINFO_OBJECT); int funcidx = 0; int nullvalue; bool keep_numeric = false; bool noresult = false; Jsonb *volatile jb; JsonbIterator *it; JsonbIteratorToken r; PLLUA_CHECK_PG_STACK_DEPTH(); lua_settop(L, 2); if (t->typeoid != JSONBOID) luaL_error(L, "datum is not of type jsonb"); switch (lua_type(L, 2)) { case LUA_TTABLE: if (lua_getfield(L, 2, "map") == LUA_TFUNCTION) { funcidx = lua_absindex(L, -1); /* leave on stack */ } else lua_pop(L, 1); if (lua_getfield(L, 2, "discard") && lua_toboolean(L, -1)) noresult = true; lua_pop(L, 1); if (lua_getfield(L, 2, "pg_numeric") && lua_toboolean(L, -1)) keep_numeric = true; lua_pop(L, 1); lua_getfield(L, 2, "null"); nullvalue = lua_absindex(L, -1); break; case LUA_TFUNCTION: lua_pushnil(L); nullvalue = lua_absindex(L, -1); funcidx = 2; break; case LUA_TNIL: default: /* if it's not a table or function, then it's the nullval. */ nullvalue = 2; break; } PLLUA_TRY(); { /* * This can detoast, but only will for a value coming from a row (hence * a child datum) that has a short header or is compressed. */ jb = DatumGetJsonbP(d->value); } PLLUA_CATCH_RETHROW(); if (JB_ROOT_COUNT(jb) == 0) { if (!noresult) lua_newtable(L); } else { int patht; int patht_len = 0; int i = 0; bool is_scalar = (JB_ROOT_IS_SCALAR(jb)) ? true : false; PLLUA_TRY(); { it = JsonbIteratorInit(&jb->root); } PLLUA_CATCH_RETHROW(); lua_newtable(L); patht = lua_absindex(L, -1); lua_pushnil(L); for (;;) { JsonbValue v; volatile JsonbIteratorToken vr; luaL_checkstack(L, patht_len + 10, NULL); PLLUA_TRY(); { vr = JsonbIteratorNext(&it, &v, false); } PLLUA_CATCH_RETHROW(); r = vr; if (r == WJB_DONE) break; switch (r) { case WJB_BEGIN_ARRAY: /* iterator puts a dummy array around scalars */ if (!is_scalar) { if (!lua_isnil(L, -1)) { lua_pushvalue(L, -1); lua_rawseti(L, patht, ++patht_len); } if (!noresult) { lua_newtable(L); lua_getfield(L, lua_upvalueindex(1), "array_mt"); lua_setmetatable(L, -2); } i = 0; lua_pushinteger(L, i); } break; case WJB_BEGIN_OBJECT: if (!lua_isnil(L, -1)) { lua_pushvalue(L, -1); lua_rawseti(L, patht, ++patht_len); } if (!noresult) { lua_newtable(L); lua_getfield(L, lua_upvalueindex(1), "object_mt"); lua_setmetatable(L, -2); } break; case WJB_KEY: if (v.type != jbvString) luaL_error(L, "unexpected type for jsonb key"); /* fallthrough */ case WJB_VALUE: case WJB_ELEM: if (v.type == jbvNull) { lua_pushvalue(L, nullvalue); } else if (v.type == jbvBool) { lua_pushboolean(L, v.val.boolean); } else if (v.type == jbvNumeric) { pllua_datum_single(L, NumericGetDatum(v.val.numeric), false, lua_upvalueindex(3), numt); if (!keep_numeric) { lua_getfield(L, -1, "tonumber"); lua_insert(L, -2); lua_call(L, 1, 1); } } else if (v.type == jbvString) { lua_pushlstring(L, v.val.string.val, v.val.string.len); } if (r == WJB_KEY) { /* leave on stack */; } else if (r == WJB_VALUE) { /* we must have stack: ... [table] key value */ /* and patht contains the path to reach table */ /* we do key,val = mapfunc(key,value,path...) */ if (funcidx) { lua_pushvalue(L, funcidx); lua_insert(L, -3); for (i = 1; i <= patht_len; ++i) lua_rawgeti(L, patht, i); lua_call(L, 2 + patht_len, 2); } if (!noresult) lua_settable(L, -3); } else if (r == WJB_ELEM) { int idx = lua_tointeger(L, -2); /* stack: nil elem or ... table idx elem */ if (funcidx) { lua_pushvalue(L, funcidx); lua_insert(L, -3); for (i = 1; i <= patht_len; ++i) lua_rawgeti(L, patht, i); lua_call(L, 2 + patht_len, 2); } if (!is_scalar && !noresult) lua_seti(L, -3, idx+1); if (!is_scalar) { lua_pop(L, 1); lua_pushinteger(L, idx+1); } } break; case WJB_END_ARRAY: if (is_scalar) break; lua_pop(L, 1); /* FALLTHROUGH */ case WJB_END_OBJECT: /* we have stack: nil arrayval or ... [table] key arrayval */ { bool is_toplevel = lua_isnil(L, -2); if (!is_toplevel) --patht_len; if (!noresult) { if (funcidx) { lua_pushvalue(L, funcidx); lua_insert(L, -3); for (i = 1; i <= patht_len; ++i) lua_rawgeti(L, patht, i); lua_call(L, 2 + patht_len, 2); } if (!is_toplevel) { int isint = lua_isinteger(L, -2); /* NOT tointegerx */ if (isint) { int idx = lua_tointeger(L, -2); /* if it was an integer key, we must be doing a table */ lua_seti(L, -3, idx+1); lua_pop(L, 1); lua_pushinteger(L, idx+1); } else lua_settable(L, -3); } } } break; default: luaL_error(L, "unexpected return from jsonb iterator"); } } } PLLUA_TRY(); { if ((Pointer)jb != DatumGetPointer(d->value)) pfree(jb); } PLLUA_CATCH_RETHROW(); return noresult ? 0 : 1; } static luaL_Reg jsonb_meta[] = { { "__call", pllua_jsonb_map }, { "tosql", pllua_jsonb_tosql }, { NULL, NULL } }; /* * Test whether a table returned from jsonb_map was originally an object or * array. */ static int pllua_jsonb_table_is_object(lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); if (luaL_getmetafield(L, 1, "__jsonb_object") != LUA_TBOOLEAN) return 0; return 1; } static int pllua_jsonb_table_is_array(lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); if (luaL_getmetafield(L, 1, "__jsonb_object") != LUA_TBOOLEAN) return 0; lua_pushboolean(L, !lua_toboolean(L, -1)); return 1; } static int pllua_jsonb_table_set_table_mt(lua_State *L, const char *mtname) { luaL_checktype(L, 1, LUA_TTABLE); if (lua_getmetatable(L, 1)) { lua_getfield(L, lua_upvalueindex(1), "object_mt"); if (!lua_rawequal(L, -1, -2)) { lua_getfield(L, lua_upvalueindex(1), "array_mt"); if (!lua_rawequal(L, -1, -3)) luaL_argerror(L, 1, "cannot replace existing metatable"); } } if (mtname) lua_getfield(L, lua_upvalueindex(1), mtname); else lua_pushnil(L); lua_setmetatable(L, 1); lua_settop(L, 1); return 1; } static int pllua_jsonb_table_set_object(lua_State *L) { return pllua_jsonb_table_set_table_mt(L, "object_mt"); } static int pllua_jsonb_table_set_array(lua_State *L) { return pllua_jsonb_table_set_table_mt(L, "array_mt"); } static int pllua_jsonb_table_set_unknown(lua_State *L) { return pllua_jsonb_table_set_table_mt(L, NULL); } static luaL_Reg jsonb_funcs[] = { { "is_object", pllua_jsonb_table_is_object }, { "is_array", pllua_jsonb_table_is_array }, { "set_as_object", pllua_jsonb_table_set_object }, { "set_as_array", pllua_jsonb_table_set_array }, { "set_as_unknown", pllua_jsonb_table_set_unknown }, { NULL, NULL } }; int pllua_open_jsonb(lua_State *L) { lua_settop(L, 0); lua_newtable(L); /* module private data table at index 1 */ lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, JSONBOID); lua_call(L, 1, 1); lua_setfield(L, 1, "jsonb_type"); lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, NUMERICOID); lua_call(L, 1, 1); lua_setfield(L, 1, "numeric_type"); luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); if (lua_getfield(L, -1, "table") != LUA_TTABLE) luaL_error(L, "table package is not loaded"); if (lua_getfield(L, -1, "sort") != LUA_TFUNCTION) luaL_error(L, "table.sort function not found"); lua_remove(L, -2); lua_remove(L, -2); lua_setfield(L, 1, "sort"); lua_newtable(L); lua_pushboolean(L, 1); lua_setfield(L, -2, "__metatable"); lua_pushboolean(L, 0); lua_setfield(L, -2, "__jsonb_object"); lua_setfield(L, 1, "array_mt"); lua_newtable(L); lua_pushboolean(L, 1); lua_setfield(L, -2, "__metatable"); lua_pushboolean(L, 1); lua_setfield(L, -2, "__jsonb_object"); lua_setfield(L, 1, "object_mt"); lua_newtable(L); /* module table at index 2 */ lua_pushvalue(L, 1); lua_getfield(L, 1, "jsonb_type"); luaL_setfuncs(L, jsonb_funcs, 2); lua_getfield(L, 1, "jsonb_type"); /* jsonb typeinfo at index 3 */ lua_getuservalue(L, -1); /* datum metatable at index 4 */ lua_pushvalue(L, 1); /* first upvalue for jsonb metamethods */ lua_pushvalue(L, 3); /* second upvalue for jsonb metamethods */ lua_getfield(L, 1, "numeric_type"); /* third upvalue is numeric's typeinfo */ luaL_setfuncs(L, jsonb_meta, 3); lua_pushvalue(L, 2); return 1; } pllua-ng-REL_2_0_4/src/numeric.c000066400000000000000000000251431347047754200165170ustar00rootroot00000000000000/* numeric.c */ #include "pllua.h" #include "catalog/pg_type.h" #include "utils/numeric.h" #include "utils/builtins.h" enum num_method_id { PLLUA_NUM_NONE = 0, /* dyadic, boolean result */ PLLUA_NUM_EQ, PLLUA_NUM_LT, PLLUA_NUM_LE, /* dyadic */ PLLUA_NUM_ADD, PLLUA_NUM_SUB, PLLUA_NUM_MUL, PLLUA_NUM_DIV, PLLUA_NUM_DIVT, PLLUA_NUM_MOD, PLLUA_NUM_POW, /* optional second numeric arg */ PLLUA_NUM_LOG, PLLUA_NUM_LN, /* change _LOG to this when arg omitted */ /* optional second integer arg */ PLLUA_NUM_ROUND, PLLUA_NUM_TRUNC, /* monadic but must ignore a second arg */ PLLUA_NUM_UNM, /* monadic */ PLLUA_NUM_ABS, PLLUA_NUM_CEIL, PLLUA_NUM_EXP, PLLUA_NUM_FLOOR, PLLUA_NUM_SIGN, PLLUA_NUM_SQRT, PLLUA_NUM_NOOP, /* monadic, boolean result */ PLLUA_NUM_ISNAN }; static bool pllua_numeric_guts(lua_State *L, pllua_datum *d, pllua_typeinfo *t, Datum val1, Datum val2, int op, lua_Integer i2, bool free_val1, bool free_val2) { volatile Datum bool_res = (Datum)0; PLLUA_TRY(); { Datum res = (Datum)0; switch (op) { case PLLUA_NUM_ADD: res = DirectFunctionCall2(numeric_add, val1, val2); break; case PLLUA_NUM_SUB: res = DirectFunctionCall2(numeric_sub, val1, val2); break; case PLLUA_NUM_MUL: res = DirectFunctionCall2(numeric_mul, val1, val2); break; case PLLUA_NUM_DIV: res = DirectFunctionCall2(numeric_div, val1, val2); break; case PLLUA_NUM_DIVT: res = DirectFunctionCall2(numeric_div_trunc, val1, val2); break; case PLLUA_NUM_MOD: res = DirectFunctionCall2(numeric_mod, val1, val2); break; case PLLUA_NUM_POW: res = DirectFunctionCall2(numeric_power, val1, val2); break; case PLLUA_NUM_UNM: res = DirectFunctionCall1(numeric_uminus, val1); break; case PLLUA_NUM_EQ: res = DirectFunctionCall2(numeric_eq, val1, val2); break; case PLLUA_NUM_LT: res = DirectFunctionCall2(numeric_lt, val1, val2); break; case PLLUA_NUM_LE: res = DirectFunctionCall2(numeric_le, val1, val2); break; case PLLUA_NUM_ABS: res = DirectFunctionCall1(numeric_abs, val1); break; case PLLUA_NUM_CEIL: res = DirectFunctionCall1(numeric_ceil, val1); break; case PLLUA_NUM_EXP: res = DirectFunctionCall1(numeric_exp, val1); break; case PLLUA_NUM_FLOOR: res = DirectFunctionCall1(numeric_floor, val1); break; case PLLUA_NUM_LOG: res = DirectFunctionCall2(numeric_log, val2, val1); break; /* yes, backwards */ case PLLUA_NUM_LN: res = DirectFunctionCall1(numeric_ln, val1); break; case PLLUA_NUM_ROUND: res = DirectFunctionCall2(numeric_round, val1, Int32GetDatum(i2)); break; case PLLUA_NUM_SIGN: res = DirectFunctionCall1(numeric_sign, val1); break; case PLLUA_NUM_SQRT: res = DirectFunctionCall1(numeric_sqrt, val1); break; case PLLUA_NUM_TRUNC: res = DirectFunctionCall2(numeric_trunc, val1, Int32GetDatum(i2)); break; case PLLUA_NUM_NOOP: res = DirectFunctionCall1(numeric_uplus, val1); break; case PLLUA_NUM_ISNAN: res = numeric_is_nan(DatumGetNumeric(val1)); break; } if (d) { MemoryContext oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); d->value = res; pllua_savedatum(L, d, t); MemoryContextSwitchTo(oldcontext); } else bool_res = res; if (free_val1) pfree(DatumGetPointer(val1)); if (free_val2) pfree(DatumGetPointer(val2)); } PLLUA_CATCH_RETHROW(); return DatumGetBool(bool_res); } /* * note, this can leave extra values on stack */ static Datum pllua_numeric_getarg(lua_State *L, int nd, pllua_datum *d) { if (d) return d->value; if (lua_type(L, nd) == LUA_TNUMBER) { int isint = 0; int64 ival = lua_tointegerx(L, nd, &isint); float8 f = isint ? 0 : lua_tonumber(L, nd); volatile Datum dt; PLLUA_TRY(); { if (isint) dt = DirectFunctionCall1(int8_numeric, Int64GetDatumFast(ival)); else dt = DirectFunctionCall1(float8_numeric, Float8GetDatumFast(f)); } PLLUA_CATCH_RETHROW(); return dt; } lua_pushvalue(L, lua_upvalueindex(1)); lua_pushvalue(L, nd); lua_call(L, 1, 1); d = pllua_todatum(L, -1, lua_upvalueindex(1)); if (!d) luaL_error(L, "numeric conversion did not yield a numeric datum"); return d->value; } /* * upvalue 1 is the numeric typeinfo object, 2 the opcode */ static int pllua_numeric_handler(lua_State *L) { int op = lua_tointeger(L, lua_upvalueindex(2)); pllua_typeinfo *t = pllua_totypeinfo(L, lua_upvalueindex(1)); pllua_datum *d; pllua_datum *d1 = pllua_todatum(L, 1, lua_upvalueindex(1)); pllua_datum *d2 = pllua_todatum(L, 2, lua_upvalueindex(1)); lua_Integer i2 = 0; Datum val1; Datum val2 = (Datum)0; bool free_val1 = !d1; bool free_val2 = !d2; lua_settop(L, 2); if (op < PLLUA_NUM_LOG) { val1 = pllua_numeric_getarg(L, 1, d1); val2 = pllua_numeric_getarg(L, 2, d2); } else if (op == PLLUA_NUM_LOG) { val1 = pllua_numeric_getarg(L, 1, d1); if (lua_isnoneornil(L, 2)) { op = PLLUA_NUM_LN; free_val2 = false; } else val2 = pllua_numeric_getarg(L, 2, d2); } else if (op < PLLUA_NUM_UNM) { int isint = 0; val1 = pllua_numeric_getarg(L, 1, d1); i2 = lua_tointegerx(L, 2, &isint); luaL_argcheck(L, (lua_isnoneornil(L, 2) || isint), 2, NULL); free_val2 = false; } else if (op < PLLUA_NUM_ABS) { val1 = pllua_numeric_getarg(L, 1, d1); free_val2 = false; } else { val1 = pllua_numeric_getarg(L, 1, d1); luaL_argcheck(L, (lua_isnoneornil(L, 2)), 2, "none expected"); free_val2 = false; } if (op >= PLLUA_NUM_ADD && op < PLLUA_NUM_ISNAN) { d = pllua_newdatum(L, lua_upvalueindex(1), (Datum)0); pllua_numeric_guts(L, d, t, val1, val2, op, i2, free_val1, free_val2); } else { lua_pushboolean(L, pllua_numeric_guts(L, NULL, NULL, val1, val2, op, i2, free_val1, free_val2)); } return 1; } /* * upvalue 1 is the numeric typeinfo object, 2 is mininteger datum, 3 is * maxinteger datum */ static int pllua_numeric_tointeger(lua_State *L) { pllua_datum *d1 = pllua_todatum(L, 1, lua_upvalueindex(1)); pllua_datum *dmin = pllua_todatum(L, lua_upvalueindex(2), lua_upvalueindex(1)); pllua_datum *dmax = pllua_todatum(L, lua_upvalueindex(3), lua_upvalueindex(1)); int isint1 = 0; lua_tointegerx(L, 1, &isint1); if (isint1) { lua_pushvalue(L, 1); return 1; } if (!d1) { luaL_argcheck(L, lua_isnumber(L, 1), 1, "number"); lua_pushnil(L); return 1; } PLLUA_TRY(); { bool res_isnil = true; if (!DatumGetBool(DirectFunctionCall2(numeric_lt, d1->value, dmin->value)) && !DatumGetBool(DirectFunctionCall2(numeric_gt, d1->value, dmax->value)) && !numeric_is_nan(DatumGetNumeric(d1->value))) { int64 val = DatumGetInt64(DirectFunctionCall1(numeric_int8, d1->value)); Datum check = DirectFunctionCall1(int8_numeric, Int64GetDatumFast(val)); if (DatumGetBool(DirectFunctionCall2(numeric_eq, d1->value, check))) { lua_pushinteger(L, (lua_Integer) val); /* already range checked */ res_isnil = false; } pfree(DatumGetPointer(check)); } if (res_isnil) lua_pushnil(L); } PLLUA_CATCH_RETHROW(); return 1; } /* * upvalue 1 is the numeric typeinfo object, 2 is mininteger datum, 3 is * maxinteger datum */ static int pllua_numeric_tonumber(lua_State *L) { pllua_datum *d1 = pllua_todatum(L, 1, lua_upvalueindex(1)); pllua_datum *dmin = pllua_todatum(L, lua_upvalueindex(2), lua_upvalueindex(1)); pllua_datum *dmax = pllua_todatum(L, lua_upvalueindex(3), lua_upvalueindex(1)); if (!d1) { luaL_argcheck(L, lua_isnumber(L, 1), 1, "number"); lua_pushvalue(L, 1); return 1; } PLLUA_TRY(); { bool done = false; if (!DatumGetBool(DirectFunctionCall2(numeric_lt, d1->value, dmin->value)) && !DatumGetBool(DirectFunctionCall2(numeric_gt, d1->value, dmax->value)) && !numeric_is_nan(DatumGetNumeric(d1->value))) { int64 val = DatumGetInt64(DirectFunctionCall1(numeric_int8, d1->value)); Datum check = DirectFunctionCall1(int8_numeric, Int64GetDatumFast(val)); if (DatumGetBool(DirectFunctionCall2(numeric_eq, d1->value, check))) { lua_pushinteger(L, (lua_Integer) val); /* already range checked */ done = true; } pfree(DatumGetPointer(check)); } if (!done) { float8 val = DatumGetFloat8(DirectFunctionCall1(numeric_float8, d1->value)); lua_pushnumber(L, (lua_Number) val); } } PLLUA_CATCH_RETHROW(); return 1; } static struct { const char *name; enum num_method_id id; } numeric_meta[] = { { "__add", PLLUA_NUM_ADD }, { "__sub", PLLUA_NUM_SUB }, { "__mul", PLLUA_NUM_MUL }, { "__div", PLLUA_NUM_DIV }, { "__idiv", PLLUA_NUM_DIVT }, { "__mod", PLLUA_NUM_MOD }, { "__pow", PLLUA_NUM_POW }, { "__unm", PLLUA_NUM_UNM }, { "__eq", PLLUA_NUM_EQ }, { "__lt", PLLUA_NUM_LT }, { "__le", PLLUA_NUM_LE }, { NULL, PLLUA_NUM_NONE } }; static struct { const char *name; enum num_method_id id; } numeric_methods[] = { { "abs", PLLUA_NUM_ABS }, { "ceil", PLLUA_NUM_CEIL }, { "equal", PLLUA_NUM_EQ }, { "exp", PLLUA_NUM_EXP }, { "floor", PLLUA_NUM_FLOOR }, { "isnan", PLLUA_NUM_ISNAN }, { "log", PLLUA_NUM_LOG }, { "new", PLLUA_NUM_NOOP }, { "round", PLLUA_NUM_ROUND }, { "sign", PLLUA_NUM_SIGN }, { "sqrt", PLLUA_NUM_SQRT }, { "trunc", PLLUA_NUM_TRUNC }, { "to", PLLUA_NUM_NOOP }, { NULL, PLLUA_NUM_NONE } }; static luaL_Reg numeric_plain_methods[] = { { "tointeger", pllua_numeric_tointeger }, { "tonumber", pllua_numeric_tonumber }, { NULL, NULL } }; int pllua_open_numeric(lua_State *L) { int i; StaticAssertStmt(LUA_MAXINTEGER <= PG_INT64_MAX, "lua_Integer type is too big"); StaticAssertStmt(sizeof(lua_Number) <= sizeof(float8), "lua_Number type is too big"); lua_settop(L, 0); lua_newtable(L); /* module table at index 1 */ lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, NUMERICOID); lua_call(L, 1, 1); /* typeinfo at index 2 */ lua_getuservalue(L, 2); /* datum metatable at index 3 */ for (i = 0; numeric_methods[i].name; ++i) { lua_pushvalue(L, 2); lua_pushinteger(L, numeric_methods[i].id); lua_pushcclosure(L, pllua_numeric_handler, 2); lua_setfield(L, 1, numeric_methods[i].name); } for (i = 0; numeric_meta[i].name; ++i) { lua_pushvalue(L, 2); lua_pushinteger(L, numeric_meta[i].id); lua_pushcclosure(L, pllua_numeric_handler, 2); lua_setfield(L, 3, numeric_meta[i].name); } /* override normal datum __index entry with our method table */ lua_pushvalue(L, 1); lua_setfield(L, 3, "__index"); lua_pushvalue(L, 1); lua_pushvalue(L, 2); lua_getfield(L, 1, "to"); lua_pushinteger(L, LUA_MININTEGER); lua_call(L, 1, 1); lua_pushvalue(L, -1); lua_setfield(L, 1, "mininteger"); lua_getfield(L, 1, "to"); lua_pushinteger(L, LUA_MAXINTEGER); lua_call(L, 1, 1); lua_pushvalue(L, -1); lua_setfield(L, 1, "maxinteger"); luaL_setfuncs(L, numeric_plain_methods, 3); lua_pop(L, 1); lua_pushvalue(L, 1); return 1; } pllua-ng-REL_2_0_4/src/objects.c000066400000000000000000000465521347047754200165150ustar00rootroot00000000000000/* objects.c */ /* * Misc. lua object handling stuff. */ #include "pllua.h" #include "nodes/makefuncs.h" /* * True if object at index "nd" is of "objtype" * * Unlike luaL_*, this uses lightuserdata keys rather than strings. */ bool pllua_isobject(lua_State *L, int nd, char *objtype) { if (lua_type(L, nd) != LUA_TUSERDATA) return false; if (!lua_getmetatable(L, nd)) return false; lua_rawgetp(L, LUA_REGISTRYINDEX, objtype); if (!lua_rawequal(L, -1, -2)) { lua_pop(L, 2); return false; } lua_pop(L, 2); return true; } void pllua_newmetatable(lua_State *L, char *objtype, luaL_Reg *mt) { lua_newtable(L); luaL_setfuncs(L, mt, 0); lua_pushstring(L, objtype); lua_setfield(L, -2, "__name"); lua_pushboolean(L, 1); lua_setfield(L, -2, "__metatable"); lua_pushvalue(L, -1); lua_rawsetp(L, LUA_REGISTRYINDEX, objtype); } /* * Create a table+metatable with the specified weak mode and (optional) name. * * Leaves both table and metatable on the stack, with the metatable on top. */ void pllua_new_weak_table(lua_State *L, const char *mode, const char *name) { lua_newtable(L); lua_newtable(L); lua_pushstring(L, mode); lua_setfield(L, -2, "__mode"); if (name) { lua_pushstring(L, name); lua_setfield(L, -2, "__name"); } lua_pushvalue(L, -1); lua_setmetatable(L, -3); } /* * Reference objects hold only a pointer in the Lua object which points to the * real data. The __gc method for each object type is responsible for freeing * the real data. */ /* * pllua_get_memory_cxt * * Get the memory context associated with the interpreter. */ MemoryContext pllua_get_memory_cxt(lua_State *L) { void *p; lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_MEMORYCONTEXT); p = lua_touserdata(L, -1); lua_pop(L, 1); return (MemoryContext) p; } /* * Create a refobj of the specified type and value (which may be NULL) * * Optionally create a table and put it in the uservalue slot. * * Leaves the object on the stack, and returns the pointer to the pointer slot. */ void **pllua_newrefobject(lua_State *L, char *objtype, void *value, bool uservalue) { void **p = lua_newuserdata(L, sizeof(void*)); *p = value; if (objtype) { int t PG_USED_FOR_ASSERTS_ONLY; t = lua_rawgetp(L, LUA_REGISTRYINDEX, objtype); Assert(t == LUA_TTABLE); lua_setmetatable(L, -2); } if (uservalue || MANDATORY_USERVALUE) { lua_newtable(L); lua_setuservalue(L, -2); } return p; } void **pllua_torefobject(lua_State *L, int nd, char *objtype) { void *p = lua_touserdata(L, nd); if (p != NULL) { if (lua_getmetatable(L, nd)) { lua_rawgetp(L, LUA_REGISTRYINDEX, objtype); if (!lua_rawequal(L, -1, -2)) p = NULL; lua_pop(L, 2); return p; } } return NULL; } /* * This is a non-ref object, the data is stored directly in lua. * * Optionally, we create a table and put it in the uservalue slot. */ void *pllua_newobject(lua_State *L, char *objtype, size_t sz, bool uservalue) { void *p = lua_newuserdata(L, sz); memset(p, 0, sz); if (objtype) { int t PG_USED_FOR_ASSERTS_ONLY; t = lua_rawgetp(L, LUA_REGISTRYINDEX, objtype); Assert(t == LUA_TTABLE); lua_setmetatable(L, -2); } if (uservalue || MANDATORY_USERVALUE) { lua_newtable(L); lua_setuservalue(L, -2); } return p; } void *pllua_toobject(lua_State *L, int nd, char *objtype) { void *p = lua_touserdata(L, nd); if (p != NULL) { if (lua_getmetatable(L, nd)) { lua_rawgetp(L, LUA_REGISTRYINDEX, objtype); if (!lua_rawequal(L, -1, -2)) p = NULL; lua_pop(L, 2); return p; } } return NULL; } /* * Create a memory context with the lifetime of a Lua object, useful for * temporary contexts that might otherwise get leaked by errors. * * Normal use would be to reset the context on the normal exit path and * leave the rest for GC to clean up. * * "name" must be a compile-time constant */ MemoryContext pllua_newmemcontext(lua_State *L, const char *name, Size minsz, Size initsz, Size maxsz) { void **p = pllua_newrefobject(L, PLLUA_MCONTEXT_OBJECT, NULL, false); MemoryContext parent = pllua_get_memory_cxt(L); volatile MemoryContext mcxt; PLLUA_TRY(); { mcxt = AllocSetContextCreateInternal(parent, name, minsz, initsz, maxsz); *p = mcxt; } PLLUA_CATCH_RETHROW(); return mcxt; } static int pllua_mcxtobject_gc(lua_State *L) { void **p = pllua_torefobject(L, 1, PLLUA_MCONTEXT_OBJECT); MemoryContext mcxt = p ? *p : NULL; if (!p) return 0; ASSERT_LUA_CONTEXT; *p = NULL; if (mcxt) { PLLUA_TRY(); { MemoryContextDelete(mcxt); } PLLUA_CATCH_RETHROW(); } return 0; } void pllua_type_error(lua_State *L, char *expected) { luaL_error(L, "wrong parameter type (expected %s)", expected); } void **pllua_checkrefobject(lua_State *L, int nd, char *objtype) { void **p = pllua_torefobject(L, nd, objtype); if (!p || !*p) luaL_argerror(L, nd, objtype); return p; } void *pllua_checkobject(lua_State *L, int nd, char *objtype) { void *p = pllua_toobject(L, nd, objtype); if (!p) pllua_type_error(L, objtype); return p; } /* * make field "field" of the uservalue table of the object at nd * contain the value at the top of the stack (which is popped). * * Install a table in the uservalue if it wasn't already one. */ void pllua_set_user_field(lua_State *L, int nd, const char *field) { nd = lua_absindex(L, nd); if (lua_getuservalue(L, nd) != LUA_TTABLE) { lua_pop(L, 1); lua_newtable(L); lua_pushvalue(L, -1); lua_setuservalue(L, nd); } lua_insert(L, -2); lua_setfield(L, -2, field); lua_pop(L, 1); } int pllua_get_user_field(lua_State *L, int nd, const char *field) { if (lua_getuservalue(L, nd) != LUA_TTABLE) { lua_pop(L, 1); lua_pushnil(L); return LUA_TNIL; } else { int typ = lua_getfield(L, -1, field); lua_remove(L, -2); return typ; } } int pllua_get_user_subfield(lua_State *L, int nd, const char *field, const char *subfield) { if (lua_getuservalue(L, nd) != LUA_TTABLE) { lua_pop(L, 1); lua_pushnil(L); return LUA_TNIL; } else if (lua_getfield(L, -1, field) != LUA_TTABLE) { lua_pop(L, 2); lua_pushnil(L); return LUA_TNIL; } else { int typ = lua_getfield(L, -1, subfield); lua_remove(L, -2); lua_remove(L, -2); return typ; } } /* Convenience functions for doing pairs() loops */ bool pllua_is_container(lua_State *L, int nd) { if (lua_type(L, nd) == LUA_TTABLE) return true; if (luaL_getmetafield(L, nd, "__pairs") != LUA_TNIL) { lua_pop(L, 1); return true; } return false; } bool pllua_pairs_start(lua_State *L, int nd, bool noerror) { nd = lua_absindex(L, nd); if (luaL_getmetafield(L, nd, "__pairs") == LUA_TNIL) { if (!noerror) luaL_checktype(L, nd, LUA_TTABLE); lua_pushnil(L); /* initial key for lua_next */ return false; } else { lua_pushvalue(L, nd); lua_call(L, 1, 3); return true; } } /* * At call, the stack is: * * iterfunc \ state \ key * * On true return, we leave * * iterfunc \ state \ key \ value * * On false return, we pop all three */ int pllua_pairs_next(lua_State *L) { lua_pushvalue(L, -3); lua_insert(L, -2); /* iter state iter key */ lua_pushvalue(L, -3); lua_insert(L, -2); /* iter state iter state key */ lua_call(L, 2, 2); /* iter state key val */ if (lua_isnil(L, -2)) { lua_pop(L, 4); return 0; } return 1; } /* * Activation objects represent a function call site (flinfo). * * We hang them off flinfo->fn_extra, and ensure that they aren't prematurely * freed by keeping a registry of them (in a table in the lua registry). * * But to ensure they actually do get freed, we use the memory context shutdown * callback of the memory context that the flinfo itself is in. * * Activations can also reference the thread of a set-returning function. In * this case, we need to reset things in the event of a rescan of the context. */ int pllua_freeactivation(lua_State *L) { pllua_func_activation *act = lua_touserdata(L, 1); act->dead = true; /* * These are allocated in the memory context that's going away, so forget * they exist */ act->argtypes = NULL; act->tupdesc = NULL; lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_ACTIVATIONS); lua_pushnil(L); lua_rawsetp(L, -2, act); lua_pop(L, 1); return 0; } static void pllua_freeactivation_cb(void *arg) { pllua_func_activation *act = arg; lua_State *L = act->L; /* * we got here from pg, in a memory context reset. Since we shouldn't ever * have allowed ourselves far enough into pg for that to happen while in * lua context, assert that fact. */ Assert(pllua_context == PLLUA_CONTEXT_PG); /* * we'd better ignore any (unlikely) lua error here, since that's safer * than raising an error into pg */ if (pllua_cpcall(L, pllua_freeactivation, act)) pllua_poperror(L); } int pllua_resetactivation(lua_State *L) { int opos = lua_gettop(L) - 1; pllua_func_activation *act = lua_touserdata(L, -1); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_ACTIVATIONS); if (lua_rawgetp(L, -1, act) == LUA_TNIL) { /* unsafe to elog here *elog(WARNING, "failed to find an activation: %p", act); */ return 0; } pllua_checkobject(L, -1, PLLUA_ACTIVATION_OBJECT); act->thread = NULL; lua_getuservalue(L, -1); lua_pushnil(L); lua_rawsetp(L, -2, PLLUA_THREAD_MEMBER); lua_settop(L, opos); return 0; } static void pllua_resetactivation_cb(Datum arg) { pllua_func_activation *act = (void*) DatumGetPointer(arg); lua_State *L = act->L; int rc; /* * we got here from pg, in an expr context reset. Since we shouldn't ever * have allowed ourselves far enough into pg for that to happen while in * lua context, assert that fact. */ Assert(pllua_context == PLLUA_CONTEXT_PG); rc = pllua_cpcall(L, pllua_resetactivation, act); if (rc) pllua_rethrow_from_lua(L, rc); } /* * Create a new activation object with the specified memory context. The * function is not filled in; a later setactivation must do that. The * activation is interned as valid. */ int pllua_newactivation(lua_State *L) { MemoryContext mcxt = lua_touserdata(L, 1); pllua_func_activation *act = pllua_newobject(L, PLLUA_ACTIVATION_OBJECT, sizeof(pllua_func_activation), true); act->func_info = NULL; act->thread = NULL; act->resolved = false; act->rettype = InvalidOid; act->tupdesc = NULL; act->interp = pllua_getinterpreter(L); act->L = L; act->cb.func = pllua_freeactivation_cb; act->cb.arg = act; act->dead = false; lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_ACTIVATIONS); lua_pushvalue(L, -2); lua_rawsetp(L, -2, act); lua_pop(L, 1); /* this can't throw a pg error, thankfully */ MemoryContextRegisterResetCallback(mcxt, &act->cb); return 1; } /* * Update the activation to point to a new function (e.g. after a recompile). * Returns nothing. */ int pllua_setactivation(lua_State *L) { pllua_func_activation *act = lua_touserdata(L, 1); void **p = pllua_checkrefobject(L, 2, PLLUA_FUNCTION_OBJECT); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_ACTIVATIONS); if (lua_rawgetp(L, -1, act) == LUA_TNIL) { pllua_warning(L, "failed to find an activation: %p", act); return 0; } pllua_checkobject(L, -1, PLLUA_ACTIVATION_OBJECT); Assert(act->thread == NULL); act->func_info = (*p); act->resolved = false; /* need to re-resolve types */ lua_getuservalue(L, -1); lua_pushvalue(L, 2); lua_rawsetp(L, -2, PLLUA_FUNCTION_MEMBER); return 0; } /* * Get an activation object given its address. */ void pllua_getactivation(lua_State *L, pllua_func_activation *act) { ASSERT_PG_CONTEXT; lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_ACTIVATIONS); if (lua_rawgetp(L, -1, act) == LUA_TNIL) elog(ERROR, "failed to find an activation: %p", act); lua_remove(L, -2); } int pllua_activation_getfunc(lua_State *L) { lua_getuservalue(L, -1); lua_rawgetp(L, -1, PLLUA_FUNCTION_MEMBER); lua_getuservalue(L, -1); lua_rawgetp(L, -1, PLLUA_FUNCTION_MEMBER); lua_insert(L, -4); lua_pop(L, 3); return 1; } FmgrInfo *pllua_get_cur_flinfo(lua_State *L) { pllua_activation_record *pact = &(pllua_getinterpreter(L)->cur_activation); return pact->fcinfo ? pact->fcinfo->flinfo : NULL; } int pllua_get_cur_act(lua_State *L) { FmgrInfo *flinfo = pllua_get_cur_flinfo(L); pllua_func_activation *act; act = (flinfo) ? flinfo->fn_extra : NULL; if (!act) return 0; lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_ACTIVATIONS); if (lua_rawgetp(L, -1, act) == LUA_TNIL) luaL_error(L, "activation not found: %p", act); lua_remove(L, -2); return 1; } /* * This one doesn't throw unless the activation is actually invalid; if we're * in DO-block context, we're not readonly. */ bool pllua_get_cur_act_readonly(lua_State *L) { FmgrInfo *flinfo = pllua_get_cur_flinfo(L); pllua_func_activation *act; act = (flinfo) ? flinfo->fn_extra : NULL; if (!act) return false; lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_ACTIVATIONS); if (lua_rawgetp(L, -1, act) == LUA_TNIL) luaL_error(L, "activation not found: %p", act); lua_pop(L, 2); return act->readonly; } static int pllua_dump_activation(lua_State *L) { pllua_func_activation *act = pllua_checkobject(L, 1, PLLUA_ACTIVATION_OBJECT); luaL_Buffer b; char *buf; int i; luaL_buffinit(L, &b); buf = luaL_prepbuffer(&b); snprintf(buf, LUAL_BUFFERSIZE, "%s" "func_info: %p thread: %p " "resolved: %d polymorphic: %d variadic_call: %d retset: %d " "rettype: %u tupdesc: %p typefuncclass: %d " "nargs: %d argtypes:", (act->dead) ? "DEAD " : "", act->func_info, act->thread, (int) act->resolved, (int) act->polymorphic, (int) act->variadic_call, (int) act->retset, (unsigned) act->rettype, act->tupdesc, (int) act->typefuncclass, act->nargs); luaL_addsize(&b, strlen(buf)); if (!act->dead && act->argtypes) { for (i = 0; i < act->nargs; ++i) { buf = luaL_prepbuffer(&b); snprintf(buf, LUAL_BUFFERSIZE, " %u", (unsigned) act->argtypes[i]); luaL_addsize(&b, strlen(buf)); } } else if (!act->dead) luaL_addstring(&b, " (null)"); luaL_pushresult(&b); return 1; } /* * nd is the stack index of an activation object, which should not already have * a thread, which needs to be registered in the econtext and have a thread * allocated to it. */ lua_State *pllua_activate_thread(lua_State *L, int nd, ExprContext *econtext) { pllua_func_activation *act = pllua_toobject(L, nd, PLLUA_ACTIVATION_OBJECT); lua_State *newthread = NULL; ASSERT_LUA_CONTEXT; Assert(act->thread == NULL); PLLUA_TRY(); { RegisterExprContextCallback(econtext, pllua_resetactivation_cb, PointerGetDatum(act)); } PLLUA_CATCH_RETHROW(); lua_getuservalue(L, nd); newthread = lua_newthread(L); act->thread = newthread; lua_rawsetp(L, -2, PLLUA_THREAD_MEMBER); lua_pop(L, 1); return newthread; } /* * act is an activation object which needs to be deregistered * in the econtext and have its thread released */ void pllua_deactivate_thread(lua_State *L, pllua_func_activation *act, ExprContext *econtext) { Assert(act->thread != NULL); PLLUA_TRY(); { UnregisterExprContextCallback(econtext, pllua_resetactivation_cb, PointerGetDatum(act)); } PLLUA_CATCH_RETHROW(); lua_pushlightuserdata(L, act); pllua_resetactivation(L); } /* * Function objects are refobjects containing cached function info. * * The uservalue slot of the object contains the actual Lua function. */ static void pllua_destroy_funcinfo(lua_State *L, pllua_function_info *obj) { PLLUA_TRY(); { /* * funcinfo is allocated in its own memory context (since we expect it * to have stuff dangling off), so free it by destroying that. */ MemoryContextDelete(obj->mcxt); } PLLUA_CATCH_RETHROW(); } static int pllua_funcobject_gc(lua_State *L) { void **p = pllua_torefobject(L, 1, PLLUA_FUNCTION_OBJECT); void *obj = p ? *p : NULL; if (!p) return 0; ASSERT_LUA_CONTEXT; *p = NULL; if (obj) pllua_destroy_funcinfo(L, obj); return 0; } /* * PGFunc objects are refobjects pointing at the FmgrInfo for some pg function * we might call. * * It would be nice to be able to initialize more stuff here. But the problem * is that most fmgr initialization needs to be done from PG context, and so * it's better to share a catch block between that and the function call proper * than have a new catch block just for this. * * By storing the memory context in a separate object in the uservalue, we * avoid needing a metatable for this; some callers might like to supply their * own (e.g. with a __call method). But that does mean that pgfunc objects are * not in fact type-checkable as refobjects, and the caller has to do their own * type checks. */ void pllua_pgfunc_new(lua_State *L) { pllua_newrefobject(L, NULL, NULL, true); lua_getuservalue(L, -1); pllua_newmemcontext(L, "pllua pgfunc context", ALLOCSET_SMALL_SIZES); lua_rawsetp(L, -2, PLLUA_MCONTEXT_MEMBER); lua_pop(L, 1); } /* * __index(tab,key) */ static int pllua_pgfunc_auto_new(lua_State *L) { lua_settop(L,2); pllua_pgfunc_new(L); lua_pushvalue(L, -2); lua_pushvalue(L, -2); lua_rawset(L, 1); return 1; } void pllua_pgfunc_table_new(lua_State *L) { lua_newtable(L); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_PGFUNC_TABLE_OBJECT); lua_setmetatable(L, -2); } /* * Actually allocate (if needed) and fill in a pgfunc. This has to be called * from PG context. */ FmgrInfo * pllua_pgfunc_init(lua_State *L, int nd, Oid fnoid, int nargs, Oid *argtypes, Oid rettype) { MemoryContext mcxt; MemoryContext oldcontext; Node *func = NULL; FmgrInfo *fn = NULL; void **p = lua_touserdata(L, nd); int i; ASSERT_PG_CONTEXT; if (!p) elog(ERROR, "pllua_pgfunc_init: param is not a userdata"); if (lua_getuservalue(L, nd) != LUA_TTABLE) elog(ERROR, "pllua_pgfunc_init: bad uservalue"); if (lua_rawgetp(L, -1, PLLUA_MCONTEXT_MEMBER) != LUA_TUSERDATA || !(mcxt = *(void **) lua_touserdata(L, -1))) elog(ERROR, "pllua_pgfunc_init: missing mcontext"); lua_pop(L, 2); oldcontext = MemoryContextSwitchTo(mcxt); if (!*p) fn = *p = palloc0(sizeof(FmgrInfo)); else fn = *p; if (nargs >= 0) { List *args = NIL; for (i = 0; i < nargs; ++i) { Param *argp = makeNode(Param); /* make an argument of a dummy Param node of the input type */ argp->paramkind = PARAM_EXEC; argp->paramid = -1; argp->paramtype = argtypes[i]; argp->paramtypmod = -1; argp->paramcollid = InvalidOid; argp->location = -1; args = lappend(args, argp); } func = (Node *) makeFuncExpr(fnoid, rettype, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); } fmgr_info_cxt(fnoid, fn, mcxt); fmgr_info_set_expr(func, fn); MemoryContextSwitchTo(oldcontext); return fn; } /* * metatables for objects and global functions */ static struct luaL_Reg funcobj_mt[] = { { "__gc", pllua_funcobject_gc }, { NULL, NULL } }; static struct luaL_Reg mcxtobj_mt[] = { { "__gc", pllua_mcxtobject_gc }, { NULL, NULL } }; static struct luaL_Reg actobj_mt[] = { { "__tostring", pllua_dump_activation }, { NULL, NULL } }; static struct luaL_Reg pgfunctab_mt[] = { { "__index", pllua_pgfunc_auto_new }, { NULL, NULL } }; int pllua_open_funcmgr(lua_State *L) { lua_newtable(L); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_FUNCS); lua_newtable(L); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_ACTIVATIONS); pllua_newmetatable(L, PLLUA_FUNCTION_OBJECT, funcobj_mt); pllua_newmetatable(L, PLLUA_ACTIVATION_OBJECT, actobj_mt); pllua_newmetatable(L, PLLUA_MCONTEXT_OBJECT, mcxtobj_mt); pllua_newmetatable(L, PLLUA_PGFUNC_TABLE_OBJECT, pgfunctab_mt); lua_pop(L, 4); /* proxy metatable for global table */ lua_newtable(L); lua_pushglobaltable(L); lua_setfield(L, -2, "__index"); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_GLOBAL_META); lua_pushboolean(L, 1); return 1; } pllua-ng-REL_2_0_4/src/paths.c000066400000000000000000000025551347047754200161760ustar00rootroot00000000000000/* paths.c */ #include "pllua.h" #include "miscadmin.h" typedef void (pathfunc_type)(const char *, char *); static int pllua_get_path(lua_State *L) { pathfunc_type *func = (pathfunc_type *) lua_touserdata(L, lua_upvalueindex(1)); char path[MAXPGPATH]; path[0] = '\0'; (*func)(my_exec_path, path); if (path[0]) lua_pushstring(L, path); else lua_pushnil(L); return 1; } static void get_bin_path(const char *exec_path, char *retpath) { char *lastsep; strlcpy(retpath, exec_path, MAXPGPATH); lastsep = strrchr(retpath, '/'); if (lastsep) *lastsep = '\0'; else *retpath = '\0'; } static struct { const char *name; pathfunc_type *func; } path_funcs[] = { { "bin", get_bin_path }, { "doc", get_doc_path }, { "etc", get_etc_path }, { "html", get_html_path }, { "include", get_include_path }, { "includeserver", get_includeserver_path }, { "lib", get_lib_path }, { "libdir", get_pkglib_path }, { "locale", get_locale_path }, { "man", get_man_path }, { "pkginclude", get_pkginclude_path }, { "pkglib", get_pkglib_path }, { "share", get_share_path }, { NULL, NULL } }; int pllua_open_paths(lua_State *L) { int i; lua_settop(L, 0); lua_newtable(L); for (i = 0; path_funcs[i].name; ++i) { lua_pushlightuserdata(L, path_funcs[i].func); lua_pushcclosure(L, pllua_get_path, 1); lua_setfield(L, 1, path_funcs[i].name); } return 1; } pllua-ng-REL_2_0_4/src/plerrcodes_old.h000066400000000000000000000405321347047754200200610ustar00rootroot00000000000000/* * From pg11 onwards, we use an autogenerated file in place of this. This copy * is stolen wholesale from plpgsql in pg10, except that the warning and * success codes were added (back) by hand and conditionals added to make it * work with 9.6 and 9.5. */ { "successful_completion", ERRCODE_SUCCESSFUL_COMPLETION }, { "warning", ERRCODE_WARNING }, { "dynamic_result_sets_returned", ERRCODE_WARNING_DYNAMIC_RESULT_SETS_RETURNED }, { "implicit_zero_bit_padding", ERRCODE_WARNING_IMPLICIT_ZERO_BIT_PADDING }, { "null_value_eliminated_in_set_function", ERRCODE_WARNING_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION }, { "privilege_not_granted", ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED }, { "privilege_not_revoked", ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED }, { "string_data_right_truncation", ERRCODE_WARNING_STRING_DATA_RIGHT_TRUNCATION }, { "deprecated_feature", ERRCODE_WARNING_DEPRECATED_FEATURE }, { "no_data", ERRCODE_NO_DATA }, { "no_additional_dynamic_result_sets_returned", ERRCODE_NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED }, { "sql_statement_not_yet_complete", ERRCODE_SQL_STATEMENT_NOT_YET_COMPLETE }, { "connection_exception", ERRCODE_CONNECTION_EXCEPTION }, { "connection_does_not_exist", ERRCODE_CONNECTION_DOES_NOT_EXIST }, { "connection_failure", ERRCODE_CONNECTION_FAILURE }, { "sqlclient_unable_to_establish_sqlconnection", ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION }, { "sqlserver_rejected_establishment_of_sqlconnection", ERRCODE_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION }, { "transaction_resolution_unknown", ERRCODE_TRANSACTION_RESOLUTION_UNKNOWN }, { "protocol_violation", ERRCODE_PROTOCOL_VIOLATION }, { "triggered_action_exception", ERRCODE_TRIGGERED_ACTION_EXCEPTION }, { "feature_not_supported", ERRCODE_FEATURE_NOT_SUPPORTED }, { "invalid_transaction_initiation", ERRCODE_INVALID_TRANSACTION_INITIATION }, { "locator_exception", ERRCODE_LOCATOR_EXCEPTION }, { "invalid_locator_specification", ERRCODE_L_E_INVALID_SPECIFICATION }, { "invalid_grantor", ERRCODE_INVALID_GRANTOR }, { "invalid_grant_operation", ERRCODE_INVALID_GRANT_OPERATION }, { "invalid_role_specification", ERRCODE_INVALID_ROLE_SPECIFICATION }, { "diagnostics_exception", ERRCODE_DIAGNOSTICS_EXCEPTION }, { "stacked_diagnostics_accessed_without_active_handler", ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER }, { "case_not_found", ERRCODE_CASE_NOT_FOUND }, { "cardinality_violation", ERRCODE_CARDINALITY_VIOLATION }, { "data_exception", ERRCODE_DATA_EXCEPTION }, { "array_subscript_error", ERRCODE_ARRAY_SUBSCRIPT_ERROR }, { "character_not_in_repertoire", ERRCODE_CHARACTER_NOT_IN_REPERTOIRE }, { "datetime_field_overflow", ERRCODE_DATETIME_FIELD_OVERFLOW }, { "division_by_zero", ERRCODE_DIVISION_BY_ZERO }, { "error_in_assignment", ERRCODE_ERROR_IN_ASSIGNMENT }, { "escape_character_conflict", ERRCODE_ESCAPE_CHARACTER_CONFLICT }, { "indicator_overflow", ERRCODE_INDICATOR_OVERFLOW }, { "interval_field_overflow", ERRCODE_INTERVAL_FIELD_OVERFLOW }, { "invalid_argument_for_logarithm", ERRCODE_INVALID_ARGUMENT_FOR_LOG }, { "invalid_argument_for_ntile_function", ERRCODE_INVALID_ARGUMENT_FOR_NTILE }, { "invalid_argument_for_nth_value_function", ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE }, { "invalid_argument_for_power_function", ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION }, { "invalid_argument_for_width_bucket_function", ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION }, { "invalid_character_value_for_cast", ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST }, { "invalid_datetime_format", ERRCODE_INVALID_DATETIME_FORMAT }, { "invalid_escape_character", ERRCODE_INVALID_ESCAPE_CHARACTER }, { "invalid_escape_octet", ERRCODE_INVALID_ESCAPE_OCTET }, { "invalid_escape_sequence", ERRCODE_INVALID_ESCAPE_SEQUENCE }, { "nonstandard_use_of_escape_character", ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER }, { "invalid_indicator_parameter_value", ERRCODE_INVALID_INDICATOR_PARAMETER_VALUE }, { "invalid_parameter_value", ERRCODE_INVALID_PARAMETER_VALUE }, { "invalid_regular_expression", ERRCODE_INVALID_REGULAR_EXPRESSION }, { "invalid_row_count_in_limit_clause", ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE }, { "invalid_row_count_in_result_offset_clause", ERRCODE_INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE }, { "invalid_tablesample_argument", ERRCODE_INVALID_TABLESAMPLE_ARGUMENT }, { "invalid_tablesample_repeat", ERRCODE_INVALID_TABLESAMPLE_REPEAT }, { "invalid_time_zone_displacement_value", ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE }, { "invalid_use_of_escape_character", ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER }, { "most_specific_type_mismatch", ERRCODE_MOST_SPECIFIC_TYPE_MISMATCH }, { "null_value_not_allowed", ERRCODE_NULL_VALUE_NOT_ALLOWED }, { "null_value_no_indicator_parameter", ERRCODE_NULL_VALUE_NO_INDICATOR_PARAMETER }, { "numeric_value_out_of_range", ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE }, #if PG_VERSION_NUM >= 100000 { "sequence_generator_limit_exceeded", ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED }, #endif { "string_data_length_mismatch", ERRCODE_STRING_DATA_LENGTH_MISMATCH }, { "string_data_right_truncation", ERRCODE_STRING_DATA_RIGHT_TRUNCATION }, { "substring_error", ERRCODE_SUBSTRING_ERROR }, { "trim_error", ERRCODE_TRIM_ERROR }, { "unterminated_c_string", ERRCODE_UNTERMINATED_C_STRING }, { "zero_length_character_string", ERRCODE_ZERO_LENGTH_CHARACTER_STRING }, { "floating_point_exception", ERRCODE_FLOATING_POINT_EXCEPTION }, { "invalid_text_representation", ERRCODE_INVALID_TEXT_REPRESENTATION }, { "invalid_binary_representation", ERRCODE_INVALID_BINARY_REPRESENTATION }, { "bad_copy_file_format", ERRCODE_BAD_COPY_FILE_FORMAT }, { "untranslatable_character", ERRCODE_UNTRANSLATABLE_CHARACTER }, { "not_an_xml_document", ERRCODE_NOT_AN_XML_DOCUMENT }, { "invalid_xml_document", ERRCODE_INVALID_XML_DOCUMENT }, { "invalid_xml_content", ERRCODE_INVALID_XML_CONTENT }, { "invalid_xml_comment", ERRCODE_INVALID_XML_COMMENT }, { "invalid_xml_processing_instruction", ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION }, { "integrity_constraint_violation", ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION }, { "restrict_violation", ERRCODE_RESTRICT_VIOLATION }, { "not_null_violation", ERRCODE_NOT_NULL_VIOLATION }, { "foreign_key_violation", ERRCODE_FOREIGN_KEY_VIOLATION }, { "unique_violation", ERRCODE_UNIQUE_VIOLATION }, { "check_violation", ERRCODE_CHECK_VIOLATION }, { "exclusion_violation", ERRCODE_EXCLUSION_VIOLATION }, { "invalid_cursor_state", ERRCODE_INVALID_CURSOR_STATE }, { "invalid_transaction_state", ERRCODE_INVALID_TRANSACTION_STATE }, { "active_sql_transaction", ERRCODE_ACTIVE_SQL_TRANSACTION }, { "branch_transaction_already_active", ERRCODE_BRANCH_TRANSACTION_ALREADY_ACTIVE }, { "held_cursor_requires_same_isolation_level", ERRCODE_HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL }, { "inappropriate_access_mode_for_branch_transaction", ERRCODE_INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION }, { "inappropriate_isolation_level_for_branch_transaction", ERRCODE_INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION }, { "no_active_sql_transaction_for_branch_transaction", ERRCODE_NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION }, { "read_only_sql_transaction", ERRCODE_READ_ONLY_SQL_TRANSACTION }, { "schema_and_data_statement_mixing_not_supported", ERRCODE_SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED }, { "no_active_sql_transaction", ERRCODE_NO_ACTIVE_SQL_TRANSACTION }, { "in_failed_sql_transaction", ERRCODE_IN_FAILED_SQL_TRANSACTION }, #if PG_VERSION_NUM >= 90600 { "idle_in_transaction_session_timeout", ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT }, #endif { "invalid_sql_statement_name", ERRCODE_INVALID_SQL_STATEMENT_NAME }, { "triggered_data_change_violation", ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION }, { "invalid_authorization_specification", ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION }, { "invalid_password", ERRCODE_INVALID_PASSWORD }, { "dependent_privilege_descriptors_still_exist", ERRCODE_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST }, { "dependent_objects_still_exist", ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST }, { "invalid_transaction_termination", ERRCODE_INVALID_TRANSACTION_TERMINATION }, { "sql_routine_exception", ERRCODE_SQL_ROUTINE_EXCEPTION }, { "function_executed_no_return_statement", ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT }, { "modifying_sql_data_not_permitted", ERRCODE_S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED }, { "prohibited_sql_statement_attempted", ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED }, { "reading_sql_data_not_permitted", ERRCODE_S_R_E_READING_SQL_DATA_NOT_PERMITTED }, { "invalid_cursor_name", ERRCODE_INVALID_CURSOR_NAME }, { "external_routine_exception", ERRCODE_EXTERNAL_ROUTINE_EXCEPTION }, { "containing_sql_not_permitted", ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED }, { "modifying_sql_data_not_permitted", ERRCODE_E_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED }, { "prohibited_sql_statement_attempted", ERRCODE_E_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED }, { "reading_sql_data_not_permitted", ERRCODE_E_R_E_READING_SQL_DATA_NOT_PERMITTED }, { "external_routine_invocation_exception", ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION }, { "invalid_sqlstate_returned", ERRCODE_E_R_I_E_INVALID_SQLSTATE_RETURNED }, { "null_value_not_allowed", ERRCODE_E_R_I_E_NULL_VALUE_NOT_ALLOWED }, { "trigger_protocol_violated", ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED }, { "srf_protocol_violated", ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED }, { "event_trigger_protocol_violated", ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED }, { "savepoint_exception", ERRCODE_SAVEPOINT_EXCEPTION }, { "invalid_savepoint_specification", ERRCODE_S_E_INVALID_SPECIFICATION }, { "invalid_catalog_name", ERRCODE_INVALID_CATALOG_NAME }, { "invalid_schema_name", ERRCODE_INVALID_SCHEMA_NAME }, { "transaction_rollback", ERRCODE_TRANSACTION_ROLLBACK }, { "transaction_integrity_constraint_violation", ERRCODE_T_R_INTEGRITY_CONSTRAINT_VIOLATION }, { "serialization_failure", ERRCODE_T_R_SERIALIZATION_FAILURE }, { "statement_completion_unknown", ERRCODE_T_R_STATEMENT_COMPLETION_UNKNOWN }, { "deadlock_detected", ERRCODE_T_R_DEADLOCK_DETECTED }, { "syntax_error_or_access_rule_violation", ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION }, { "syntax_error", ERRCODE_SYNTAX_ERROR }, { "insufficient_privilege", ERRCODE_INSUFFICIENT_PRIVILEGE }, { "cannot_coerce", ERRCODE_CANNOT_COERCE }, { "grouping_error", ERRCODE_GROUPING_ERROR }, { "windowing_error", ERRCODE_WINDOWING_ERROR }, { "invalid_recursion", ERRCODE_INVALID_RECURSION }, { "invalid_foreign_key", ERRCODE_INVALID_FOREIGN_KEY }, { "invalid_name", ERRCODE_INVALID_NAME }, { "name_too_long", ERRCODE_NAME_TOO_LONG }, { "reserved_name", ERRCODE_RESERVED_NAME }, { "datatype_mismatch", ERRCODE_DATATYPE_MISMATCH }, { "indeterminate_datatype", ERRCODE_INDETERMINATE_DATATYPE }, { "collation_mismatch", ERRCODE_COLLATION_MISMATCH }, { "indeterminate_collation", ERRCODE_INDETERMINATE_COLLATION }, { "wrong_object_type", ERRCODE_WRONG_OBJECT_TYPE }, #if PG_VERSION_NUM >= 100000 { "generated_always", ERRCODE_GENERATED_ALWAYS }, #endif { "undefined_column", ERRCODE_UNDEFINED_COLUMN }, { "undefined_function", ERRCODE_UNDEFINED_FUNCTION }, { "undefined_table", ERRCODE_UNDEFINED_TABLE }, { "undefined_parameter", ERRCODE_UNDEFINED_PARAMETER }, { "undefined_object", ERRCODE_UNDEFINED_OBJECT }, { "duplicate_column", ERRCODE_DUPLICATE_COLUMN }, { "duplicate_cursor", ERRCODE_DUPLICATE_CURSOR }, { "duplicate_database", ERRCODE_DUPLICATE_DATABASE }, { "duplicate_function", ERRCODE_DUPLICATE_FUNCTION }, { "duplicate_prepared_statement", ERRCODE_DUPLICATE_PSTATEMENT }, { "duplicate_schema", ERRCODE_DUPLICATE_SCHEMA }, { "duplicate_table", ERRCODE_DUPLICATE_TABLE }, { "duplicate_alias", ERRCODE_DUPLICATE_ALIAS }, { "duplicate_object", ERRCODE_DUPLICATE_OBJECT }, { "ambiguous_column", ERRCODE_AMBIGUOUS_COLUMN }, { "ambiguous_function", ERRCODE_AMBIGUOUS_FUNCTION }, { "ambiguous_parameter", ERRCODE_AMBIGUOUS_PARAMETER }, { "ambiguous_alias", ERRCODE_AMBIGUOUS_ALIAS }, { "invalid_column_reference", ERRCODE_INVALID_COLUMN_REFERENCE }, { "invalid_column_definition", ERRCODE_INVALID_COLUMN_DEFINITION }, { "invalid_cursor_definition", ERRCODE_INVALID_CURSOR_DEFINITION }, { "invalid_database_definition", ERRCODE_INVALID_DATABASE_DEFINITION }, { "invalid_function_definition", ERRCODE_INVALID_FUNCTION_DEFINITION }, { "invalid_prepared_statement_definition", ERRCODE_INVALID_PSTATEMENT_DEFINITION }, { "invalid_schema_definition", ERRCODE_INVALID_SCHEMA_DEFINITION }, { "invalid_table_definition", ERRCODE_INVALID_TABLE_DEFINITION }, { "invalid_object_definition", ERRCODE_INVALID_OBJECT_DEFINITION }, { "with_check_option_violation", ERRCODE_WITH_CHECK_OPTION_VIOLATION }, { "insufficient_resources", ERRCODE_INSUFFICIENT_RESOURCES }, { "disk_full", ERRCODE_DISK_FULL }, { "out_of_memory", ERRCODE_OUT_OF_MEMORY }, { "too_many_connections", ERRCODE_TOO_MANY_CONNECTIONS }, { "configuration_limit_exceeded", ERRCODE_CONFIGURATION_LIMIT_EXCEEDED }, { "program_limit_exceeded", ERRCODE_PROGRAM_LIMIT_EXCEEDED }, { "statement_too_complex", ERRCODE_STATEMENT_TOO_COMPLEX }, { "too_many_columns", ERRCODE_TOO_MANY_COLUMNS }, { "too_many_arguments", ERRCODE_TOO_MANY_ARGUMENTS }, { "object_not_in_prerequisite_state", ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE }, { "object_in_use", ERRCODE_OBJECT_IN_USE }, { "cant_change_runtime_param", ERRCODE_CANT_CHANGE_RUNTIME_PARAM }, { "lock_not_available", ERRCODE_LOCK_NOT_AVAILABLE }, { "operator_intervention", ERRCODE_OPERATOR_INTERVENTION }, { "query_canceled", ERRCODE_QUERY_CANCELED }, { "admin_shutdown", ERRCODE_ADMIN_SHUTDOWN }, { "crash_shutdown", ERRCODE_CRASH_SHUTDOWN }, { "cannot_connect_now", ERRCODE_CANNOT_CONNECT_NOW }, { "database_dropped", ERRCODE_DATABASE_DROPPED }, { "system_error", ERRCODE_SYSTEM_ERROR }, { "io_error", ERRCODE_IO_ERROR }, { "undefined_file", ERRCODE_UNDEFINED_FILE }, { "duplicate_file", ERRCODE_DUPLICATE_FILE }, #if PG_VERSION_NUM >= 90600 { "snapshot_too_old", ERRCODE_SNAPSHOT_TOO_OLD }, #endif { "config_file_error", ERRCODE_CONFIG_FILE_ERROR }, { "lock_file_exists", ERRCODE_LOCK_FILE_EXISTS }, { "fdw_error", ERRCODE_FDW_ERROR }, { "fdw_column_name_not_found", ERRCODE_FDW_COLUMN_NAME_NOT_FOUND }, { "fdw_dynamic_parameter_value_needed", ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED }, { "fdw_function_sequence_error", ERRCODE_FDW_FUNCTION_SEQUENCE_ERROR }, { "fdw_inconsistent_descriptor_information", ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION }, { "fdw_invalid_attribute_value", ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE }, { "fdw_invalid_column_name", ERRCODE_FDW_INVALID_COLUMN_NAME }, { "fdw_invalid_column_number", ERRCODE_FDW_INVALID_COLUMN_NUMBER }, { "fdw_invalid_data_type", ERRCODE_FDW_INVALID_DATA_TYPE }, { "fdw_invalid_data_type_descriptors", ERRCODE_FDW_INVALID_DATA_TYPE_DESCRIPTORS }, { "fdw_invalid_descriptor_field_identifier", ERRCODE_FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER }, { "fdw_invalid_handle", ERRCODE_FDW_INVALID_HANDLE }, { "fdw_invalid_option_index", ERRCODE_FDW_INVALID_OPTION_INDEX }, { "fdw_invalid_option_name", ERRCODE_FDW_INVALID_OPTION_NAME }, { "fdw_invalid_string_length_or_buffer_length", ERRCODE_FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH }, { "fdw_invalid_string_format", ERRCODE_FDW_INVALID_STRING_FORMAT }, { "fdw_invalid_use_of_null_pointer", ERRCODE_FDW_INVALID_USE_OF_NULL_POINTER }, { "fdw_too_many_handles", ERRCODE_FDW_TOO_MANY_HANDLES }, { "fdw_out_of_memory", ERRCODE_FDW_OUT_OF_MEMORY }, { "fdw_no_schemas", ERRCODE_FDW_NO_SCHEMAS }, { "fdw_option_name_not_found", ERRCODE_FDW_OPTION_NAME_NOT_FOUND }, { "fdw_reply_handle", ERRCODE_FDW_REPLY_HANDLE }, { "fdw_schema_not_found", ERRCODE_FDW_SCHEMA_NOT_FOUND }, { "fdw_table_not_found", ERRCODE_FDW_TABLE_NOT_FOUND }, { "fdw_unable_to_create_execution", ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION }, { "fdw_unable_to_create_reply", ERRCODE_FDW_UNABLE_TO_CREATE_REPLY }, { "fdw_unable_to_establish_connection", ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION }, { "plpgsql_error", ERRCODE_PLPGSQL_ERROR }, { "raise_exception", ERRCODE_RAISE_EXCEPTION }, { "no_data_found", ERRCODE_NO_DATA_FOUND }, { "too_many_rows", ERRCODE_TOO_MANY_ROWS }, { "assert_failure", ERRCODE_ASSERT_FAILURE }, { "internal_error", ERRCODE_INTERNAL_ERROR }, { "data_corrupted", ERRCODE_DATA_CORRUPTED }, { "index_corrupted", ERRCODE_INDEX_CORRUPTED }, pllua-ng-REL_2_0_4/src/pllua.c000066400000000000000000000144051347047754200161710ustar00rootroot00000000000000/* * pllua.c: PL/Lua NG call handler * By Andrew "RhodiumToad" Gierth, rhodiumtoad at postgresql.org * Based in some part on pllua by Luis Carvalho and others * License: MIT license or PostgreSQL licence */ #include "pllua.h" #include "commands/event_trigger.h" #include "commands/trigger.h" PG_MODULE_MAGIC; /* * Exposed interface * * see also _PG_init in init.c */ PGDLLEXPORT Datum pllua_validator(PG_FUNCTION_ARGS); PGDLLEXPORT Datum pllua_call_handler(PG_FUNCTION_ARGS); PGDLLEXPORT Datum pllua_inline_handler(PG_FUNCTION_ARGS); PGDLLEXPORT Datum plluau_validator(PG_FUNCTION_ARGS); PGDLLEXPORT Datum plluau_call_handler(PG_FUNCTION_ARGS); PGDLLEXPORT Datum plluau_inline_handler(PG_FUNCTION_ARGS); static Datum pllua_common_call(FunctionCallInfo fcinfo, bool trusted); static Datum pllua_common_inline(FunctionCallInfo fcinfo, bool trusted); static Datum pllua_common_validator(FunctionCallInfo fcinfo, bool trusted); /* Trusted entry points */ PG_FUNCTION_INFO_V1(pllua_validator); Datum pllua_validator(PG_FUNCTION_ARGS) { return pllua_common_validator(fcinfo, true); } PG_FUNCTION_INFO_V1(pllua_call_handler); Datum pllua_call_handler(PG_FUNCTION_ARGS) { return pllua_common_call(fcinfo, true); } PG_FUNCTION_INFO_V1(pllua_inline_handler); Datum pllua_inline_handler(PG_FUNCTION_ARGS) { return pllua_common_inline(fcinfo, true); } /* Untrusted entry points */ PG_FUNCTION_INFO_V1(plluau_validator); Datum plluau_validator(PG_FUNCTION_ARGS) { return pllua_common_validator(fcinfo, false); } PG_FUNCTION_INFO_V1(plluau_call_handler); Datum plluau_call_handler(PG_FUNCTION_ARGS) { return pllua_common_call(fcinfo, false); } PG_FUNCTION_INFO_V1(plluau_inline_handler); Datum plluau_inline_handler(PG_FUNCTION_ARGS) { return pllua_common_inline(fcinfo, false); } /* Common implementations */ static void pllua_entry_stack_check(void) { check_stack_depth(); } Datum pllua_common_call(FunctionCallInfo fcinfo, bool trusted) { pllua_interpreter *volatile interp = NULL; pllua_activation_record act; pllua_func_activation *funcact = (fcinfo->flinfo) ? fcinfo->flinfo->fn_extra : NULL; ErrorContextCallback ecxt; pllua_entry_stack_check(); act.fcinfo = fcinfo; act.retval = (Datum) 0; act.atomic = true; act.trusted = trusted; act.cblock = NULL; act.validate_func = InvalidOid; act.interp = NULL; act.active_error = LUA_REFNIL; act.err_text = NULL; #if PG_VERSION_NUM >= 110000 if (fcinfo->context && IsA(fcinfo->context, CallContext)) act.atomic = castNode(CallContext, fcinfo->context)->atomic; #endif pllua_setcontext(PLLUA_CONTEXT_PG); /* * this catch block exists to save/restore the error context stack and * allow cleanup of our internal error state when returning to PG proper */ PG_TRY(); { ecxt.callback = pllua_error_callback; ecxt.arg = &act; ecxt.previous = error_context_stack; error_context_stack = &ecxt; if (funcact && funcact->thread) act.interp = funcact->interp; else act.interp = pllua_getstate(trusted, &act); interp = act.interp; if (funcact && funcact->thread) { /* * We're resuming a value-per-call SRF, so we bypass almost * everything since we don't want to, for example, compile a new * version of the function halfway through a result set. We know * we're in a non-first-row condition if there's an existing thread * in the function activation. */ pllua_initial_protected_call(act.interp, pllua_resume_function, &act); } else if (CALLED_AS_TRIGGER(fcinfo)) pllua_initial_protected_call(act.interp, pllua_call_trigger, &act); else if (CALLED_AS_EVENT_TRIGGER(fcinfo)) pllua_initial_protected_call(act.interp, pllua_call_event_trigger, &act); else pllua_initial_protected_call(act.interp, pllua_call_function, &act); } PG_CATCH(); { if (interp) pllua_error_cleanup(interp, &act); PG_RE_THROW(); } PG_END_TRY(); return act.retval; } Datum pllua_common_validator(FunctionCallInfo fcinfo, bool trusted) { pllua_interpreter *volatile interp = NULL; pllua_activation_record act; Oid funcoid = PG_GETARG_OID(0); ErrorContextCallback ecxt; /* security checks */ if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) PG_RETURN_VOID(); /* * This doesn't need a stack check because the validator is careful * not to execute user-supplied Lua code. */ act.fcinfo = NULL; act.retval = (Datum) 0; act.atomic = true; act.trusted = trusted; act.cblock = NULL; act.validate_func = funcoid; act.interp = NULL; act.active_error = LUA_REFNIL; act.err_text = NULL; pllua_setcontext(PLLUA_CONTEXT_PG); /* * this catch block exists to save/restore the error context stack and * allow cleanup of our internal error state when returning to PG proper */ PG_TRY(); { ecxt.callback = pllua_error_callback; ecxt.arg = &act; ecxt.previous = error_context_stack; error_context_stack = &ecxt; interp = act.interp = pllua_getstate(trusted, &act); pllua_initial_protected_call(act.interp, pllua_validate, &act); } PG_CATCH(); { if (interp) pllua_error_cleanup(interp, &act); PG_RE_THROW(); } PG_END_TRY(); PG_RETURN_VOID(); } Datum pllua_common_inline(FunctionCallInfo fcinfo, bool trusted) { pllua_interpreter *volatile interp = NULL; pllua_activation_record act; ErrorContextCallback ecxt; pllua_entry_stack_check(); act.fcinfo = NULL; act.retval = (Datum) 0; act.atomic = true; act.trusted = trusted; act.cblock = (InlineCodeBlock *) PG_GETARG_POINTER(0); act.validate_func = InvalidOid; act.interp = NULL; act.active_error = LUA_REFNIL; act.err_text = "inline block entry"; #if PG_VERSION_NUM >= 110000 act.atomic = act.cblock->atomic; #endif pllua_setcontext(PLLUA_CONTEXT_PG); /* probably excess paranoia */ if (act.cblock->langIsTrusted != act.trusted) elog(ERROR, "trusted state mismatch"); /* * this catch block exists to save/restore the error context stack and * allow cleanup of our internal error state when returning to PG proper */ PG_TRY(); { ecxt.callback = pllua_error_callback; ecxt.arg = &act; ecxt.previous = error_context_stack; error_context_stack = &ecxt; interp = act.interp = pllua_getstate(trusted, &act); pllua_initial_protected_call(act.interp, pllua_call_inline, &act); } PG_CATCH(); { if (interp) pllua_error_cleanup(interp, &act); PG_RE_THROW(); } PG_END_TRY(); PG_RETURN_VOID(); } pllua-ng-REL_2_0_4/src/pllua.h000066400000000000000000000500431347047754200161740ustar00rootroot00000000000000/* * pllua.h */ #ifndef PLLUA_H #define PLLUA_H #include "postgres.h" #include "fmgr.h" #include "funcapi.h" #include "utils/guc.h" #include "utils/memutils.h" #include "utils/palloc.h" #include "miscadmin.h" #include #include #include #define PLLUA_VERSION_STR "2.0" #define PLLUA_VERSION_NUM 200 #define PLLUA_REVISION_STR "2.0004" #define PLLUA_REVISION_NUM 20004 /* PG version dependencies */ #include "pllua_pgver.h" #if LUA_VERSION_NUM == 501 /* assume Lua 5.1 is actually luajit */ #include "pllua_luajit.h" #elif LUA_VERSION_NUM == 503 /* Lua version dependencies */ #include "pllua_luaver.h" #else #error Unsupported Lua version (only Lua 5.3 and Luajit are supported) #endif /* * Define how we want to handle int8 values. * * If nothing here is set, we will treat int8 as any other ordinary datum, * which means it'll be passed through unchanged, will stringify to its * ordinary decimal representation, will work as a json key or value and so on, * but won't be accessible to arithmetic or comparisons (or use as a table key) * in the lua code except by conversion to pgtype.numeric and use of the * numeric module. * * If PLLUA_INT8_OK is defined, which we do if the underlying Lua has a 64-bit * integer subtype (lua 5.3 with lua_Integer being 64 bits), then we convert * int8 to a Lua value and back, which makes it available for direct arithmetic * and use as a table key, while still stringifying to the same value and * working as a json key or value. * * If PLLUA_INT8_LUAJIT_HACK is defined, which we never do automatically at * present but only if using luajit _and_ USE_INT8_CDATA is defined on the * command line, then we convert int8 to a luajit cdata. This breaks * stringification and json key/value usage, does not enable use as a table * key, but makes arithmetic possible. This all seems like a major loss on * balance, hence why it's not the default. * * NO_LUAJIT turns off all attempts to use luajit-specific features. * */ #if LUA_VERSION_NUM == 503 #if LUA_MAXINTEGER == PG_INT64_MAX #define PLLUA_INT8_OK #endif #endif #if LUAJIT_VERSION_NUM > 0 && defined(USE_INT8_CDATA) && !defined(NO_LUAJIT) #define PLLUA_INT8_LUAJIT_HACK #else #undef PLLUA_INT8_LUAJIT_HACK #endif /* * Track what error handling context we're in, to try and detect any violations * of error-handling protocol (lua errors thrown through pg catch blocks and * vice-versa). */ typedef enum { PLLUA_CONTEXT_PG, PLLUA_CONTEXT_LUA } pllua_context_type; extern pllua_context_type pllua_context; #define ASSERT_PG_CONTEXT Assert(pllua_context == PLLUA_CONTEXT_PG) #define ASSERT_LUA_CONTEXT Assert(pllua_context == PLLUA_CONTEXT_LUA) static inline pllua_context_type pllua_setcontext(pllua_context_type newctx) { pllua_context_type oldctx = pllua_context; pllua_context = newctx; return oldctx; } /* * Abbreviate the most common form of catch block. */ #define PLLUA_TRY() do { \ pllua_context_type _pllua_oldctx = pllua_setcontext(PLLUA_CONTEXT_PG); \ MemoryContext _pllua_oldmcxt = CurrentMemoryContext; \ PG_TRY() #define PLLUA_CATCH_RETHROW() \ PG_CATCH(); \ { pllua_setcontext(_pllua_oldctx); pllua_rethrow_from_pg(L, _pllua_oldmcxt); } \ PG_END_TRY(); pllua_setcontext(_pllua_oldctx); } while(0) #define pllua_debug(L_, ...) \ do { if (pllua_context==PLLUA_CONTEXT_PG) elog(DEBUG1, __VA_ARGS__); \ else pllua_debug_lua(L_, __VA_ARGS__); } while(0) #define PLLUA_CHECK_PG_STACK_DEPTH() \ do { if (stack_is_too_deep()) luaL_error(L, "stack depth exceeded"); } while (0) /* * Describes one call to the top-level handler. */ struct pllua_interpreter; typedef struct pllua_activation_record { FunctionCallInfo fcinfo; Datum retval; /* if fcinfo is null, we're validating or doing inline */ InlineCodeBlock *cblock; Oid validate_func; bool atomic; bool trusted; /* registry ref for current error if any */ int active_error; /* for error context stuff */ struct pllua_interpreter *interp; const char *err_text; } pllua_activation_record; typedef struct pllua_cache_inval { bool inval_type; bool inval_rel; bool inval_cast; Oid inval_typeoid; Oid inval_reloid; } pllua_cache_inval; /* * Top-level data for one interpreter. We keep a hashtable of these per user_id * (for trusted mode isolation). We keep a pointer to this in the Lua registry * and use it to access the current activation fields (which are saved/restored * on recursive entries). */ typedef struct pllua_interpreter { Oid user_id; /* Hash key (must be first!) */ lua_State *L; /* The interpreter proper */ bool trusted; bool new_ident; unsigned long gc_debt; /* estimated additional GC debt */ /* state below must be saved/restored for recursive calls */ pllua_activation_record cur_activation; /* stuff used transiently in error handling and elsewhere */ lua_Debug ar; int errdepth; bool update_errdepth; pllua_cache_inval *inval; } pllua_interpreter; /* We abuse the node system to pass this in fcinfo->context */ #define PLLUA_MAGIC 0x4c554101 typedef struct pllua_node { NodeTag type; /* we put T_Invalid here */ uint32 magic; lua_State *L; } pllua_node; /* * We don't put this in the body of a lua userdata for error handling reasons; * we want to build it from pg data without involving lua too much until we're * ready to actually compile the function. Instead, the lua object is a pointer * to this with a __gc method, and the object itself is palloc'd (in its own * memory context). The activation records (corresponding to flinfo) are lua * objects that reference the funcinfo, preventing it from being GC'd while in * use. * * The actual lua function object is stored in the uservalue slot under key * light(PLLUA_FUNCTION_MEMBER). */ typedef struct pllua_function_info { Oid fn_oid; /* for revalidation checks */ TransactionId fn_xmin; ItemPointerData fn_tid; Oid rettype; bool returns_row; bool retset; bool readonly; bool is_trigger; bool is_event_trigger; int nargs; bool variadic; bool variadic_any; bool polymorphic; bool polymorphic_ret; Oid *argtypes; Oid language_oid; bool trusted; MemoryContext mcxt; const char *name; } pllua_function_info; /* * This is info we need to compile the function but not needed to run it. */ typedef struct pllua_function_compile_info { pllua_function_info *func_info; MemoryContext mcxt; text *prosrc; int nargs; int nallargs; Oid variadic; Oid *allargtypes; char *argmodes; char **argnames; bool validate_only; /* don't run any code when compiling */ } pllua_function_compile_info; /* this one ends up in flinfo->fn_extra */ typedef struct pllua_func_activation { lua_State *thread; /* non-null for a running SRF */ bool onstack; /* needed for error handling */ pllua_interpreter *interp; /* direct access for SRF resume */ pllua_function_info *func_info; bool resolved; bool polymorphic; bool variadic_call; /* only if variadic_any */ bool retset; bool readonly; Oid rettype; TupleDesc tupdesc; TypeFuncClass typefuncclass; bool retdomain; int nargs; Oid *argtypes; /* with polymorphism resolved */ /* * this data is allocated and referenced in lua, so we need to arrange to * drop it for GC when the context containing the pointer to it is reset */ lua_State *L; bool dead; MemoryContextCallback cb; } pllua_func_activation; /* * Body of a Datum object. typmod is usually -1 except when we got the value * from a source with a declared typmod (such as a column). */ typedef struct pllua_datum { Datum value; int32 typmod; bool need_gc; bool modified; /* composite value has been exploded */ } pllua_datum; /* * Stuff we store about types. Datum values reference this from their * metatables (in fact the metatable of the Datum is the uservalue of * this object, which also contains a reference to the object itself). */ typedef struct pllua_typeinfo { Oid typeoid; int32 typmod; /* only for RECORD */ int arity; /* 1 for scalars, otherwise no. undropped cols */ int natts; /* -1 for scalars */ TupleDesc tupdesc; Oid reloid; /* for named composite types */ Oid basetype; /* for domains */ Oid elemtype; /* for arrays */ Oid rangetype; /* for ranges */ bool hasoid; bool is_array; bool is_range; bool is_enum; bool is_anonymous_record; bool nested_unknowns; bool nested_composites; bool revalidate; bool modified; bool obsolete; int16 typlen; bool typbyval; char typalign; char typdelim; Oid typioparam; Oid outfuncid; Oid infuncid; /* we don't look these up until we need them */ Oid sendfuncid; Oid recvfuncid; FmgrInfo outfunc; FmgrInfo infunc; FmgrInfo sendfunc; FmgrInfo recvfunc; bool coerce_typmod; /* typmod coercions needed */ bool coerce_typmod_element; Oid typmod_funcid; int32 basetypmod; /* for domains */ void *domain_extra; /* domain_check workspace */ ArrayMetaState array_meta; /* array workspace */ int16 elemtyplen; /* for arrays only */ bool elemtypbyval; char elemtypalign; Oid fromsql; /* fromsql(internal) returns internal */ Oid tosql; /* tosql(internal) returns datum */ /* * we give this its own context, because we can't control what fmgr will * dangle off the FmgrInfo structs */ MemoryContext mcxt; } pllua_typeinfo; /* * are we shutting down? */ extern bool pllua_ending; /* * Addresses used as lua registry or object keys * * Note the key is the address, not the string; the string is only for * debugging purposes. * * The registry looks like this (all keys are light userdata): * * global state: * reg[PLLUA_MEMORYCONTEXT] = light(MemoryContext) - for refobj data * reg[PLLUA_ERRORCONTEXT] = light(MemoryContext) - for error handling * reg[PLLUA_USERID] = int user_id for trusted, InvalidOid for untrusted * reg[PLLUA_TRUSTED] = boolean * reg[PLLUA_LANG_OID] = oid of language * reg[PLLUA_LAST_ERROR] = last pg error object to enter error handling * reg[PLLUA_RECURSIVE_ERROR] = preallocated error object * reg[PLLUA_INTERP] = pllua_interpreter struct * * registries for cached data: * reg[PLLUA_FUNCS] = { [integer oid] = funcinfo object } * reg[PLLUA_ACTIVATIONS] = { [light(act)] = activation object } * reg[PLLUA_TYPES] = { [integer oid] = typeinfo object } * reg[PLLUA_RECORDS] = { [integer typmod] = typeinfo object } * reg[PLLUA_PORTALS] = { [light(Portal)] = cursor object } * * metatables: * reg[PLLUA_FUNCTION_OBJECT] * reg[PLLUA_ACTIVATION_OBJECT] * reg[PLLUA_ERROR_OBJECT] * reg[PLLUA_TYPEINFO_OBJECT] * reg[PLLUA_TYPEINFO_PACKAGE_OBJECT] (the pgtype() object itself) * - datum objects and tupconv objects have dynamic metatables * * sandbox: * reg[PLLUA_TRUSTED_SANDBOX] = value of _ENV for trusted funcs * reg[PLLUA_TRUSTED_SANDBOX_LOADED] = modules loaded in sandbox * reg[PLLUA_TRUSTED_SANDBOX_ALLOW] = modules allowed in sandbox * * reg[PLLUA_] * * */ extern char PLLUA_MEMORYCONTEXT[]; extern char PLLUA_ERRORCONTEXT[]; extern char PLLUA_INTERP[]; extern char PLLUA_USERID[]; extern char PLLUA_LANG_OID[]; extern char PLLUA_TRUSTED[]; extern char PLLUA_FUNCS[]; extern char PLLUA_TYPES[]; extern char PLLUA_RECORDS[]; extern char PLLUA_ACTIVATIONS[]; extern char PLLUA_PORTALS[]; extern char PLLUA_FUNCTION_OBJECT[]; extern char PLLUA_ERROR_OBJECT[]; extern char PLLUA_IDXLIST_OBJECT[]; extern char PLLUA_ACTIVATION_OBJECT[]; extern char PLLUA_MCONTEXT_OBJECT[]; extern char PLLUA_TYPEINFO_OBJECT[]; extern char PLLUA_TYPEINFO_PACKAGE_OBJECT[]; extern char PLLUA_TYPEINFO_PACKAGE_ARRAY_OBJECT[]; extern char PLLUA_TUPCONV_OBJECT[]; extern char PLLUA_TRIGGER_OBJECT[]; extern char PLLUA_EVENT_TRIGGER_OBJECT[]; extern char PLLUA_SPI_STMT_OBJECT[]; extern char PLLUA_SPI_CURSOR_OBJECT[]; extern char PLLUA_LAST_ERROR[]; extern char PLLUA_RECURSIVE_ERROR[]; extern char PLLUA_FUNCTION_MEMBER[]; extern char PLLUA_MCONTEXT_MEMBER[]; extern char PLLUA_THREAD_MEMBER[]; extern char PLLUA_TYPEINFO_MEMBER[]; extern char PLLUA_TRUSTED_SANDBOX[]; extern char PLLUA_TRUSTED_SANDBOX_LOADED[]; extern char PLLUA_TRUSTED_SANDBOX_ALLOW[]; extern char PLLUA_PGFUNC_TABLE_OBJECT[]; extern char PLLUA_TYPECONV_REGISTRY[]; extern char PLLUA_ERRCODES_TABLE[]; extern char PLLUA_PRINT_SEVERITY[]; extern char PLLUA_GLOBAL_META[]; extern char PLLUA_SANDBOX_META[]; /* functions */ /* init.c */ pllua_interpreter *pllua_getstate(bool trusted, pllua_activation_record *act); pllua_interpreter *pllua_getinterpreter(lua_State *L); int pllua_set_new_ident(lua_State *L); void pllua_run_extra_gc(lua_State *L, unsigned long gc_debt); PGDLLEXPORT bool pllua_stack_is_too_deep(void); PGDLLEXPORT void pllua_stack_depth_error(void); extern bool pllua_track_gc_debt; /* * This is a macro because we want to avoid executing (sz_) at all if not tracking * gc debt, since it might be a toast_datum_size call with nontrivial overhead. */ #define pllua_record_gc_debt(L_, sz_) \ do { if (pllua_track_gc_debt) pllua_record_gc_debt_real(L_, (sz_)); } while (0) static inline void pllua_record_gc_debt_real(lua_State *L, unsigned long bytes) { pllua_interpreter *interp = pllua_getinterpreter(L); if (interp) interp->gc_debt += bytes; } /* compile.c */ pllua_func_activation *pllua_validate_and_push(lua_State *L, FunctionCallInfo fcinfo, bool trusted); void pllua_compile_inline(lua_State *L, const char *str, bool trusted); int pllua_compile(lua_State *L); int pllua_intern_function(lua_State *L); void pllua_validate_function(lua_State *L, Oid fn_oid, bool trusted); /* datum.c */ int pllua_open_pgtype(lua_State *L); void pllua_verify_encoding(lua_State *L, const char *str); bool pllua_verify_encoding_noerror(lua_State *L, const char *str); void *pllua_palloc(lua_State *L, size_t sz); pllua_typeinfo *pllua_totypeinfo(lua_State *L, int nd); pllua_typeinfo *pllua_checktypeinfo(lua_State *L, int nd, bool revalidate); pllua_datum *pllua_checkanydatum(lua_State *L, int nd, pllua_typeinfo **ti); pllua_datum *pllua_checkdatum(lua_State *L, int nd, int td); pllua_datum *pllua_toanydatum(lua_State *L, int nd, pllua_typeinfo **ti); pllua_datum *pllua_todatum(lua_State *L, int nd, int td); int pllua_typeinfo_invalidate(lua_State *L); void pllua_savedatum(lua_State *L, struct pllua_datum *d, struct pllua_typeinfo *t); void pllua_save_one_datum(lua_State *L, pllua_datum *d, pllua_typeinfo *t); int pllua_value_from_datum(lua_State *L, Datum value, Oid typeid); int pllua_datum_transform_fromsql(lua_State *L, Datum value, int nt, pllua_typeinfo *t); bool pllua_datum_from_value(lua_State *L, int nd, Oid typeid, Datum *result, bool *isnull, const char **errstr); pllua_datum *pllua_newdatum(lua_State *L, int nt, Datum value); int pllua_typeinfo_lookup(lua_State *L); pllua_typeinfo *pllua_newtypeinfo_raw(lua_State *L, Oid oid, int32 typmod, TupleDesc tupdesc); int pllua_typeinfo_parsetype(lua_State *L); int pllua_datum_single(lua_State *L, Datum res, bool isnull, int nt, pllua_typeinfo *t); int pllua_typeconv_invalidate(lua_State *L); void pllua_typeinfo_check_domain(lua_State *L, Datum *val, bool *isnull, int32 typmod, int nt, pllua_typeinfo *t); /* elog.c */ int pllua_open_elog(lua_State *L); int pllua_open_print(lua_State *L); int pllua_p_print (lua_State *L); void pllua_debug_lua(lua_State *L, const char *msg, ...) pg_attribute_printf(2, 3); void pllua_error(lua_State *L, const char *msg, ...) pg_attribute_noreturn(); void pllua_warning(lua_State *L, const char *msg, ...) pg_attribute_printf(2, 3); void pllua_error_callback(void *arg); int pllua_error_callback_location(lua_State *L); /* error.c */ int pllua_open_error(lua_State *L); ErrorData *pllua_make_recursive_error(void); void pllua_error_cleanup(pllua_interpreter *interp, pllua_activation_record *act); int pllua_panic(lua_State *L); int pllua_newerror(lua_State *L); int pllua_register_error(lua_State *L); void pllua_poperror(lua_State *L); void pllua_rethrow_from_lua(lua_State *L, int rc); /* These are DLLEXPORT so that transform modules can get at them */ PGDLLEXPORT void pllua_rethrow_from_pg(lua_State *L, MemoryContext mcxt); PGDLLEXPORT int pllua_pcall_nothrow(lua_State *L, int nargs, int nresults, int msgh); PGDLLEXPORT int pllua_cpcall(lua_State *L, lua_CFunction func, void* arg); PGDLLEXPORT void pllua_pcall(lua_State *L, int nargs, int nresults, int msgh); PGDLLEXPORT int pllua_trampoline(lua_State *L); void pllua_initial_protected_call(pllua_interpreter *interp, lua_CFunction func, pllua_activation_record *arg); int pllua_t_assert(lua_State *L); int pllua_t_error(lua_State *L); int pllua_t_pcall(lua_State *L); int pllua_t_xpcall(lua_State *L); int pllua_t_lpcall(lua_State *L); int pllua_t_lxpcall(lua_State *L); /* exec.c */ int pllua_resume_function(lua_State *L); int pllua_call_function(lua_State *L); int pllua_call_trigger(lua_State *L); int pllua_call_event_trigger(lua_State *L); int pllua_call_inline(lua_State *L); int pllua_validate(lua_State *L); /* jsonb.c */ int pllua_open_jsonb(lua_State *L); /* numeric.c */ int pllua_open_numeric(lua_State *L); /* objects.c */ int pllua_open_funcmgr(lua_State *L); /* These are DLLEXPORT so that transform modules can get at them */ PGDLLEXPORT bool pllua_is_container(lua_State *L, int nd); PGDLLEXPORT bool pllua_pairs_start(lua_State *L, int nd, bool noerror); PGDLLEXPORT int pllua_pairs_next(lua_State *L); bool pllua_isobject(lua_State *L, int nd, char *objtype); void pllua_newmetatable(lua_State *L, char *objtype, luaL_Reg *mt); void pllua_new_weak_table(lua_State *L, const char *mode, const char *name); MemoryContext pllua_get_memory_cxt(lua_State *L); void **pllua_newrefobject(lua_State *L, char *objtype, void *value, bool uservalue); void **pllua_torefobject(lua_State *L, int nd, char *objtype); void *pllua_newobject(lua_State *L, char *objtype, size_t sz, bool uservalue); void *pllua_toobject(lua_State *L, int nd, char *objtype); void pllua_type_error(lua_State *L, char *expected); void **pllua_checkrefobject(lua_State *L, int nd, char *objtype); void *pllua_checkobject(lua_State *L, int nd, char *objtype); MemoryContext pllua_newmemcontext(lua_State *L, const char *name, Size minsz, Size initsz, Size maxsz); void pllua_set_user_field(lua_State *L, int nd, const char *field); int pllua_get_user_field(lua_State *L, int nd, const char *field); int pllua_get_user_subfield(lua_State *L, int nd, const char *field, const char *subfield); int pllua_newactivation(lua_State *L); int pllua_setactivation(lua_State *L); void pllua_getactivation(lua_State *L, pllua_func_activation *act); int pllua_activation_getfunc(lua_State *L); int pllua_get_cur_act(lua_State *L); FmgrInfo *pllua_get_cur_flinfo(lua_State *L); bool pllua_get_cur_act_readonly(lua_State *L); int pllua_freeactivation(lua_State *L); int pllua_resetactivation(lua_State *L); lua_State *pllua_activate_thread(lua_State *L, int nd, ExprContext *econtext); void pllua_deactivate_thread(lua_State *L, pllua_func_activation *act, ExprContext *econtext); void pllua_pgfunc_new(lua_State *L); FmgrInfo *pllua_pgfunc_init(lua_State *L, int nd, Oid fnoid, int nargs, Oid *argtypes, Oid rettype); void pllua_pgfunc_table_new(lua_State *L); /* paths.c */ int pllua_open_paths(lua_State *L); /* preload.c */ int pllua_preload_compat(lua_State *L); /* spi.c */ int pllua_open_spi(lua_State *L); int pllua_spi_convert_args(lua_State *L); int pllua_spi_prepare_result(lua_State *L); int pllua_cursor_cleanup_portal(lua_State *L); int pllua_spi_newcursor(lua_State *L); int pllua_cursor_name(lua_State *L); /* time.c */ int pllua_open_time(lua_State *L); /* trigger.c */ int pllua_open_trigger(lua_State *L); struct TriggerData; struct EventTriggerData; void pllua_trigger_begin(lua_State *L, struct TriggerData *td); void pllua_trigger_end(lua_State *L, int nd); int pllua_push_trigger_args(lua_State *L, struct TriggerData *td); Datum pllua_return_trigger_result(lua_State *L, int nret, int nd); void pllua_evtrigger_begin(lua_State *L, struct EventTriggerData *td); void pllua_evtrigger_end(lua_State *L, int nd); /* trusted.c */ int pllua_open_trusted(lua_State *L); int pllua_open_trusted_late(lua_State *L); #endif pllua-ng-REL_2_0_4/src/pllua_luajit.h000066400000000000000000000105741347047754200175510ustar00rootroot00000000000000/* pllua_luajit.h */ #ifndef PLLUA_LUAJIT_H #define PLLUA_LUAJIT_H #include #include "pllua_luaver.h" /* * Parts of the lua 5.1 compatibility cruft here is derived from the * lua-compat-5.3 project, which is licensed under the same terms as this * project and carries the following copyright: * * Copyright (c) 2015 Kepler Project. */ extern const char *luaL_tolstring(lua_State *L, int nd, size_t *len); static inline int lua_absindex(lua_State *L, int nd) { return (nd < 0 && nd > LUA_REGISTRYINDEX) ? nd + lua_gettop(L) + 1 : nd; } static inline int lua_rawgetp(lua_State *L, int nd, void *p) { int tnd = lua_absindex(L, nd); lua_pushlightuserdata(L, p); lua_rawget(L, tnd); return lua_type(L, -1); } static inline void lua_rawsetp(lua_State *L, int nd, void *p) { int tnd = lua_absindex(L, nd); lua_pushlightuserdata(L, p); lua_insert(L, -2); lua_rawset(L, tnd); } static inline int lua_geti(lua_State *L, int nd, lua_Integer i) { int tnd = lua_absindex(L, nd); lua_pushinteger(L, i); lua_gettable(L, tnd); return lua_type(L, -1); } static inline void lua_seti(lua_State *L, int nd, lua_Integer i) { int tnd = lua_absindex(L, nd); lua_pushinteger(L, i); lua_insert(L, -2); lua_settable(L, tnd); } #define lua_rawgeti(L_,nd_,i_) ((lua_rawgeti)(L_,nd_,i_),lua_type(L_, -1)) #define lua_rawget(L_,nd_) ((lua_rawget)(L_,nd_),lua_type(L_, -1)) #define lua_getfield(L_,nd_,i_) ((lua_getfield)(L_,nd_,i_),lua_type(L_, -1)) #define lua_gettable(L_,nd_) ((lua_gettable)(L_,nd_),lua_type(L_, -1)) #define luaL_getmetafield(L_,nd_,f_) ((luaL_getmetafield)(L_,nd_,f_) ? lua_type(L_, -1) : LUA_TNIL) /* luajit 2.1's version of this one is ok. */ #if LUAJIT_VERSION_NUM < 20100 static inline lua_Number lua_tonumberx(lua_State *L, int i, int *isnum) { lua_Number n = lua_tonumber(L, i); if (isnum != NULL) { *isnum = (n != 0 || lua_isnumber(L, i)); } return n; } #endif /* * but for these we need to kill luajit's version and use ours: we depend on * isinteger/tointegerx/checkinteger accepting only actually integral values, * not rounded/truncated floats. */ #if LUAJIT_VERSION_NUM >= 20100 #define lua_isinteger pllua_isinteger #define lua_tointegerx pllua_tointegerx #endif /* no luajit version of these is usable: */ #define luaL_checkinteger pllua_checkinteger #define luaL_optinteger pllua_optinteger static inline bool lua_isinteger(lua_State *L, int nd) { if (lua_type(L, nd) == LUA_TNUMBER) { lua_Number n = lua_tonumber(L, nd); lua_Integer i = lua_tointeger(L, nd); if (i == n) return 1; } return 0; } static inline lua_Integer lua_tointegerx(lua_State *L, int i, int *isint) { lua_Integer n = lua_tointeger(L, i); if (isint != NULL) { int isnum = 0; /* be careful, it might not be a number at all */ if (n == lua_tonumberx(L, i, &isnum)) *isint = isnum; else *isint = 0; } return n; } static inline lua_Integer luaL_checkinteger(lua_State *L, int i) { int isint = 0; lua_Integer res = lua_tointegerx(L, i, &isint); if (!isint) luaL_argerror(L, i, "integer"); return res; } static inline lua_Integer luaL_optinteger(lua_State *L, int i, lua_Integer def) { if (lua_isnoneornil(L, i)) return def; else return luaL_checkinteger(L, i); } /* * Miscellaneous functions */ #define lua_getuservalue(L_,nd_) (lua_getfenv(L_,nd_), lua_type(L_,-1)) #define lua_setuservalue(L_,nd_) lua_setfenv(L_,nd_) #define lua_pushglobaltable(L_) lua_pushvalue((L_), LUA_GLOBALSINDEX) #if LUAJIT_VERSION_NUM < 20100 #define luaL_setfuncs(L_,f_,u_) pllua_setfuncs(L_,f_,u_) void pllua_setfuncs(lua_State *L, const luaL_Reg *reg, int nup); #endif #define luaL_getsubtable(L_,i_,n_) pllua_getsubtable(L_,i_,n_) int pllua_getsubtable(lua_State *L, int i, const char *name); #define luaL_requiref(L_,m_,f_,g_) pllua_requiref(L_,m_,f_,g_) void pllua_requiref(lua_State *L, const char *modname, lua_CFunction openf, int glb); #ifndef luaL_newlibtable #define luaL_newlibtable(L, l) \ (lua_createtable((L), 0, sizeof((l))/sizeof(*(l))-1)) #define luaL_newlib(L, l) \ (luaL_newlibtable((L), (l)), luaL_register((L), NULL, (l))) #endif #define LUA_OK 0 /* * these should be the largest and smallest integers representable exactly in a * Lua number; must be compile-time constant */ #define LUA_MAXINTEGER (INT64CONST(9007199254740991)) #define LUA_MININTEGER (-INT64CONST(9007199254740991)) #if LUAJIT_VERSION_NUM > 0 && !defined(NO_LUAJIT) #define LUA_TCDATA 10 #endif #endif pllua-ng-REL_2_0_4/src/pllua_luaver.h000066400000000000000000000034531347047754200175550ustar00rootroot00000000000000/* pllua_luaver.h */ #ifndef PLLUA_LUAVER_H #define PLLUA_LUAVER_H #ifndef LUAJIT_VERSION_NUM #define LUAJIT_VERSION_NUM 0 #endif /* * We can only use 5.1 environments in a compatible way to 5.2+ uservalues if * we forcibly set an environment on every userdata we create. */ #if LUA_VERSION_NUM == 501 #define MANDATORY_USERVALUE 1 #else #define MANDATORY_USERVALUE 0 #endif /* * pllua_pushcfunction must absolutely not throw error. * * In Lua 5.4+ (and also 5.3.5+, but we don't check that) it's safe to just use * lua_pushcfunction, but in 5.1 (by design) and 5.3.[34] (due to bugs) we must * instead arrange to store all the function values in the registry, and use a * rawget to fetch them. */ #if LUA_VERSION_NUM > 503 #define pllua_pushcfunction(L_,f_) lua_pushcfunction(L_,f_) #else #define pllua_pushcfunction(L_,f_) do { \ int rc PG_USED_FOR_ASSERTS_ONLY; \ rc = lua_rawgetp((L_),LUA_REGISTRYINDEX,(f_)); \ Assert(rc==LUA_TFUNCTION); } while(0) #endif /* * used to label functions that need registration despite not being * directly passed to pcall or cpcall; the first arg is unused */ #define pllua_register_cfunc(L_, f_) (f_) /* * Function to use to set an environment on a code chunk. */ #if LUA_VERSION_NUM == 501 #define pllua_set_environment(L_,i_) lua_setfenv(L, i_) #else #define pllua_set_environment(L_,i_) lua_setupvalue(L_, i_, 1) #endif /* * Handle API differences for lua_resume by emulating the 5.4 API on earlier * versions. */ #if LUA_VERSION_NUM < 504 static inline int pllua_resume(lua_State *L, lua_State *from, int nargs, int *nret) { #if LUA_VERSION_NUM == 501 int rc = (lua_resume)(L, nargs); #else int rc = (lua_resume)(L, from, nargs); #endif *nret = lua_gettop(L); return rc; } #define lua_resume(L_,f_,a_,r_) (pllua_resume(L_,f_,a_,r_)) #endif #endif pllua-ng-REL_2_0_4/src/pllua_pgver.h000066400000000000000000000066251347047754200174060ustar00rootroot00000000000000/* * pllua_pgver.h */ #ifndef PLLUA_PGVER_H #define PLLUA_PGVER_H /* PG version cruft */ /* * It sucks to depend on point release version, but some values we want to use * in #if directives are not compile-time constant in older point releases. * So for unfixed point releases, forcibly fix things here. * * Fortunately this matters only at compile time. */ #if (PG_VERSION_NUM >= 90500 && PG_VERSION_NUM < 90510) \ || (PG_VERSION_NUM >= 90600 && PG_VERSION_NUM < 90606) \ || (PG_VERSION_NUM >= 100000 && PG_VERSION_NUM < 100001) #undef INT64CONST #undef UINT64CONST #if defined(HAVE_LONG_INT_64) #define INT64CONST(x) (x##L) #define UINT64CONST(x) (x##UL) #elif defined(HAVE_LONG_LONG_INT_64) #define INT64CONST(x) (x##LL) #define UINT64CONST(x) (x##ULL) #else #error must have a working 64-bit integer datatype #endif #ifdef PG_INT64_MIN #undef PG_INT64_MIN #endif #ifdef PG_INT64_MAX #undef PG_INT64_MAX #endif #endif /* CONST_MAX_HACK */ #ifndef PG_INT64_MIN #define PG_INT64_MIN (-INT64CONST(0x7FFFFFFFFFFFFFFF) - 1) #endif #ifndef PG_INT64_MAX #define PG_INT64_MAX INT64CONST(0x7FFFFFFFFFFFFFFF) #endif /* RIP, oids. */ #if PG_VERSION_NUM >= 120000 #define TupleDescHasOids(tupdesc) (false) #define IsObjectIdAttributeNumber(a) (false) /* * since hasoid is always supposed to be false thanks to the above, any * references to the below macros should be unreachable */ #define HeapTupleHeaderGetOid(h) (AssertMacro(false), InvalidOid) #define HeapTupleHeaderSetOid(h,o) do { (void) (h); (void) (o); Assert(false); } while (0) #define HeapTupleSetOid(h,o) do { (void) (h); (void) (o); Assert(false); } while (0) #else #define TupleDescHasOids(tupdesc) ((tupdesc)->tdhasoid) #define IsObjectIdAttributeNumber(a) ((a) == ObjectIdAttributeNumber) #endif /* cope with variable-length fcinfo in pg12 */ #if PG_VERSION_NUM < 120000 #define LOCAL_FCINFO(name_,nargs_) \ FunctionCallInfoData name_##data; \ FunctionCallInfo name_ = &name_##data #define LFCI_ARG_VALUE(fci_,n_) ((fci_)->arg[n_]) #define LFCI_ARGISNULL(fci_,n_) ((fci_)->argnull[n_]) #else #define LFCI_ARG_VALUE(fci_,n_) ((fci_)->args[n_].value) #define LFCI_ARGISNULL(fci_,n_) ((fci_)->args[n_].isnull) #endif /* TupleDesc structure change */ #if PG_VERSION_NUM < 100000 #define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) #endif /* AllocSetContextCreate API changes */ #if PG_VERSION_NUM < 110000 #define AllocSetContextCreateInternal AllocSetContextCreate #elif PG_VERSION_NUM < 120000 #define AllocSetContextCreateInternal AllocSetContextCreateExtended #endif /* * Protect against backpatching or lack thereof. */ #ifndef ALLOCSET_DEFAULT_SIZES #define ALLOCSET_DEFAULT_SIZES \ ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE #endif #ifndef ALLOCSET_SMALL_SIZES #define ALLOCSET_SMALL_SIZES \ ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE #endif #ifndef ALLOCSET_START_SMALL_SIZES #define ALLOCSET_START_SMALL_SIZES \ ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE #endif /* We want a way to do noinline, but old PGs don't have it. */ #if defined(pg_noinline) #define pllua_noinline pg_noinline #elif (defined(__GNUC__) && __GNUC__ > 2) || defined(__SUNPRO_C) || defined(__IBMC__) #define pllua_noinline __attribute__((noinline)) /* msvc via declspec */ #elif defined(_MSC_VER) #define pllua_noinline __declspec(noinline) #else #define pllua_noinline #endif #endif /* PLLUA_PGVER_H */ pllua-ng-REL_2_0_4/src/preload.c000066400000000000000000000032231347047754200164760ustar00rootroot00000000000000/* preload.c */ /* * Preloading of lua modules built into the binary. */ #include "pllua.h" /* * Apple has to be different, of course. */ #ifdef __darwin__ #include #include #include static void pllua_darwin_get_chunk(lua_State *L, const char *name, const void **ptr, size_t *sz) { void *tptr; Dl_info info; if (!dladdr(&pllua_darwin_get_chunk, &info)) luaL_error(L, "dladdr failed: %s", dlerror()); tptr = getsectiondata(info.dli_fbase, "binary", name, sz); if (!tptr) luaL_error(L, "getsectiondata failed"); *ptr = tptr; return; } #define GETCHUNK(L_, chunk_,start_,sz_) \ pllua_darwin_get_chunk(L_, #chunk_, &(start_), &(sz_)) #else extern const char _binary_src_compat_luac_start[]; extern const char _binary_src_compat_luac_end[]; #define MKSYM(a_,b_,c_) a_##b_##c_ #define GETCHUNK(L_, chunk_,start_,sz_) \ do { (start_) = MKSYM(_binary_src_,chunk_,_start); \ (sz_) = MKSYM(_binary_src_,chunk_,_end) \ - MKSYM(_binary_src_,chunk_,_start); \ } while (0) #endif static void pllua_load_binary_chunk(lua_State *L, const char *chunkname, const char *start, size_t len) { int rc = luaL_loadbuffer(L, start, len, chunkname); if (rc) lua_error(L); } /* * Upvalue 1 is the metatable to use with the environment */ int pllua_preload_compat(lua_State *L) { const void *ptr; size_t sz; GETCHUNK(L, compat_luac, ptr, sz); pllua_load_binary_chunk(L, "compat.lua", ptr, sz); lua_newtable(L); lua_pushvalue(L, lua_upvalueindex(1)); lua_setmetatable(L, -2); pllua_set_environment(L, -2); lua_pushvalue(L, lua_upvalueindex(1)); lua_call(L, 1, 1); return 1; } pllua-ng-REL_2_0_4/src/spi.c000066400000000000000000001100731347047754200156450ustar00rootroot00000000000000/* spi.c */ #include "pllua.h" #include "access/htup_details.h" #if PG_VERSION_NUM >= 110000 #include "access/xact.h" #endif #include "commands/trigger.h" #include "catalog/pg_type.h" #include "executor/spi.h" #include "parser/analyze.h" #include "parser/parse_param.h" #if PG_VERSION_NUM >= 110000 #define PortalGetHeapMemory(portal) ((portal)->portalContext) #endif /* * plpgsql uses 10. We have a bit more overhead per queue fill since we * start/stop SPI and do a bunch of data copies, so a larger value seems good. * However, above 50 the returns are very small and the potential for memory * problems increases. * * The fetch count can be set per-statement. */ #define DEFAULT_FETCH_COUNT 50 typedef struct pllua_spi_statement { SPIPlanPtr plan; bool kept; bool cursor_plan; int fetch_count; /* only used for private cursors */ int nparams; int param_types_len; Oid *param_types; MemoryContext mcxt; } pllua_spi_statement; /* * This is an object not a refobject since it references no memory other than * the Portal, which has its own memory context already. * * But, we have to allow for the possibility that the portal will be ripped out * from under us, e.g. by transaction end, explicit CLOSE, or whatever. So we * use the same trick as for activations (with the slight variation that we use * a weak table for tracking, since unlike with activations we expect to shed * references to no-longer-needed cursors). * * We also track whether the cursor is considered "private" to us (meaning that * it hasn't been exposed anywhere that the user would see, e.g. we're keeping it * in a closure for a rows() iterator). Since the position of such a cursor isn't * visible to others, we can prefetch. */ typedef struct pllua_spi_cursor { Portal portal; /* or null if closed or dead */ MemoryContextCallback *cb; /* allocated in PortalContext */ lua_State *L; /* needed by callback */ int fetch_count; /* only used for private cursors */ bool is_ours; /* we created (and will close) it? */ bool is_private; /* nobody else should be touching it */ bool is_live; /* cleared by callback */ } pllua_spi_cursor; static pllua_spi_cursor *pllua_newcursor(lua_State *L); static void pllua_cursor_setportal(lua_State *L, int nd, pllua_spi_cursor *curs, Portal portal, bool is_ours); /* * Pushes up to four entries on the stack - beware! */ static void pllua_spi_alloc_argspace(lua_State *L, int nargs, Datum **values, bool **isnull, Oid **argtypes, pllua_typeinfo ***typeinfos) { if (values) *values = lua_newuserdata(L, nargs * sizeof(Datum)); if (isnull) *isnull = lua_newuserdata(L, nargs * sizeof(bool)); if (argtypes) *argtypes = lua_newuserdata(L, nargs * sizeof(Oid)); if (typeinfos) *typeinfos = lua_newuserdata(L, nargs * sizeof(pllua_typeinfo *)); } static bool pllua_spi_enter(lua_State *L) { bool readonly = pllua_get_cur_act_readonly(L); ASSERT_PG_CONTEXT; SPI_connect(); #if PG_VERSION_NUM >= 100000 { pllua_activation_record *pact = &(pllua_getinterpreter(L)->cur_activation); if (pact->fcinfo && CALLED_AS_TRIGGER(pact->fcinfo)) SPI_register_trigger_data((TriggerData *) pact->fcinfo->context); } #endif return readonly; } static int pllua_spi_is_readonly(lua_State *L) { bool readonly = pllua_get_cur_act_readonly(L); lua_pushboolean(L, readonly); return 1; } static void pllua_spi_exit(lua_State *L) { SPI_finish(); } /* * This creates the result but does not copy the data into the proper memory * context; see pllua_spi_save_result for that. * * args: light[tuptab] nrows [table baseidx] * returns: typeinfo table baseidx */ int pllua_spi_prepare_result(lua_State *L) { SPITupleTable *tuptab = lua_touserdata(L, 1); lua_Integer nrows = lua_tointeger(L, 2); TupleDesc tupdesc = tuptab->tupdesc; lua_Integer base = 1; lua_Integer i; if (!lua_istable(L, 3)) { lua_settop(L, 3); lua_createtable(L, nrows, 0); lua_replace(L, 3); } else base = 1 + lua_tointeger(L, 4); if (tupdesc->tdtypeid == RECORDOID && tupdesc->tdtypmod < 0) pllua_newtypeinfo_raw(L, tupdesc->tdtypeid, tupdesc->tdtypmod, tupdesc); else { lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) tupdesc->tdtypeid); lua_pushinteger(L, (lua_Integer) tupdesc->tdtypmod); lua_call(L, 2, 1); } for (i = 0; i < nrows; ++i) { HeapTuple htup = tuptab->vals[i]; HeapTupleHeader h = htup->t_data; pllua_datum *d; /* htup might be in on-disk format or datum format. Force datum format. */ HeapTupleHeaderSetDatumLength(h, htup->t_len); HeapTupleHeaderSetTypeId(h, tupdesc->tdtypeid); HeapTupleHeaderSetTypMod(h, tupdesc->tdtypmod); d = pllua_newdatum(L, -1, (Datum)0); /* we intentionally do not detoast anything here, see savedatum */ d->value = PointerGetDatum(h); lua_rawseti(L, 3, i+base); } lua_pushvalue(L, 3); lua_pushinteger(L, base+nrows-1); lua_setfield(L, -2, "n"); lua_pushinteger(L, base); return 3; } /* * stack: ... typeinfo table base */ static void pllua_spi_save_result(lua_State *L, lua_Integer nrows) { MemoryContext oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); pllua_typeinfo *t = *(void **)lua_touserdata(L, -3); lua_Integer base = lua_tointeger(L, -1); lua_Integer i; /* we rely on the fact that rawgeti won't throw */ for (i = 0; i < nrows; ++i) { pllua_datum *d; lua_rawgeti(L, -2, i+base); d = lua_touserdata(L, -1); pllua_savedatum(L, d, t); lua_pop(L,1); } MemoryContextSwitchTo(oldcontext); } static int pllua_cursor_options(lua_State *L, int nd, int *fetch_count) { int n = 0; int isint = 0; int flag = 0; if (lua_isnoneornil(L, nd)) return 0; luaL_checktype(L, nd, LUA_TTABLE); lua_getfield(L, nd, "scroll"); /* * support scroll = false as if it were no_scroll = true, because not doing * so is too confusing. But note that specifying neither has a different * effect. */ if (!lua_isnil(L, -1)) { if (lua_toboolean(L, -1)) flag |= CURSOR_OPT_SCROLL; else flag |= CURSOR_OPT_NO_SCROLL; } lua_pop(L, 1); lua_getfield(L, nd, "no_scroll"); flag |= (lua_toboolean(L, -1)) ? CURSOR_OPT_NO_SCROLL : 0; lua_pop(L, 1); lua_getfield(L, nd, "hold"); flag |= (lua_toboolean(L, -1)) ? CURSOR_OPT_HOLD : 0; lua_pop(L, 1); lua_getfield(L, nd, "fast_start"); flag |= (lua_toboolean(L, -1)) ? CURSOR_OPT_FAST_PLAN : 0; lua_pop(L, 1); lua_getfield(L, nd, "custom_plan"); flag |= (lua_toboolean(L, -1)) ? CURSOR_OPT_CUSTOM_PLAN : 0; lua_pop(L, 1); lua_getfield(L, nd, "generic_plan"); flag |= (lua_toboolean(L, -1)) ? CURSOR_OPT_GENERIC_PLAN : 0; lua_pop(L, 1); lua_getfield(L, nd, "fetch_count"); n = lua_tointegerx(L, -1, &isint); if (isint && n >= 1 && n < 10000000) *fetch_count = n; lua_pop(L, 1); return flag; } static int pllua_spi_prepare_recursion = -1; static post_parse_analyze_hook_type pllua_spi_prev_parse_hook = NULL; static void pllua_spi_prepare_checkparam_hook(ParseState *pstate, Query *query) { if (pllua_spi_prepare_recursion == 1) check_variable_parameters(pstate, query); if (pllua_spi_prev_parse_hook) pllua_spi_prev_parse_hook(pstate, query); } static void pllua_spi_prepare_parser_setup_hook(ParseState *pstate, void *arg) { pllua_spi_statement *stmt = arg; parse_variable_parameters(pstate, &stmt->param_types, &stmt->param_types_len); } static pllua_spi_statement *pllua_spi_make_statement(lua_State *L, const char *str, int nargs_known, Oid *argtypes, int opts) { MemoryContext mcxt = AllocSetContextCreate(CurrentMemoryContext, "PL/Lua SPI statement object", ALLOCSET_SMALL_SIZES); MemoryContext oldcontext = MemoryContextSwitchTo(mcxt); pllua_spi_statement *stmt = palloc0(sizeof(pllua_spi_statement)); int i; ASSERT_PG_CONTEXT; stmt->mcxt = mcxt; stmt->nparams = 0; stmt->fetch_count = 0; if (nargs_known > 0) { stmt->param_types_len = nargs_known; stmt->param_types = palloc(nargs_known * sizeof(Oid)); memcpy(stmt->param_types, argtypes, nargs_known * sizeof(Oid)); } else { /* we have to preallocate this to get it in the right context */ stmt->param_types_len = 16; /* wholly arbitrary */ stmt->param_types = palloc0(16 * sizeof(Oid)); } /* * GAH. To do parameter type checking properly, we have to arrange to * enable our own global post-parse hook transiently. */ if (pllua_spi_prepare_recursion != 0) elog(ERROR, "pllua: recursive entry into prepare!"); /* paranoia */ PG_TRY(); { ++pllua_spi_prepare_recursion; stmt->plan = SPI_prepare_params(str, pllua_spi_prepare_parser_setup_hook, stmt, opts); --pllua_spi_prepare_recursion; } PG_CATCH(); { --pllua_spi_prepare_recursion; PG_RE_THROW(); } PG_END_TRY(); if (!stmt->plan) elog(ERROR, "spi error: %s", SPI_result_code_string(SPI_result)); for (i = stmt->param_types_len; i > 0; --i) { if (stmt->param_types[i - 1] != 0) { stmt->nparams = i; break; } } stmt->cursor_plan = SPI_is_cursor_plan(stmt->plan); MemoryContextSwitchTo(oldcontext); return stmt; } /* * prepare(cmd,[{argtypes}, [{flag=true,...,fetch_count=n}]) * * argtypes are given as text or typeinfo. * flags: "scroll","no_scroll","fast_start","custom_plan","generic_plan" */ static int pllua_spi_prepare(lua_State *L) { const char *str = lua_tostring(L, 1); int fetch_count = 0; int opts = pllua_cursor_options(L, 3, &fetch_count); void **volatile p; int i; int nargs = 0; Oid d_argtypes[100]; Oid *argtypes = d_argtypes; if (pllua_ending) luaL_error(L, "cannot call SPI during shutdown"); if (nargs > 99) pllua_spi_alloc_argspace(L, nargs, NULL, NULL, &argtypes, NULL); lua_settop(L, 2); /* make the plan object - index 3 */ p = pllua_newrefobject(L, PLLUA_SPI_STMT_OBJECT, NULL, true); nargs = 0; if (!lua_isnoneornil(L, 2)) { for(i = 1; ; ++i) { pllua_typeinfo *t; if (lua_geti(L, 2, i) == LUA_TNIL) break; if (lua_isstring(L, -1)) { lua_pushcfunction(L, pllua_typeinfo_parsetype); lua_pushvalue(L, -2); lua_call(L, 1, 1); if (lua_isnil(L, -1)) luaL_error(L, "unknown type '%s'", lua_tostring(L, -2)); lua_remove(L, -2); } t = pllua_totypeinfo(L, -1); if (!t) luaL_error(L, "unexpected object type in argtypes list"); argtypes[nargs++] = t->typeoid; } } PLLUA_TRY(); { pllua_spi_statement *stmt; pllua_spi_enter(L); stmt = pllua_spi_make_statement(L, str, nargs, argtypes, opts); /* reparent everything */ SPI_keepplan(stmt->plan); stmt->kept = true; stmt->fetch_count = fetch_count; MemoryContextSetParent(stmt->mcxt, pllua_get_memory_cxt(L)); *p = stmt; pllua_spi_exit(L); } PLLUA_CATCH_RETHROW(); lua_getuservalue(L, 3); { pllua_spi_statement *stmt = *p; for(i = 0; i < stmt->nparams; ++i) { pllua_typeinfo *t; /* unused params will have InvalidOid here */ if (OidIsValid(stmt->param_types[i])) { lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) stmt->param_types[i]); lua_call(L, 1, 1); t = pllua_totypeinfo(L, -1); if (!t) luaL_error(L, "unexpected type in paramtypes list: %d", (lua_Integer) stmt->param_types[i]); lua_rawseti(L, -2, i+1); } } } lua_pushvalue(L, 3); return 1; } /* * args: light[values] light[isnull] light[argtypes] argtable arg... * * argtypes are as determined by the parser, may not match the actual type of * arg. */ int pllua_spi_convert_args(lua_State *L) { Datum *values = lua_touserdata(L, 1); bool *isnull = lua_touserdata(L, 2); Oid *argtypes = lua_touserdata(L, 3); int nargs = lua_gettop(L) - 4; int argbase = 5; int i; for (i = 0; i < nargs; ++i) { if (!lua_isnil(L, argbase+i) && OidIsValid(argtypes[i])) { pllua_typeinfo *dt; pllua_datum *d; lua_pushvalue(L, argbase+i); d = pllua_toanydatum(L, -1, &dt); /* not already an unexploded datum of correct type? */ if (!d || dt->typeoid != argtypes[i] || dt->obsolete || dt->modified || d->modified) { if (d) lua_pop(L, 1); /* discard typeinfo */ lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) argtypes[i]); lua_call(L, 1, 1); lua_insert(L, -2); lua_call(L, 1, 1); d = pllua_toanydatum(L, -1, &dt); } /* it better be the right type now */ if (!d || dt->typeoid != argtypes[i]) luaL_error(L, "inconsistent value type in SPI parameter list"); lua_pop(L, 1); /* discard typeinfo */ /* * holding a reference here means that d remains valid even though * it's no longer on the stack */ lua_rawseti(L, 4, i+1); values[i] = d->value; isnull[i] = false; } else { values[i] = (Datum)0; isnull[i] = true; } } return 0; } static ParamListInfo pllua_spi_init_paramlist(int nargs, Datum *values, bool *isnull, Oid *types) { ParamListInfo paramLI; int i; ASSERT_PG_CONTEXT; paramLI = (ParamListInfo) palloc0(offsetof(ParamListInfoData, params) + nargs * sizeof(ParamExternData)); /* we have static list of params, so no hooks needed */ paramLI->paramFetch = NULL; paramLI->paramFetchArg = NULL; #if PG_VERSION_NUM >= 110000 paramLI->paramCompile = NULL; paramLI->paramCompileArg = NULL; #endif paramLI->parserSetup = NULL; paramLI->parserSetupArg = NULL; paramLI->numParams = nargs; #if PG_VERSION_NUM >= 90600 && PG_VERSION_NUM < 110000 paramLI->paramMask = NULL; #endif for (i = 0; i < nargs; i++) { ParamExternData *prm = ¶mLI->params[i]; prm->value = values[i]; prm->isnull = isnull[i]; prm->pflags = PARAM_FLAG_CONST; prm->ptype = types[i]; } return paramLI; } /* * spi.execute_count(cmd, count, arg...) returns {rows...} * also stmt:execute_count(count, arg...) * */ static int pllua_spi_execute_count(lua_State *L) { void **p = pllua_torefobject(L, 1, PLLUA_SPI_STMT_OBJECT); const char *str = lua_tostring(L, 1); int nargs = lua_gettop(L) - 2; int argbase = 3; Datum d_values[100]; bool d_isnull[100]; Oid d_argtypes[100]; Datum *values = d_values; bool *isnull = d_isnull; Oid *argtypes = d_argtypes; long count = luaL_optinteger(L, 2, 0); volatile lua_Integer nrows = -1; int i; if (!str && !p) luaL_error(L, "incorrect argument type for execute, string or statement expected"); if (count == 0) count = Min(LUA_MAXINTEGER-1, LONG_MAX-1); else if (count < 0 || count > LUA_MAXINTEGER-1 || count > LONG_MAX-1) luaL_error(L, "requested number of rows is out of range"); if (pllua_ending) luaL_error(L, "cannot call SPI during shutdown"); if (nargs > 99) pllua_spi_alloc_argspace(L, nargs, &values, &isnull, &argtypes, NULL); /* check encoding of query string */ if (str) pllua_verify_encoding(L, str); /* * If we don't have a prepared stmt, then extract argtypes where we have * definite info (i.e. only when the parameter is actually a datum). */ if (!p) { for (i = 0; i < nargs; ++i) { argtypes[i] = 0; if (lua_type(L, argbase+i) == LUA_TUSERDATA) { pllua_typeinfo *dt; pllua_datum *d = pllua_toanydatum(L, argbase+i, &dt); if (d) { argtypes[i] = dt->typeoid; lua_pop(L, 1); } } } } /* we're going to re-push all the args, better have space */ luaL_checkstack(L, 40+nargs, NULL); lua_createtable(L, nargs, 0); /* table to hold refs to arg datums */ PLLUA_TRY(); { bool readonly = pllua_spi_enter(L); pllua_spi_statement *stmt = p ? *p : NULL; ParamListInfo paramLI = NULL; int rc; if (!stmt) stmt = pllua_spi_make_statement(L, str, nargs, argtypes, 0); if (stmt->nparams != nargs) elog(ERROR, "pllua: wrong number of arguments to SPI query: expected %d got %d", stmt->nparams, nargs); pllua_pushcfunction(L, pllua_spi_convert_args); lua_pushlightuserdata(L, values); lua_pushlightuserdata(L, isnull); lua_pushlightuserdata(L, stmt->param_types); lua_pushvalue(L, -5); for (i = 0; i < nargs; ++i) { lua_pushvalue(L, argbase+i); } pllua_pcall(L, 4+nargs, 0, 0); if (nargs > 0) paramLI = pllua_spi_init_paramlist(nargs, values, isnull, stmt->param_types); rc = SPI_execute_plan_with_paramlist(stmt->plan, paramLI, readonly, count); if (rc >= 0) { nrows = SPI_processed; if (SPI_tuptable) { /* * Blessing the tupdesc of the result turns out to be a bad * idea if we can avoid it; in a long-running backend the * tupdescs can really pile up. */ pllua_pushcfunction(L, pllua_spi_prepare_result); lua_pushlightuserdata(L, SPI_tuptable); lua_pushinteger(L, nrows); pllua_pcall(L, 2, 3, 0); pllua_spi_save_result(L, nrows); lua_pop(L, 1); } else lua_pushinteger(L, nrows); } else elog(ERROR, "spi error: %s", SPI_result_code_string(rc)); /* * If we made our own statement, we didn't save it so it goes away here */ pllua_spi_exit(L); } PLLUA_CATCH_RETHROW(); return 1; } /* * spi.execute(cmd, arg...) returns {rows...} * also stmt:execute(arg...) * * simple shim to insert the count arg */ static int pllua_spi_execute(lua_State *L) { luaL_checkany(L, 1); lua_pushcfunction(L, pllua_spi_execute_count); lua_insert(L, 1); lua_pushnil(L); lua_insert(L, 3); lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); return lua_gettop(L); } /* * c:open(cmd, arg...) * c:open(stmt, arg...) * */ static int pllua_spi_cursor_open(lua_State *L) { pllua_spi_cursor *curs = pllua_checkobject(L, 1, PLLUA_SPI_CURSOR_OBJECT); void **p = pllua_torefobject(L, 2, PLLUA_SPI_STMT_OBJECT); pllua_spi_statement *stmt = p ? *p : NULL; const char *str = lua_tostring(L, 2); const char *name = NULL; int nargs = lua_gettop(L) - 2; int argbase = 3; Datum d_values[100]; bool d_isnull[100]; Oid d_argtypes[100]; Datum *values = d_values; bool *isnull = d_isnull; Oid *argtypes = d_argtypes; volatile Portal portal; int i; if (!str && !p) luaL_error(L, "incorrect argument type for cursor open, string or statement expected"); if (curs->portal) luaL_error(L, "cursor is already open"); if (pllua_ending) luaL_error(L, "cannot call SPI during shutdown"); if (stmt && !stmt->cursor_plan) luaL_error(L, "invalid statement for cursor"); if (nargs > 99) pllua_spi_alloc_argspace(L, nargs, &values, &isnull, &argtypes, NULL); /* check encoding of query string */ if (str) pllua_verify_encoding(L, str); lua_getuservalue(L, 1); lua_getfield(L, -1, "name"); name = lua_tostring(L, -1); lua_pop(L, 1); /* * If we don't have a prepared stmt, then extract argtypes where we have * definite info (i.e. only when the parameter is actually a datum). */ if (!stmt) { for (i = 0; i < nargs; ++i) { argtypes[i] = 0; if (lua_type(L, argbase+i) == LUA_TUSERDATA) { pllua_typeinfo *dt; pllua_datum *d = pllua_toanydatum(L, argbase+i, &dt); if (d) { argtypes[i] = dt->typeoid; lua_pop(L, 1); } } } } /* we're going to re-push all the args, better have space */ luaL_checkstack(L, 40+nargs, NULL); lua_createtable(L, nargs, 0); PLLUA_TRY(); { bool readonly = pllua_spi_enter(L); ParamListInfo paramLI = NULL; if (!stmt) { stmt = pllua_spi_make_statement(L, str, nargs, argtypes, 0); if (!stmt->cursor_plan) elog(ERROR, "pllua: invalid query for cursor"); } if (stmt->nparams != nargs) elog(ERROR, "pllua: wrong number of arguments to SPI query: expected %d got %d", stmt->nparams, nargs); pllua_pushcfunction(L, pllua_spi_convert_args); lua_pushlightuserdata(L, values); lua_pushlightuserdata(L, isnull); lua_pushlightuserdata(L, stmt->param_types); lua_pushvalue(L, -5); for (i = 0; i < nargs; ++i) { lua_pushvalue(L, argbase+i); } pllua_pcall(L, 4+nargs, 0, 0); if (nargs > 0) paramLI = pllua_spi_init_paramlist(nargs, values, isnull, stmt->param_types); portal = SPI_cursor_open_with_paramlist(name, stmt->plan, paramLI, readonly); /* * If we made our own statement, we didn't save it so it goes away here * The portal does _not_ go away - it's not tied to SPI. */ pllua_spi_exit(L); } PLLUA_CATCH_RETHROW(); /* * Treat the new cursor as ours until told otherwise, but not private * (caller does that if appropriate) */ pllua_cursor_setportal(L, 1, curs, portal, true); lua_pushvalue(L, 1); return 1; } /* * s:getcursor(args) returns cursor * * Doesn't allow setting the name. * * = return spi.newcursor():open(self,args) */ static int pllua_spi_stmt_getcursor(lua_State *L) { pllua_newcursor(L); lua_insert(L, 1); lua_pushcfunction(L, pllua_spi_cursor_open); lua_insert(L, 1); lua_call(L, lua_gettop(L) - 1, 1); return 1; } /* * c:fetch(n [,dir]) -- returns rows * * dir = 'forward', 'backward', 'absolute', 'relative' */ static FetchDirection pllua_spi_cursor_direction(lua_State *L, int nd) { const char *str = luaL_optstring(L, nd, "forward"); switch (*str) { case 'f': if (strcmp(str, "forward") == 0) return FETCH_FORWARD; else break; case 'b': if (strcmp(str, "backward") == 0) return FETCH_BACKWARD; else break; case 'a': if (strcmp(str, "absolute") == 0) return FETCH_ABSOLUTE; else break; case 'r': if (strcmp(str, "relative") == 0) return FETCH_RELATIVE; else break; case 'p': if (strcmp(str, "prior") == 0) return FETCH_BACKWARD; else break; case 'n': if (strcmp(str, "next") == 0) return FETCH_FORWARD; else break; } return luaL_error(L, "unknown fetch direction '%s'", str); } static int pllua_spi_cursor_fetch(lua_State *L) { pllua_spi_cursor *curs = pllua_checkobject(L, 1, PLLUA_SPI_CURSOR_OBJECT); int64 count = luaL_optinteger(L, 2, 1); FetchDirection dir = pllua_spi_cursor_direction(L, 3); if (pllua_ending) luaL_error(L, "cannot call SPI during shutdown"); if (!curs->portal || !curs->is_live) luaL_error(L, "attempting to fetch from a closed cursor"); PLLUA_TRY(); { int64 nrows; pllua_spi_enter(L); SPI_scroll_cursor_fetch(curs->portal, dir, count); nrows = SPI_processed; if (SPI_tuptable) { pllua_pushcfunction(L, pllua_spi_prepare_result); lua_pushlightuserdata(L, SPI_tuptable); lua_pushinteger(L, nrows); pllua_pcall(L, 2, 3, 0); pllua_spi_save_result(L, nrows); lua_pop(L, 1); } else lua_pushinteger(L, nrows); pllua_spi_exit(L); } PLLUA_CATCH_RETHROW(); return 1; } static int pllua_spi_cursor_move(lua_State *L) { pllua_spi_cursor *curs = pllua_checkobject(L, 1, PLLUA_SPI_CURSOR_OBJECT); int64 count = luaL_optinteger(L, 2, 1); FetchDirection dir = pllua_spi_cursor_direction(L, 3); if (pllua_ending) luaL_error(L, "cannot call SPI during shutdown"); if (!curs->portal || !curs->is_live) luaL_error(L, "attempting to fetch from a closed cursor"); PLLUA_TRY(); { int64 nrows; pllua_spi_enter(L); SPI_scroll_cursor_move(curs->portal, dir, count); nrows = SPI_processed; lua_pushinteger(L, nrows); pllua_spi_exit(L); } PLLUA_CATCH_RETHROW(); return 1; } static int pllua_stmt_cursor_ok(lua_State *L) { pllua_spi_statement *stmt = *pllua_checkrefobject(L, 1, PLLUA_SPI_STMT_OBJECT); lua_pushboolean(L, stmt->cursor_plan); return 1; } static int pllua_stmt_numargs(lua_State *L) { pllua_spi_statement *stmt = *pllua_checkrefobject(L, 1, PLLUA_SPI_STMT_OBJECT); lua_pushinteger(L, stmt->nparams); return 1; } static int pllua_stmt_argtype(lua_State *L) { pllua_spi_statement *stmt = *pllua_checkrefobject(L, 1, PLLUA_SPI_STMT_OBJECT); int i = luaL_checkinteger(L, 2); int nparams = stmt->nparams; if (i < 1 || i > nparams) luaL_error(L, "parameter %d out of range", i); lua_getuservalue(L, 1); lua_rawgeti(L, -1, i); return 1; } static int pllua_stmt_gc(lua_State *L) { void **p = pllua_torefobject(L, 1, PLLUA_SPI_STMT_OBJECT); pllua_spi_statement *stmt = p ? *p : NULL; ASSERT_LUA_CONTEXT; if (!p) return 0; *p = NULL; if (!stmt) return 0; PLLUA_TRY(); { if (stmt->kept && stmt->plan) SPI_freeplan(stmt->plan); MemoryContextDelete(stmt->mcxt); } PLLUA_CATCH_RETHROW(); return 0; } int pllua_cursor_cleanup_portal(lua_State *L) { Portal portal = lua_touserdata(L, 1); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_PORTALS); lua_pushnil(L); lua_rawsetp(L, -2, portal); lua_pop(L, 1); return 0; } static void pllua_cursor_cb(void *arg) { pllua_spi_cursor *curs = arg; if (curs && curs->is_live) { lua_State *L = curs->L; Portal portal = curs->portal; curs->is_live = false; if (curs->cb) curs->cb->arg = NULL; curs->cb = NULL; curs->portal = NULL; /* * we got here from pg, in a memory context reset. Since we shouldn't ever * have allowed ourselves far enough into pg for that to happen while in * lua context, assert that fact. */ ASSERT_PG_CONTEXT; /* * we'd better ignore any (unlikely) lua error here, since that's safer * than raising an error into pg here */ if (portal && pllua_cpcall(L, pllua_cursor_cleanup_portal, portal)) pllua_poperror(L); } } static pllua_spi_cursor *pllua_newcursor(lua_State *L) { pllua_spi_cursor *curs = pllua_newobject(L, PLLUA_SPI_CURSOR_OBJECT, sizeof(pllua_spi_cursor), true); curs->L = L; curs->portal = NULL; curs->cb = NULL; curs->fetch_count = 0; curs->is_ours = false; curs->is_private = false; curs->is_live = false; return curs; } /* * Associate a Portal with a cursor object. If the cursor was open, closes the * portal (if we own it) or dissociates from it (if it's not ours). * * A cursor object without a portal just has various data in its uservalue * (e.g. a name or (unexecuted) query). Setting a portal turns it into a * live open cursor. * * Caller must NOT set an open portal on a new cursor unless it has verified * that no entry already exists in reg[PORTALS] for this portal! */ static void pllua_cursor_setportal(lua_State *L, int nd, pllua_spi_cursor *curs, Portal portal, bool is_ours) { Portal oldportal = curs->portal; nd = lua_absindex(L, nd); if (oldportal) { /* * Dissociate everything from the portal first. If for some reason * something throws an error here, we'll no longer have any references * to the portal anywhere. */ if (curs->cb) curs->cb->arg = NULL; lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_PORTALS); lua_pushnil(L); lua_rawsetp(L, -2, oldportal); lua_pop(L, 1); curs->portal = NULL; } if ((oldportal && curs->is_ours) || portal) { PLLUA_TRY(); { if (curs->is_ours && oldportal) SPI_cursor_close(oldportal); if (portal) curs->cb = MemoryContextAlloc(PortalGetHeapMemory(portal), sizeof(MemoryContextCallback)); } PLLUA_CATCH_RETHROW(); } if (portal) { curs->cb->func = pllua_cursor_cb; curs->cb->arg = NULL; curs->L = L; lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_PORTALS); lua_pushvalue(L, nd); lua_rawsetp(L, -2, portal); lua_pop(L, 1); curs->portal = portal; curs->cb->arg = curs; curs->is_live = true; curs->is_ours = is_ours; curs->is_private = false; MemoryContextRegisterResetCallback(PortalGetHeapMemory(portal), curs->cb); } } static Portal pllua_spi_findportal(lua_State *L, const char *name) { volatile Portal portal; PLLUA_TRY(); { portal = SPI_cursor_find(name); } PLLUA_CATCH_RETHROW(); return portal; } /* * findcursor('name') - return cursor object given a portal name */ static int pllua_spi_findcursor(lua_State *L) { const char *name = luaL_checkstring(L, 1); Portal portal = pllua_spi_findportal(L, name); if (!portal) return 0; pllua_verify_encoding(L, name); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_PORTALS); if (lua_rawgetp(L, -1, portal) == LUA_TUSERDATA) { pllua_spi_cursor *curs = pllua_toobject(L, -1, PLLUA_SPI_CURSOR_OBJECT); if (!curs || curs->portal != portal) luaL_error(L, "portal lookup mismatch"); return 1; } else { pllua_spi_cursor *curs = pllua_newcursor(L); /* store the portal name */ lua_getuservalue(L, -1); lua_pushvalue(L, 1); lua_setfield(L, -2, "name"); lua_pop(L, 1); pllua_cursor_setportal(L, -1, curs, portal, false); return 1; } } /* * newcursor(['name']) - unlike findcursor, always returns a cursor; if name * was not specified or no portal exists with the given name, returns an * unopened cursor. */ int pllua_spi_newcursor(lua_State *L) { const char *name = luaL_optstring(L, 1, NULL); if (name) { lua_pushcfunction(L, pllua_spi_findcursor); lua_pushvalue(L, 1); lua_call(L, 1, 1); if (!lua_isnil(L, -1)) return 1; } pllua_newcursor(L); if (name) { /* store the portal name (encoding already checked) */ lua_getuservalue(L, -1); lua_pushvalue(L, 1); lua_setfield(L, -2, "name"); lua_pop(L, 1); } /* no actual portal yet */ return 1; } /* * s:close */ static int pllua_cursor_close(lua_State *L) { pllua_spi_cursor *curs = pllua_checkobject(L, 1, PLLUA_SPI_CURSOR_OBJECT); if (!curs->portal || !curs->is_live) return 0; curs->is_ours = true; pllua_cursor_setportal(L, 1, curs, NULL, false); return 0; } /* * s:isopen */ static int pllua_cursor_isopen(lua_State *L) { pllua_spi_cursor *curs = pllua_checkobject(L, 1, PLLUA_SPI_CURSOR_OBJECT); lua_pushboolean(L, (curs->portal && curs->is_live)); return 1; } /* * s:own */ static int pllua_cursor_own(lua_State *L) { pllua_spi_cursor *curs = pllua_checkobject(L, 1, PLLUA_SPI_CURSOR_OBJECT); lua_settop(L, 1); if (!curs->portal || !curs->is_live) return 1; curs->is_ours = true; return 1; } /* * s:disown */ static int pllua_cursor_disown(lua_State *L) { pllua_spi_cursor *curs = pllua_checkobject(L, 1, PLLUA_SPI_CURSOR_OBJECT); lua_settop(L, 1); if (!curs->portal || !curs->is_live) return 1; curs->is_ours = false; return 1; } /* * s:isowned */ static int pllua_cursor_isowned(lua_State *L) { pllua_spi_cursor *curs = pllua_checkobject(L, 1, PLLUA_SPI_CURSOR_OBJECT); lua_pushboolean(L, curs->is_ours); return 1; } /* * s:name */ int pllua_cursor_name(lua_State *L) { pllua_spi_cursor *curs = pllua_checkobject(L, 1, PLLUA_SPI_CURSOR_OBJECT); if (curs->portal && curs->is_live && curs->portal->name) lua_pushstring(L, curs->portal->name); else { lua_getuservalue(L, 1); lua_getfield(L, -1, "name"); } return 1; } static int pllua_cursor_gc(lua_State *L) { pllua_spi_cursor *curs = pllua_toobject(L, 1, PLLUA_SPI_CURSOR_OBJECT); ASSERT_LUA_CONTEXT; if (!curs || !curs->is_live || !curs->portal) return 0; pllua_cursor_setportal(L, 1, curs, NULL, false); return 0; } /* * rows iterator * * upvalue 1: cursor object * upvalue 2: current queue pos * upvalue 3: current queue size */ static int pllua_spi_stmt_rows_iter(lua_State *L) { pllua_spi_cursor *curs = pllua_checkobject(L, lua_upvalueindex(1), PLLUA_SPI_CURSOR_OBJECT); int fetch_count = curs->is_private ? curs->fetch_count : 1; int qpos = lua_tointeger(L, lua_upvalueindex(2)); int qlen = lua_tointeger(L, lua_upvalueindex(3)); /* * If the cursor disappeared from under us, then throw error even if * there's stuff in the queue; otherwise things would be too confusing and * unpredictable */ if (!curs->portal || !curs->is_live) luaL_error(L, "cannot iterate a closed cursor"); if (fetch_count == 0) fetch_count = DEFAULT_FETCH_COUNT; if (fetch_count > 1 && qpos < qlen) { pllua_get_user_field(L, lua_upvalueindex(1), "q"); lua_geti(L, -1, ++qpos); lua_remove(L, -2); } else { lua_pushcfunction(L, pllua_spi_cursor_fetch); lua_pushvalue(L, lua_upvalueindex(1)); lua_pushinteger(L, fetch_count); lua_call(L, 2, 1); if (lua_isnil(L,-1)) luaL_error(L, "cursor fetch returned nil"); if (fetch_count > 1) { lua_pushvalue(L, -1); pllua_set_user_field(L, lua_upvalueindex(1), "q"); qpos = 1; lua_getfield(L, -1, "n"); qlen = lua_tointeger(L, -1); lua_replace(L, lua_upvalueindex(3)); } lua_geti(L, -1, 1); } if (lua_isnil(L, -1)) { if (curs->is_private) { lua_pushcfunction(L, pllua_cursor_close); lua_pushvalue(L, lua_upvalueindex(1)); lua_call(L, 1, 0); lua_pushnil(L); lua_replace(L, lua_upvalueindex(1)); } lua_pushnil(L); return 1; } if (fetch_count > 1) { lua_pushinteger(L, qpos); lua_replace(L, lua_upvalueindex(2)); } return 1; } /* * s:rows(args) returns iterator, nil, nil * */ static int pllua_spi_stmt_rows(lua_State *L) { void **p = pllua_torefobject(L, 1, PLLUA_SPI_STMT_OBJECT); pllua_spi_cursor *curs = pllua_newcursor(L); if (p) { pllua_spi_statement *stmt = *p; curs->fetch_count = stmt->fetch_count; } lua_insert(L, 1); lua_pushcfunction(L, pllua_spi_cursor_open); lua_insert(L, 1); lua_call(L, lua_gettop(L) - 1, 1); curs->is_private = 1; lua_pushinteger(L, 0); lua_pushinteger(L, 0); lua_pushcclosure(L, pllua_spi_stmt_rows_iter, 3); lua_pushnil(L); lua_pushnil(L); return 3; } /* * c:rows() returns iterator, nil, nil * */ static int pllua_cursor_rows(lua_State *L) { pllua_checkobject(L, 1, PLLUA_SPI_CURSOR_OBJECT); lua_settop(L, 1); lua_pushinteger(L, 0); lua_pushinteger(L, 0); lua_pushcclosure(L, pllua_spi_stmt_rows_iter, 3); lua_pushnil(L); lua_pushnil(L); return 3; } #if PG_VERSION_NUM >= 110000 static int pllua_spi_xact(lua_State *L, bool commit) { pllua_interpreter *interp = pllua_getinterpreter(L); if (interp->cur_activation.atomic) luaL_error(L, "cannot commit or rollback in this context"); if (IsSubTransaction()) luaL_error(L, "cannot commit or rollback from inside a subtransaction"); PLLUA_TRY(); { SPI_connect_ext(SPI_OPT_NONATOMIC); if (commit) SPI_commit(); else SPI_rollback(); SPI_start_transaction(); SPI_finish(); } PLLUA_CATCH_RETHROW(); return 0; } static int pllua_spi_commit(lua_State *L) { return pllua_spi_xact(L, true); } static int pllua_spi_rollback(lua_State *L) { return pllua_spi_xact(L, false); } #endif static int pllua_spi_is_atomic(lua_State *L) { pllua_interpreter *interp = pllua_getinterpreter(L); lua_pushboolean(L, interp->cur_activation.atomic ? 1 : 0); return 1; } static struct luaL_Reg spi_funcs[] = { { "execute", pllua_spi_execute }, { "execute_count", pllua_spi_execute_count }, { "prepare", pllua_spi_prepare }, { "readonly", pllua_spi_is_readonly }, { "findcursor", pllua_spi_findcursor }, { "newcursor", pllua_spi_newcursor }, { "rows", pllua_spi_stmt_rows }, #if PG_VERSION_NUM >= 110000 { "commit", pllua_spi_commit }, { "rollback", pllua_spi_rollback }, #endif { "is_atomic", pllua_spi_is_atomic }, { NULL, NULL } }; static struct luaL_Reg spi_cursor_methods[] = { { "fetch", pllua_spi_cursor_fetch }, { "move", pllua_spi_cursor_move }, { "own", pllua_cursor_own }, { "isowned", pllua_cursor_isowned }, { "disown", pllua_cursor_disown }, { "close", pllua_cursor_close }, { "isopen", pllua_cursor_isopen }, { "name", pllua_cursor_name }, { "rows", pllua_cursor_rows }, { "open", pllua_spi_cursor_open }, { NULL, NULL } }; static struct luaL_Reg spi_cursor_mt[] = { { "__gc", pllua_cursor_gc }, { NULL, NULL } }; static int pllua_spi_noop(lua_State *L) { lua_settop(L, 1); return 1; /* return first arg */ } static int pllua_spi_noop_true(lua_State *L) { lua_pushboolean(L, 1); return 1; /* return true */ } static struct luaL_Reg spi_stmt_methods[] = { { "save", pllua_spi_noop }, { "issaved", pllua_spi_noop_true }, { "execute", pllua_spi_execute }, { "execute_count", pllua_spi_execute_count }, { "getcursor", pllua_spi_stmt_getcursor }, { "rows", pllua_spi_stmt_rows }, { "numargs", pllua_stmt_numargs }, { "argtype", pllua_stmt_argtype }, { "cursor_ok", pllua_stmt_cursor_ok }, { NULL, NULL } }; static struct luaL_Reg spi_stmt_mt[] = { { "__gc", pllua_stmt_gc }, { "__call", pllua_spi_execute }, { NULL, NULL } }; int pllua_open_spi(lua_State *L) { if (pllua_spi_prepare_recursion == -1) { pllua_spi_prev_parse_hook = post_parse_analyze_hook; post_parse_analyze_hook = pllua_spi_prepare_checkparam_hook; pllua_spi_prepare_recursion = 0; } pllua_newmetatable(L, PLLUA_SPI_STMT_OBJECT, spi_stmt_mt); luaL_newlib(L, spi_stmt_methods); lua_setfield(L, -2, "__index"); lua_pop(L, 1); /* make a weak table to hold portals: light[Portal] = cursor object */ pllua_new_weak_table(L, "v", "spi portal registry table"); lua_pop(L, 1); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_PORTALS); pllua_newmetatable(L, PLLUA_SPI_CURSOR_OBJECT, spi_cursor_mt); luaL_newlib(L, spi_cursor_methods); lua_setfield(L, -2, "__index"); lua_pop(L, 1); lua_newtable(L); luaL_setfuncs(L, spi_funcs, 0); /* * Inherit the pllua.elog module via a metatable */ lua_newtable(L); lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); lua_getfield(L, -1, "pllua.elog"); lua_setfield(L, -3, "__index"); lua_pushboolean(L, 1); lua_setfield(L, -3, "__metatable"); lua_pop(L, 1); lua_setmetatable(L, -2); return 1; } pllua-ng-REL_2_0_4/src/time.c000066400000000000000000000673321347047754200160210ustar00rootroot00000000000000/* time.c */ #include "pllua.h" #include "pgtime.h" #include "catalog/pg_type.h" #include "datatype/timestamp.h" #include "utils/builtins.h" #include "utils/date.h" #include "utils/datetime.h" #if PG_VERSION_NUM >= 120000 #include "utils/float.h" #endif #if PG_VERSION_NUM >= 100000 #include "utils/fmgrprotos.h" #endif #include "utils/timestamp.h" #ifndef DATETIME_MIN_JULIAN #define DATETIME_MIN_JULIAN (0) #endif #ifndef DATE_END_JULIAN #define DATE_END_JULIAN JULIAN_MAX #endif #ifndef IS_VALID_DATE /* Range-check a date (given in Postgres, not Julian, numbering) */ #define IS_VALID_DATE(d) \ ((DATETIME_MIN_JULIAN - POSTGRES_EPOCH_JDATE) <= (d) && \ (d) < (DATE_END_JULIAN - POSTGRES_EPOCH_JDATE)) #endif #ifdef HAVE_INT64_TIMESTAMP #define FSEC_T_SCALE(f_) (f_) #else #define FSEC_T_SCALE(f_) ((int)(rint((f_) * 1000000.0))) #endif /* floor division assuming a positive divisor */ static inline int64 floordiv(int64 dividend, int64 divisor) { return (dividend / divisor) - (dividend < 0 && (dividend % divisor) != 0); } /* overflow calculation */ static inline int calc_overflow(int val, int modulus, int *nextfield) { int carry = floordiv(val, modulus); *nextfield += carry; return val - (carry * modulus); } /* * Convenience function - given a stack index, is it: * * - not convertible to a number (error) * - an integer (including an integral float) * - an infinity (error if inf_sign is null or already has the other sign) * - a float (error if NaN or if fval is null) * * A lua_Integer might, in unusual cases such as running on luajit on a 32-bit * platform, not be big enough to handle things we want to use this for (such * as microsecond times), so use int64 instead. On 5.3+ where we have real * integers, we try to avoid loss of integer precision. * * Returns true for floats, false for integers. inf_sign is not changed if * the value is not an infinity. */ static bool getnumber(lua_State *L, int idx, int64 *ival, lua_Number *fval, int *inf_sign, const char *diag_field) { int isnum = 0; int isign = 0; lua_Integer inum; lua_Number num; #if LUA_VERSION_NUM < 503 num = lua_tonumberx(L, idx, &isnum); inum = (int64) num; *ival = inum; if (isnum) { if (num == (lua_Number)inum) return false; if (isinf(num)) isign = (num < 0) ? -1 : 1; } #else inum = lua_tointegerx(L, idx, &isnum); *ival = inum; if (isnum) return false; num = lua_tonumberx(L, idx, &isnum); if (isnum) { if (num == (lua_Number)(int64)num) { *ival = (int64) num; return false; } if (isinf(num)) isign = (num < 0) ? -1 : 1; } #endif if (!isnum || isnan(num) || (isign && (!inf_sign || (*inf_sign && isign != *inf_sign))) || (!isign && !fval)) luaL_error(L, "invalid value in field '%s'", diag_field); if (inf_sign && isign) *inf_sign = isign; else *fval = num; return true; } /* * determine_timezone_offset * * This is a corrected version of pg's DetermineTimeZoneOffset, which does not * correctly handle the case where the value is in the ambiguous hour but * already has tm_isdst set to disambiguate it. */ static int determine_timezone_offset(struct pg_tm *tm, pg_tz *tzp) { int date, sec; pg_time_t day, mytime, prevtime, boundary, beforetime, aftertime; long int before_gmtoff, after_gmtoff; int before_isdst, after_isdst; int res; /* * First, generate the pg_time_t value corresponding to the given * y/m/d/h/m/s taken as GMT time. If this overflows, punt and decide the * timezone is GMT. (For a valid Julian date, integer overflow should be * impossible with 64-bit pg_time_t, but let's check for safety.) */ if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday)) goto overflow; date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - UNIX_EPOCH_JDATE; day = ((pg_time_t) date) * SECS_PER_DAY; if (day / SECS_PER_DAY != date) goto overflow; sec = tm->tm_sec + (tm->tm_min + tm->tm_hour * MINS_PER_HOUR) * SECS_PER_MINUTE; mytime = day + sec; /* since sec >= 0, overflow could only be from +day to -mytime */ if (mytime < 0 && day > 0) goto overflow; /* * Find the DST time boundary just before or following the target time. We * assume that all zones have GMT offsets less than 24 hours, and that DST * boundaries can't be closer together than 48 hours, so backing up 24 * hours and finding the "next" boundary will work. */ prevtime = mytime - SECS_PER_DAY; if (mytime < 0 && prevtime > 0) goto overflow; res = pg_next_dst_boundary(&prevtime, &before_gmtoff, &before_isdst, &boundary, &after_gmtoff, &after_isdst, tzp); if (res < 0) goto overflow; /* failure? */ if (res == 0) { /* Non-DST zone, life is simple */ tm->tm_isdst = before_isdst; return -(int) before_gmtoff; } /* * Form the candidate pg_time_t values with local-time adjustment */ beforetime = mytime - before_gmtoff; if ((before_gmtoff > 0 && mytime < 0 && beforetime > 0) || (before_gmtoff <= 0 && mytime > 0 && beforetime < 0)) goto overflow; aftertime = mytime - after_gmtoff; if ((after_gmtoff > 0 && mytime < 0 && aftertime > 0) || (after_gmtoff <= 0 && mytime > 0 && aftertime < 0)) goto overflow; /* * If both before or both after the boundary time, we know what to do. The * boundary time itself is considered to be after the transition, which * means we can accept aftertime == boundary in the second case. */ if (beforetime < boundary && aftertime < boundary) { tm->tm_isdst = before_isdst; return -(int) before_gmtoff; } if (beforetime > boundary && aftertime >= boundary) { tm->tm_isdst = after_isdst; return -(int) after_gmtoff; } /* * It's an invalid or ambiguous time due to timezone transition. * * In a spring-forward transition, this means the originally specified * time was invalid, e.g. 2019-03-31 01:30:00 Europe/London (a time which * never happened because 00:59:59 was followed by 02:00:00). If tm_isdst * is set to -1, we prefer to use the "before" interpretation, under which * this time will be interpreted as if 02:30:00. (The "after" * interpretation would have made it 00:30:00 which would be surprising.) * * In a fall-back transition, the originally specified time was ambiguous, * i.e. it occurred more than once. There is no principled choice here, but * "after" is how the original version of this code behaved, and that seems * consistent with typical mktime implementations. * * If tm_isdst is not -1, though, we respect that value and do not * override it. */ if (tm->tm_isdst == -1) { if (beforetime > aftertime) { tm->tm_isdst = before_isdst; return -(int) before_gmtoff; } else { tm->tm_isdst = after_isdst; return -(int) after_gmtoff; } } else if (tm->tm_isdst == before_isdst) return -(int) before_gmtoff; else return -(int) after_gmtoff; overflow: /* Given date is out of range, so assume UTC */ tm->tm_isdst = 0; return 0; } /* * _tosql function * * Upvalue 1 is the typeinfo, upvalue 2 is the type oid. * * We accept a lua value if it is a table or userdata which we can index into * for field names like "year" etc. We won't get here in the case of a single * datum value, so a userdata param is assumed not to be a datum. * * The completely preposterous length of this function is mostly down to the * lack of any kind of usable internal interfaces for the PG date/time types * (and even the external interfaces are badly flawed). */ static int pllua_time_tosql(lua_State *L) { pllua_typeinfo *t = *pllua_torefobject(L, lua_upvalueindex(1), PLLUA_TYPEINFO_OBJECT); pllua_datum *d; Oid oid = (Oid) lua_tointeger(L, lua_upvalueindex(2)); int nargs = lua_gettop(L); TimestampTz tsval; DateADT dateresult; Datum iresult; Datum result = 0; static struct pg_tm ztm = { 0 }; struct pg_tm tm; const char *tzname = NULL; pg_tz *tz = NULL; int64 gmtoff = 0; int64 tmpint = 0; lua_Number tmpflt = 0.0; int64 microsecs = 0; int64 epoch_microsecs = 0; int found_year = 0; int found_mon = 0; int found_mday = 0; int found_hour = 0; int found_min = 0; int found_sec = 0; int found_epoch = 0; int found_tz = 0; int found_gmtoff = 0; int inf_sign = 0; /* for now, decline if not exactly 1 indexable arg. */ if (nargs != 1 || !(lua_type(L, 1) == LUA_TTABLE || (lua_type(L, 1) == LUA_TUSERDATA && luaL_getmetafield(L, 1, "__index") != LUA_TNIL))) return 0; lua_settop(L, 1); /* * Note: for most uses of pg_tm, tm_year has the actual year (not offset * by 1900) and tm_mon starts at 1 not 0, matching the Lua convention. * isdst defaults to -1 and not 0 if not found in the input. */ tm = ztm; tm.tm_isdst = -1; #define TMGET(name_,fname_) \ if (lua_getfield(L, 1, name_) != LUA_TNIL) \ { \ getnumber(L, -1, &tmpint, NULL, &inf_sign, name_); \ tm.tm_##fname_ = tmpint; \ found_##fname_ = 1; \ } TMGET("year", year); TMGET("month", mon); TMGET("day", mday); TMGET("hour", hour); TMGET("min", min); /* "sec" handled specially below */ #undef TMGET if (lua_getfield(L, 1, "isdst") != LUA_TNIL) tm.tm_isdst = lua_toboolean(L, -1) ? 1 : 0; lua_settop(L, 1); /* * Accept a fractional part as any combination of: * sec = float * millisecs = number * microsecs = number */ if (lua_getfield(L, 1, "sec") != LUA_TNIL) { if (getnumber(L, -1, &tmpint, &tmpflt, &inf_sign, "sec")) { double fisec = 0; double frac = modf(fabs(tmpflt), &fisec); if (tmpflt < 0) { tm.tm_sec = -((int) fisec + 1); microsecs = 1000000 - (int) rint(frac * 1000000.0); } else { tm.tm_sec = (int) fisec; microsecs = (int) rint(frac * 1000000.0); } } else tm.tm_sec = tmpint; found_sec = 1; } /* * Fields "msec" and "usec" are offsets (which may be negative and/or * larger than one second) from the time specified by the other values. */ if (lua_getfield(L, 1, "msec") != LUA_TNIL) { if (getnumber(L, -1, &tmpint, &tmpflt, &inf_sign, "msec")) microsecs += (int64) rint(tmpflt * 1000.0); else microsecs += (tmpint * INT64CONST(1000)); } if (lua_getfield(L, 1, "usec") != LUA_TNIL) { if (getnumber(L, -1, &tmpint, &tmpflt, &inf_sign, "usec")) microsecs += (int64) rint(tmpflt); else microsecs += tmpint; } /* * In place of YMDhms, accept any one of: * * epoch = number * epoch_msec = number * epoch_usec = number */ if (lua_getfield(L, 1, "epoch") != LUA_TNIL) { if (getnumber(L, -1, &tmpint, &tmpflt, &inf_sign, "epoch")) epoch_microsecs = (int64) rint(tmpflt * 1000000.0); else epoch_microsecs = tmpint * INT64CONST(1000000); ++found_epoch; } if (lua_getfield(L, 1, "epoch_msec") != LUA_TNIL) { if (getnumber(L, -1, &tmpint, &tmpflt, &inf_sign, "epoch_msec")) epoch_microsecs = (int64) rint(tmpflt * 1000.0); else epoch_microsecs = tmpint * INT64CONST(1000); ++found_epoch; } if (lua_getfield(L, 1, "epoch_usec") != LUA_TNIL) { if (getnumber(L, -1, &tmpint, &tmpflt, &inf_sign, "epoch_usec")) epoch_microsecs = (int64) rint(tmpflt); else epoch_microsecs = tmpint; ++found_epoch; } lua_settop(L, 1); switch (lua_getfield(L, 1, "timezone")) { case LUA_TNIL: break; case LUA_TBOOLEAN: if (lua_toboolean(L, -1)) found_tz = 1; break; case LUA_TSTRING: { int tz = 0; found_tz = 1; tzname = lua_tostring(L, -1); if (tzname && DecodeTimezone((char *) tzname, &tz) == 0) { gmtoff = -tz; found_gmtoff = 1; } } break; default: getnumber(L, -1, &gmtoff, NULL, NULL, "timezone"); found_gmtoff = 1; break; } /* input done, check validity of everything */ if (found_epoch > 1) luaL_error(L, "cannot specify multiple epoch fields"); else if (found_epoch) { if (found_year || found_mon || found_mday) luaL_error(L, "cannot specify both epoch and date fields"); if (found_hour || found_min || found_sec) luaL_error(L, "cannot specify both epoch and time fields"); if (oid == TIMESTAMPTZOID && (found_tz || found_gmtoff)) luaL_error(L, "cannot specify timezone with epoch for timestamptz"); } else if (oid == DATEOID || oid == TIMESTAMPTZOID || oid == TIMESTAMPOID) { if (!found_year) luaL_error(L, "missing datetime field '%s'", "year"); if (!found_mon) luaL_error(L, "missing datetime field '%s'", "mon"); if (!found_mday) luaL_error(L, "missing datetime field '%s'", "day"); if (oid != TIMESTAMPTZOID && (found_tz || found_gmtoff)) luaL_error(L, "cannot specify timezone for this type"); } else if (oid == TIMEOID || oid == TIMETZOID) { if (!found_hour) luaL_error(L, "missing datetime field '%s'", "hour"); if (found_sec && !found_min) luaL_error(L, "missing datetime field '%s'", "min"); if (oid == TIMETZOID && found_tz && !found_gmtoff) luaL_error(L, "non-numeric timezones not supported for 'timetz'"); if (oid != TIMETZOID && (found_tz || found_gmtoff)) luaL_error(L, "cannot specify timezone for this type"); } if (inf_sign && !(oid == TIMESTAMPOID || oid == TIMESTAMPTZOID)) luaL_error(L, "infinite values not permitted for this type"); d = pllua_newdatum(L, lua_upvalueindex(1), 0); PLLUA_TRY(); { if (found_tz || found_gmtoff) { tz = (found_gmtoff ? pg_tzset_offset(-gmtoff) : tzname ? pg_tzset(tzname) : session_timezone); if (!tz) ereport(ERROR, (errmsg("invalid timezone specified"))); } if (found_epoch) { microsecs += epoch_microsecs; if (oid != DATEOID) { tmpflt = microsecs / 1000000.0; iresult = DirectFunctionCall7(make_interval, Int32GetDatum(0),Int32GetDatum(0),Int32GetDatum(0), Int32GetDatum(0),Int32GetDatum(0),Int32GetDatum(0), Float8GetDatumFast(tmpflt)); } switch (oid) { case TIMESTAMPTZOID: case TIMESTAMPOID: if (inf_sign != 0) { Timestamp tresult; if (inf_sign > 0) TIMESTAMP_NOEND(tresult); else TIMESTAMP_NOBEGIN(tresult); result = TimestampGetDatum(tresult); break; } tsval = time_t_to_timestamptz(0); result = DirectFunctionCall2(timestamptz_pl_interval, TimestampTzGetDatum(tsval), iresult); if (oid == TIMESTAMPOID && ((!found_gmtoff && found_tz) || (found_gmtoff && gmtoff != 0))) { fsec_t fsec; int tzo; Timestamp newresult; if (timestamp2tm(DatumGetTimestampTz(result), &tzo, &tm, &fsec, NULL, tz) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); if (tm2timestamp(&tm, fsec, NULL, &newresult) != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not convert to time zone"))); result = TimestampGetDatum(newresult); } break; case DATEOID: if ((!found_tz && !found_gmtoff) || (found_gmtoff && gmtoff == 0)) { int64 jdate = floordiv(microsecs, INT64CONST(86400) * 1000000) + UNIX_EPOCH_JDATE - POSTGRES_EPOCH_JDATE; if (!IS_VALID_DATE(jdate)) ereport(ERROR, (errmsg("date value out of range"))); dateresult = jdate; } else { struct pg_tm *tmp; pg_time_t tval = floordiv(microsecs, 1000000); tmp = pg_localtime(&tval, tz); if (!tmp) elog(ERROR, "date value conversion failed"); dateresult = date2j(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday) - POSTGRES_EPOCH_JDATE; } result = DateADTGetDatum(dateresult); break; case TIMEOID: { float8 secs = 0.0; result = DirectFunctionCall3(make_time, Int32GetDatum(0), Int32GetDatum(0), Float8GetDatumFast(secs)); result = DirectFunctionCall2(time_pl_interval, result, iresult); } break; case TIMETZOID: { char strbuf[32]; int64 absoff = gmtoff < 0 ? -gmtoff : gmtoff; Datum tmpdatum; sprintf(strbuf, "00:00:00%c%02d:%02d:%02d", gmtoff < 0 ? '-' : '+', (int) (absoff / 3600), (int) ((absoff / 60) % 60), (int) (absoff % 60)); tmpdatum = DirectFunctionCall3(timetz_in, CStringGetDatum(strbuf), ObjectIdGetDatum(TIMETZOID), Int32GetDatum(-1)); result = DirectFunctionCall2(timetz_pl_interval, tmpdatum, iresult); pfree(DatumGetPointer(tmpdatum)); } break; case INTERVALOID: result = iresult; break; } } else { PGFunction addfunc = NULL; /* * We have to normalize the pg_tm ourselves, except for interval * and mday fields. But note that xx:59:60 is allowed, as is * 24:00:00. * * The semantics of overflowing from minutes to hours to days here * are highly questionable at best, but implementations of POSIX * mktime seem to do it this way too. */ if (oid != INTERVALOID) { if (tm.tm_hour != 24 || tm.tm_min != 0 || tm.tm_sec != 0) { if (tm.tm_sec < 0 || tm.tm_sec > 60 || (tm.tm_sec == 60 && tm.tm_min != 59)) tm.tm_sec = calc_overflow(tm.tm_sec, 60, &tm.tm_min); if (tm.tm_min < 0 || tm.tm_min >= 60) tm.tm_min = calc_overflow(tm.tm_min, 60, &tm.tm_hour); if (tm.tm_hour < 0 || tm.tm_hour >= 24) tm.tm_hour = calc_overflow(tm.tm_hour, 24, &tm.tm_mday); } if (tm.tm_mon < 1 || tm.tm_mon > 12) tm.tm_mon = 1 + calc_overflow(tm.tm_mon - 1, 12, &tm.tm_year); } switch (oid) { case DATEOID: dateresult = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE; result = DateADTGetDatum(dateresult); break; case TIMESTAMPTZOID: { TimestampTz newresult; int tzo = determine_timezone_offset(&tm, tz ? tz : session_timezone); if (tm2timestamp(&tm, 0, &tzo, &newresult) != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not convert to timestamp"))); result = TimestampTzGetDatum(newresult); addfunc = timestamptz_pl_interval; } break; case TIMESTAMPOID: { TimestampTz newresult; if (tm2timestamp(&tm, 0, NULL, &newresult) != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not convert to timestamp"))); result = TimestampGetDatum(newresult); addfunc = timestamp_pl_interval; } break; case TIMEOID: { float8 secs = tm.tm_sec; result = DirectFunctionCall3(make_time, Int32GetDatum(tm.tm_hour), Int32GetDatum(tm.tm_min), Float8GetDatumFast(secs)); addfunc = time_pl_interval; } break; case TIMETZOID: { char strbuf[32]; int64 absoff = gmtoff < 0 ? -gmtoff : gmtoff; sprintf(strbuf, "%02d:%02d:%02d%c%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec, gmtoff < 0 ? '-' : '+', (int) (absoff / 3600), (int) ((absoff / 60) % 60), (int) (absoff % 60)); result = DirectFunctionCall3(timetz_in, CStringGetDatum(strbuf), ObjectIdGetDatum(TIMETZOID), Int32GetDatum(-1)); addfunc = timetz_pl_interval; } break; case INTERVALOID: { float8 secs = tm.tm_sec + (microsecs / 1000000.0); result = DirectFunctionCall7(make_interval, Int32GetDatum(tm.tm_year), Int32GetDatum(tm.tm_mon), Int32GetDatum(0), /* weeks */ Int32GetDatum(tm.tm_mday), Int32GetDatum(tm.tm_hour), Int32GetDatum(tm.tm_min), Float8GetDatumFast(secs)); } break; } if (microsecs != 0 && oid != DATEOID && oid != INTERVALOID) { float8 secs = microsecs / 1000000.0; iresult = DirectFunctionCall7(make_interval, Int32GetDatum(0),Int32GetDatum(0),Int32GetDatum(0), Int32GetDatum(0),Int32GetDatum(0),Int32GetDatum(0), Float8GetDatumFast(secs)); result = DirectFunctionCall2(addfunc, result, iresult); } } d->value = result; pllua_savedatum(L, d, t); } PLLUA_CATCH_RETHROW(); return 1; } static float8 pllua_time_raw_part(lua_State *L, const char *part, Datum val, Oid oid, PGFunction func, bool *isnull) { volatile float8 res = 0; *isnull = false; PLLUA_TRY(); { text *part_text = cstring_to_text(part); Datum resd; LOCAL_FCINFO(fcinfo, 2); if (oid == DATEOID) val = DirectFunctionCall1(date_timestamp, val); InitFunctionCallInfoData(*fcinfo, NULL, 2, InvalidOid, NULL, NULL); LFCI_ARG_VALUE(fcinfo,0) = PointerGetDatum(part_text); LFCI_ARG_VALUE(fcinfo,1) = val; LFCI_ARGISNULL(fcinfo,0) = false; LFCI_ARGISNULL(fcinfo,1) = false; resd = (*func) (fcinfo); if (fcinfo->isnull) *isnull = true; else res = DatumGetFloat8(resd); } PLLUA_CATCH_RETHROW(); return res; } static int pllua_time_as_table(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); Oid oid = (Oid) lua_tointeger(L, lua_upvalueindex(2)); static struct pg_tm ztm = { 0 }; struct pg_tm tm; fsec_t fsec = 0; float8 epoch_flt = 0; Datum val = d->value; float8 tmpflt; double tmpflt2; int tmpint; int64 microsecs = 0; bool isnull; const char *tzname = NULL; const char *tzn = NULL; int tzo = 0; pg_tz *tz = NULL; bool omit_date = false; bool omit_time = false; int found_tz = 0; int found_gmtoff = 0; int64 gmtoff = 0; tm = ztm; tm.tm_isdst = -1; lua_settop(L, 2); if (oid == TIMESTAMPTZOID) { switch (lua_type(L, 2)) { case LUA_TNIL: case LUA_TNONE: case LUA_TBOOLEAN: break; case LUA_TSTRING: { int tz = 0; found_tz = 1; tzname = lua_tostring(L, -1); if (tzname && DecodeTimezone((char *) tzname, &tz) == 0) { gmtoff = -tz; found_gmtoff = 1; } } break; default: getnumber(L, 2, &gmtoff, NULL, NULL, "timezone"); found_gmtoff = 1; break; } } else if (!lua_isnil(L, 2)) luaL_error(L, "cannot specify timezone parameter for this type"); switch (oid) { case DATEOID: { DateADT dval = DatumGetDateADT(val); j2date(dval + POSTGRES_EPOCH_JDATE, &tm.tm_year, &tm.tm_mon, &tm.tm_mday); omit_time = true; } break; case TIMESTAMPTZOID: case TIMESTAMPOID: /* * We abuse the fact that these two have the same underlying * representation. */ { Timestamp tstmp = DatumGetTimestamp(val); if (TIMESTAMP_NOT_FINITE(tstmp)) { if (TIMESTAMP_IS_NOBEGIN(tstmp)) epoch_flt = -get_float8_infinity(); else epoch_flt = get_float8_infinity(); break; } PLLUA_TRY(); { if (found_tz || found_gmtoff) { tz = (found_gmtoff ? pg_tzset_offset(-gmtoff) : tzname ? pg_tzset(tzname) : session_timezone); if (!tz) ereport(ERROR, (errmsg("invalid timezone specified"))); } if (timestamp2tm(tstmp, (oid == TIMESTAMPTZOID) ? &tzo : NULL, &tm, &fsec, (oid == TIMESTAMPTZOID) ? &tzn : NULL, tz) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); } PLLUA_CATCH_RETHROW(); microsecs = FSEC_T_SCALE(fsec); } break; case TIMETZOID: tmpflt = pllua_time_raw_part(L, "timezone", val, oid, timetz_part, &isnull); if (isnull) luaL_error(L, "unexpected null from time_part"); tm.tm_gmtoff = (int) tmpflt; /*FALLTHROUGH*/ case TIMEOID: tmpflt = pllua_time_raw_part(L, "epoch", val, oid, (oid == TIMEOID) ? time_part : timetz_part, &isnull); if (isnull) luaL_error(L, "unexpected null from time_part"); tmpflt += tm.tm_gmtoff; microsecs = (int64) rint(1000000.0 * modf(tmpflt, &tmpflt2)); tmpint = (int) tmpflt2; tm.tm_sec = (tmpint % 60); tm.tm_min = ((tmpint / 60) % 60); tm.tm_hour = (tmpint / 3600); omit_date = true; break; case INTERVALOID: { Interval *itmp = DatumGetIntervalP(val); PLLUA_TRY(); { if (interval2tm(*itmp, &tm, &fsec) != 0) elog(ERROR, "interval output failed"); } PLLUA_CATCH_RETHROW(); microsecs = FSEC_T_SCALE(fsec); } break; } lua_createtable(L, 0, 10); if (epoch_flt) { lua_pushnumber(L, epoch_flt); lua_setfield(L, -2, "epoch"); } else { if (!omit_date) { lua_pushinteger(L, tm.tm_year); lua_setfield(L, -2, "year"); lua_pushinteger(L, tm.tm_mon); lua_setfield(L, -2, "month"); lua_pushinteger(L, tm.tm_mday); lua_setfield(L, -2, "day"); } if (!omit_time) { lua_pushinteger(L, tm.tm_hour); lua_setfield(L, -2, "hour"); lua_pushinteger(L, tm.tm_min); lua_setfield(L, -2, "min"); lua_pushinteger(L, tm.tm_sec); lua_setfield(L, -2, "sec"); lua_pushinteger(L, microsecs); lua_setfield(L, -2, "usec"); } if (oid == TIMESTAMPTZOID && tm.tm_isdst >= 0) { lua_pushboolean(L, tm.tm_isdst != 0); lua_setfield(L, -2, "isdst"); } if (oid == TIMESTAMPTZOID || oid == TIMETZOID) { lua_pushinteger(L, tm.tm_gmtoff); lua_setfield(L, -2, "timezone"); } if (tzn) { lua_pushstring(L, tzn); lua_setfield(L, -2, "timezone_abbrev"); } } return 1; } static int pllua_time_part(lua_State *L, pllua_datum *d, Oid oid, const char *opart) { const char *part = opart; PGFunction func; float8 res = 0; bool isnull = false; if (strcmp(opart,"epoch_msec") == 0 || strcmp(opart,"epoch_usec") == 0) part = "epoch"; if (strcmp(opart,"isoweek") == 0) part = "week"; switch (oid) { case DATEOID: func = timestamp_part; break; case TIMESTAMPTZOID: func = timestamptz_part; break; case TIMESTAMPOID: func = timestamp_part; break; case TIMEOID: func = time_part; break; case TIMETZOID: func = timetz_part; break; case INTERVALOID: func = interval_part; break; default: luaL_error(L, "unknown datetime type"); return 0; /* keep compiler happy */ } res = pllua_time_raw_part(L, part, d->value, oid, func, &isnull); if (isnull) lua_pushnil(L); else if (isinf(res)) lua_pushnumber(L, res); else if (part != opart) { if (strcmp(opart, "epoch_msec") == 0) lua_pushnumber(L, res * 1000.0); else if (strcmp(opart, "epoch_usec") == 0) { #ifdef PLLUA_INT8_OK lua_pushinteger(L, (int64) rint(res * 1000000.0)); #else lua_pushnumber(L, rint(res * 1000000.0)); #endif } else lua_pushinteger(L, (lua_Integer) rint(res)); } else if (strcmp(part,"epoch") == 0 || strcmp(part,"second") == 0) lua_pushnumber(L, res); else lua_pushinteger(L, (lua_Integer) rint(res)); return 1; } static int pllua_time_index(lua_State *L) { pllua_datum *d = pllua_checkdatum(L, 1, lua_upvalueindex(1)); Oid oid = (Oid) lua_tointeger(L, lua_upvalueindex(2)); const char *part = luaL_checkstring(L, 2); lua_settop(L, 2); if (lua_getfield(L, lua_upvalueindex(3), part) != LUA_TNIL) return 1; lua_pop(L, 1); return pllua_time_part(L, d, oid, part); } static luaL_Reg time_methods[] = { { "as_table", pllua_time_as_table }, { NULL, NULL } }; static luaL_Reg time_meta[] = { { "tosql", pllua_time_tosql }, { "__index", pllua_time_index }, { NULL, NULL } }; static luaL_Reg time_funcs[] = { { NULL, NULL } }; int pllua_open_time(lua_State *L) { static Oid oidlist[] = { TIMESTAMPTZOID, TIMESTAMPOID, DATEOID, TIMEOID, TIMETZOID, INTERVALOID, InvalidOid }; int i; lua_settop(L, 0); lua_newtable(L); /* module table at index 1 */ luaL_setfuncs(L, time_funcs, 0); for (i = 0; OidIsValid(oidlist[i]); ++i) { Oid oid = oidlist[i]; lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, oid); lua_call(L, 1, 1); lua_getuservalue(L, -1); /* first upvalue for metamethods */ lua_pushvalue(L, -2); /* second upvalue for metamethods */ lua_pushinteger(L, oid); /* methods table */ lua_newtable(L); /* first upvalue for methods */ lua_pushvalue(L, -3); /* second upvalue for methods */ lua_pushinteger(L, oid); /* methods table is third upvalue for metamethods */ luaL_setfuncs(L, time_methods, 2); luaL_setfuncs(L, time_meta, 3); lua_pop(L, 2); } lua_settop(L, 1); return 1; } pllua-ng-REL_2_0_4/src/trigger.c000066400000000000000000000344241347047754200165220ustar00rootroot00000000000000/* trigger.c */ #include "pllua.h" #include "access/htup_details.h" #include "commands/event_trigger.h" #include "commands/trigger.h" #include "utils/reltrigger.h" #include "utils/rel.h" #include "utils/lsyscache.h" typedef struct pllua_trigger { TriggerData *td; /* NULLed out when trigger ends */ bool modified; } pllua_trigger; typedef struct pllua_event_trigger { EventTriggerData *etd; /* NULLed out when trigger ends */ } pllua_event_trigger; /* * Push a new trigger object on the stack */ void pllua_trigger_begin(lua_State *L, TriggerData *td) { pllua_trigger *obj = pllua_newobject(L, PLLUA_TRIGGER_OBJECT, sizeof(pllua_trigger), 1); obj->td = td; } void pllua_trigger_end(lua_State *L, int nd) { pllua_trigger *obj = pllua_checkobject(L, nd, PLLUA_TRIGGER_OBJECT); obj->td = NULL; } static pllua_trigger * pllua_checktrigger(lua_State *L, int nd) { pllua_trigger *obj = pllua_checkobject(L, nd, PLLUA_TRIGGER_OBJECT); if (!obj->td) luaL_error(L, "cannot access dead trigger object"); return obj; } /* * Push a new event trigger object on the stack */ void pllua_evtrigger_begin(lua_State *L, EventTriggerData *etd) { pllua_event_trigger *obj = pllua_newobject(L, PLLUA_EVENT_TRIGGER_OBJECT, sizeof(pllua_event_trigger), 1); obj->etd = etd; } void pllua_evtrigger_end(lua_State *L, int nd) { pllua_event_trigger *obj = pllua_checkobject(L, nd, PLLUA_EVENT_TRIGGER_OBJECT); obj->etd = NULL; } static pllua_event_trigger * pllua_checkevtrigger(lua_State *L, int nd) { pllua_event_trigger *obj = pllua_checkobject(L, nd, PLLUA_EVENT_TRIGGER_OBJECT); if (!obj->etd) luaL_error(L, "cannot access dead event trigger object"); return obj; } /* * We support the following: * * trigger.new - always the new row (or nil) * trigger.old - always the old row (or nil) * trigger.row - new for insert/update, old for delete * trigger.name * trigger.when * trigger.operation * trigger.level * trigger.relation * * Assigning nil or a new row to trigger.row modifies the result of the * trigger, though this is for compatibility and returning a new row or nil * from the function overrides this. * */ static void pllua_trigger_get_typeinfo(lua_State *L, pllua_trigger *obj, int cache) { cache = lua_absindex(L, cache); if (lua_getfield(L, cache, ".typeinfo") != LUA_TUSERDATA) { lua_pushcfunction(L, pllua_typeinfo_lookup); lua_pushinteger(L, (lua_Integer) obj->td->tg_relation->rd_att->tdtypeid); lua_pushinteger(L, (lua_Integer) obj->td->tg_relation->rd_att->tdtypmod); lua_call(L, 2, 1); if (lua_isnil(L, -1)) luaL_error(L, "trigger failed to find relation typeinfo"); lua_pushvalue(L, -1); lua_setfield(L, cache, ".typeinfo"); } } static int pllua_trigger_getrow(lua_State *L, pllua_trigger *obj, HeapTuple tuple) { pllua_datum *d = pllua_newdatum(L, -1, (Datum)0); /* * Bit of a dance to avoid an extra copy step */ PLLUA_TRY(); { MemoryContext oldcontext = MemoryContextSwitchTo(pllua_get_memory_cxt(L)); Datum htup = heap_copy_tuple_as_datum(tuple, obj->td->tg_relation->rd_att); d->value = htup; d->need_gc = 1; MemoryContextSwitchTo(oldcontext); } PLLUA_CATCH_RETHROW(); return 1; } static int pllua_trigger_get_new(lua_State *L) { pllua_trigger *obj = pllua_checktrigger(L, 1); HeapTuple tuple = NULL; lua_settop(L, 1); lua_getuservalue(L, 1); /* index 2 */ if (!TRIGGER_FIRED_FOR_ROW(obj->td->tg_event)) return 0; if (TRIGGER_FIRED_BY_INSERT(obj->td->tg_event)) tuple = obj->td->tg_trigtuple; else if (TRIGGER_FIRED_BY_UPDATE(obj->td->tg_event)) tuple = obj->td->tg_newtuple; if (!tuple) return 0; pllua_trigger_get_typeinfo(L, obj, 2); return pllua_trigger_getrow(L, obj, tuple); } static int pllua_trigger_get_old(lua_State *L) { pllua_trigger *obj = pllua_checktrigger(L, 1); lua_settop(L, 1); lua_getuservalue(L, 1); /* index 2 */ if (!TRIGGER_FIRED_FOR_ROW(obj->td->tg_event) || TRIGGER_FIRED_BY_INSERT(obj->td->tg_event)) return 0; pllua_trigger_get_typeinfo(L, obj, 2); return pllua_trigger_getrow(L, obj, obj->td->tg_trigtuple); } static int pllua_trigger_get_name(lua_State *L) { pllua_trigger *obj = pllua_checktrigger(L, 1); lua_pushstring(L, obj->td->tg_trigger->tgname); return 1; } static int pllua_trigger_get_when(lua_State *L) { pllua_trigger *obj = pllua_checktrigger(L, 1); TriggerEvent ev = obj->td->tg_event; if (TRIGGER_FIRED_BEFORE(ev)) lua_pushstring(L, "before"); else if (TRIGGER_FIRED_AFTER(ev)) lua_pushstring(L, "after"); else if (TRIGGER_FIRED_INSTEAD(ev)) lua_pushstring(L, "instead"); else lua_pushnil(L); return 1; } static int pllua_trigger_get_operation(lua_State *L) { pllua_trigger *obj = pllua_checktrigger(L, 1); TriggerEvent ev = obj->td->tg_event; if (TRIGGER_FIRED_BY_INSERT(ev)) lua_pushstring(L, "insert"); else if (TRIGGER_FIRED_BY_UPDATE(ev)) lua_pushstring(L, "update"); else if (TRIGGER_FIRED_BY_DELETE(ev)) lua_pushstring(L, "delete"); else if (TRIGGER_FIRED_BY_TRUNCATE(ev)) lua_pushstring(L, "truncate"); else lua_pushnil(L); return 1; } static int pllua_trigger_get_level(lua_State *L) { pllua_trigger *obj = pllua_checktrigger(L, 1); TriggerEvent ev = obj->td->tg_event; if (TRIGGER_FIRED_FOR_ROW(ev)) lua_pushstring(L, "row"); else if (TRIGGER_FIRED_FOR_STATEMENT(ev)) lua_pushstring(L, "statement"); else lua_pushnil(L); return 1; } /* * Structure copied from old pllua: * * ["relation"] = { * ["namespace"] = "public", * ["attributes"] = { * ["test_column"] = 0, * }, * ["name"] = "table_name", * ["oid"] = 59059 * } * */ static int pllua_trigger_get_relation(lua_State *L) { pllua_trigger *obj = pllua_checktrigger(L, 1); Relation rel = obj->td->tg_relation; const char *schema = NULL; int i; TupleDesc tupdesc = rel->rd_att; int natts = tupdesc->natts; PLLUA_TRY(); { schema = get_namespace_name(rel->rd_rel->relnamespace); } PLLUA_CATCH_RETHROW(); lua_createtable(L, 0, 4); lua_pushstring(L, schema ? schema : ""); lua_setfield(L, -2, "namespace"); lua_pushstring(L, NameStr(rel->rd_rel->relname)); lua_setfield(L, -2, "name"); lua_pushinteger(L, (lua_Integer) rel->rd_id); lua_setfield(L, -2, "oid"); lua_createtable(L, 0, natts); for (i = 0; i < natts; ++i) { if (TupleDescAttr(tupdesc, i)->attisdropped) continue; lua_pushinteger(L, i); lua_setfield(L, -2, NameStr(TupleDescAttr(tupdesc, i)->attname)); } lua_setfield(L, -2, "attributes"); return 1; } static int pllua_trigger_index(lua_State *L) { pllua_trigger *obj = pllua_checktrigger(L, 1); const char *str = luaL_checkstring(L, 2); lua_settop(L, 2); lua_getuservalue(L, 1); /* index 3 */ if (!*str || *str == '_' || *str == '.') { lua_pushnil(L); return 1; } /* treat "row" as an alias for old or new, depending */ if (strcmp(str, "row") == 0) { if (TRIGGER_FIRED_BY_DELETE(obj->td->tg_event)) str = "old"; else str = "new"; lua_pushstring(L, str); lua_replace(L, 2); } else if (strcmp(str, "op") == 0) { /* some people like shorter names */ str = "operation"; lua_pushstring(L, str); lua_replace(L, 2); } /* previously cached result? */ lua_pushvalue(L, 2); switch (lua_rawget(L, -2)) { case LUA_TBOOLEAN: if (!lua_toboolean(L, -1)) lua_pushnil(L); return 1; case LUA_TNIL: break; default: return 1; } lua_pop(L, 1); if (luaL_getmetafield(L, 1, "_keys") != LUA_TTABLE) luaL_error(L, "missing trigger keys"); if (lua_getfield(L, -1, str) == LUA_TFUNCTION) { lua_pushvalue(L, 1); lua_call(L, 1, 1); if (!lua_isnil(L, -1)) { lua_pushvalue(L, -1); lua_setfield(L, 3, str); } } else lua_pushnil(L); return 1; } static int pllua_trigger_newindex(lua_State *L) { pllua_trigger *obj = pllua_checktrigger(L, 1); const char *str = luaL_checkstring(L, 2); luaL_checkany(L, 3); lua_settop(L, 3); lua_getuservalue(L, 1); /* index 4 */ if (strcmp(str, "row") != 0) luaL_error(L, "cannot modify field trigger.%s", str); if (!TRIGGER_FIRED_FOR_ROW(obj->td->tg_event)) luaL_error(L, "trigger row can only be modified in a per-row trigger"); /* * what we're assigning must be nil, or convertible to the correct type, or * a table. Simplest approach is just feed it to the type constructor if * it's not nil. If it _is_ nil, we have to store some random other value * (we choose "false") because nil values aren't allowed. */ if (!lua_isnil(L, 3)) { pllua_trigger_get_typeinfo(L, obj, 4); lua_pushvalue(L, 3); lua_call(L, 1, 1); } else lua_pushboolean(L, false); /* * at this point, stack top should be a value of a suitable type * * "row" corresponds to "new" for insert/update triggers or "old" for delete, * we don't put "row" in the cache so update only the real name */ if (TRIGGER_FIRED_BY_DELETE(obj->td->tg_event)) lua_setfield(L, 4, "old"); else lua_setfield(L, 4, "new"); obj->modified = true; return 0; } static Datum pllua_trigger_copytuple(lua_State *L, Datum val, Oid tableoid) { volatile Datum res; PLLUA_TRY(); { HeapTupleHeader htup = (HeapTupleHeader) DatumGetPointer(val); HeapTupleData tuple; tuple.t_len = HeapTupleHeaderGetDatumLength(htup); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_tableOid = tableoid; tuple.t_data = htup; res = PointerGetDatum(heap_copytuple(&tuple)); } PLLUA_CATCH_RETHROW(); return res; } /* * "nret" return values are on the stack ending at the current stack top. * * "nd" indexes a trigger object. * * We must return as a pointer datum a HeapTuple (NOT a HeapTupleHeader) * which is the result of heap_copytuple in the caller's memory context. * Or we can return PointerGetDatum(NULL) to suppress the operation. */ Datum pllua_return_trigger_result(lua_State *L, int nret, int nd) { pllua_trigger *obj = pllua_checktrigger(L, nd); Datum retval = PointerGetDatum(NULL); TriggerEvent ev = obj->td->tg_event; int retindex = lua_gettop(L); const char *fieldname = TRIGGER_FIRED_BY_DELETE(ev) ? "old" : "new"; pllua_datum *d; /* no point doing anything fancy for these cases */ if (!TRIGGER_FIRED_FOR_ROW(ev) || TRIGGER_FIRED_AFTER(ev)) return retval; if (nret < 0 || nret > 1) luaL_error(L, "invalid number of results from trigger"); /* trigger returned an explicit nil */ if (nret == 1 && lua_isnil(L, retindex)) return PointerGetDatum(NULL); /* pick the default tuple to return */ if (TRIGGER_FIRED_BY_UPDATE(ev)) retval = PointerGetDatum(obj->td->tg_newtuple); else retval = PointerGetDatum(obj->td->tg_trigtuple); /* * if no return result and the trigger object was not modified, just return * the default tuple. Note that we have to check whether the existing tuple * was exploded in-place (which does not call __newindex) so obj->modified * can't be trusted completely, it only tells us whether the row has been * replaced wholesale. */ if (nret == 0) { lua_getuservalue(L, nd); pllua_trigger_get_typeinfo(L, obj, -1); switch (lua_getfield(L, -2, fieldname)) { /* if it's not even in the cache, it can't have been modified */ case LUA_TNIL: return retval; /* check for dummied-out "nil" */ case LUA_TBOOLEAN: if (!lua_toboolean(L, -1)) return PointerGetDatum(NULL); break; default: break; } d = pllua_todatum(L, -1, -2); if (!d) luaL_error(L, "incorrect type in trigger.row on return from trigger"); /* * newindex func has already built us a tuple of the correct form, but * it's possible that the user subsequently exploded it by assigning to * it element-wise. If so, we just leave it on the stack as if it was a * function return value and drop out to the general case. * * But if it's unmodified, we can just copy it out. */ if (!d->modified) { if (!obj->modified) { /* * If the user didn't replace or modify the tuple, it must be * the original one, so no need to copy */ return retval; } return pllua_trigger_copytuple(L, d->value, obj->td->tg_relation->rd_id); } retindex = lua_gettop(L); nret = 1; } else if (!obj->modified) { /* * Check whether the return value is raw-equal to the original unmodified * and unexploded val. */ lua_getuservalue(L, nd); pllua_trigger_get_typeinfo(L, obj, -1); lua_getfield(L, -2, fieldname); if (lua_rawequal(L, -1, retindex)) { d = pllua_todatum(L, -1, -2); if (!d) luaL_error(L, "incorrect type in trigger.row on return from trigger"); if (!d->modified) return retval; /* user returned the row unchanged */ } lua_pop(L, 3); } /* * no short cuts: take the value at retindex, push it through the value * constructor, and return it as a new tuple */ lua_getuservalue(L, nd); pllua_trigger_get_typeinfo(L, obj, -1); lua_pushvalue(L, -1); lua_pushvalue(L, retindex); lua_call(L, 1, 1); d = pllua_todatum(L, -1, -2); if (!d) luaL_error(L, "incorrect type on return from trigger"); return pllua_trigger_copytuple(L, d->value, obj->td->tg_relation->rd_id); } int pllua_push_trigger_args(lua_State *L, TriggerData *td) { char **tgargs = td->tg_trigger->tgargs; int nargs = td->tg_trigger->tgnargs; int i; for (i = 0; i < nargs; ++i) lua_pushstring(L, tgargs[i]); return nargs; } static struct luaL_Reg triggerobj_keys[] = { { "new", pllua_trigger_get_new }, { "old", pllua_trigger_get_old }, { "name", pllua_trigger_get_name }, { "when", pllua_trigger_get_when }, { "operation", pllua_trigger_get_operation }, { "level", pllua_trigger_get_level }, { "relation", pllua_trigger_get_relation }, { NULL, NULL } }; static struct luaL_Reg triggerobj_mt[] = { { "__index", pllua_trigger_index }, { "__newindex", pllua_trigger_newindex }, { NULL, NULL } }; /* * For event triggers we don't bother doing anything fancy */ static int pllua_evtrigger_index(lua_State *L) { pllua_event_trigger *obj = pllua_checkevtrigger(L, 1); const char *str = luaL_checkstring(L, 2); lua_settop(L, 2); if (strcmp(str, "event") == 0) lua_pushstring(L, obj->etd->event); else if (strcmp(str, "tag") == 0) lua_pushstring(L, obj->etd->tag); else lua_pushnil(L); return 1; } static struct luaL_Reg evtriggerobj_mt[] = { { "__index", pllua_evtrigger_index }, { NULL, NULL } }; int pllua_open_trigger(lua_State *L) { pllua_newmetatable(L, PLLUA_TRIGGER_OBJECT, triggerobj_mt); lua_newtable(L); luaL_setfuncs(L, triggerobj_keys, 0); lua_setfield(L, -2, "_keys"); lua_pop(L,1); pllua_newmetatable(L, PLLUA_EVENT_TRIGGER_OBJECT, evtriggerobj_mt); lua_pop(L,1); lua_pushboolean(L, 1); return 1; } pllua-ng-REL_2_0_4/src/trusted.c000066400000000000000000000524231347047754200165500ustar00rootroot00000000000000/* trusted.c */ #include "pllua.h" /* from init.c */ extern bool pllua_do_install_globals; /* * Trusted versions or wrappers for functionality that we need to restrict in a * trusted interpreter. * */ /* * this defines the trusted subset of the "os" package (installed as * "trusted.os" in the outer environment) */ static struct luaL_Reg trusted_os_funcs[] = { { "date", NULL }, { "clock", NULL }, { "time", NULL }, { "difftime", NULL }, { NULL, NULL } }; static int pllua_open_trusted_os(lua_State *L) { const luaL_Reg *p; lua_getglobal(L, "os"); luaL_newlibtable(L, trusted_os_funcs); for (p = trusted_os_funcs; p->name; ++p) { lua_getfield(L, -2, p->name); lua_setfield(L, -2, p->name); } return 1; } /* * load(chunk[,chunkname[,mode[,env]]]) * * Wrapper must force "mode" to be "t" to disallow loading binary chunks. Also * must force "env" to be the sandbox env if not supplied by the caller. * * Punts to _G.load after munging the args. */ static int pllua_t_load(lua_State *L) { int nargs = lua_gettop(L); if (nargs < 4) { lua_settop(L, 3); nargs = 4; lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX); } lua_pushstring(L, "t"); lua_replace(L, 3); lua_getglobal(L, "load"); lua_insert(L, 1); lua_call(L, nargs, LUA_MULTRET); return lua_gettop(L); } /* * user-facing "require" function */ static void pllua_t_require_findloader(lua_State *L, int nd, const char *name); static int pllua_t_require(lua_State *L) { const char *name = luaL_checkstring(L, 1); lua_settop(L, 1); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_LOADED); lua_getfield(L, 2, name); /* LOADED[name] */ if (lua_toboolean(L, -1)) /* is it there? */ return 1; /* package is already loaded */ lua_pop(L, 1); /* remove 'getfield' result */ if (lua_getfield(L, lua_upvalueindex(1), "searchers") != LUA_TTABLE) luaL_error(L, "'package.searchers' must be a table"); pllua_t_require_findloader(L, -1, name); lua_pushstring(L, name); /* pass name as argument to module loader */ lua_insert(L, -2); /* name is 1st argument (before search data) */ lua_call(L, 2, 1); /* run loader to load module */ /* * If the module returned nil, see if it stored a non-nil value in the * loaded table itself (older deprecated protocol). If not, use "true". */ if (lua_isnil(L, -1) && lua_getfield(L, 2, name) == LUA_TNIL) lua_pushboolean(L, 1); /* use true as result */ lua_pushvalue(L, -1); lua_setfield(L, 2, name); /* LOADED[name] = returned value */ return 1; } /* * "require" function helper */ static void pllua_t_require_findloader(lua_State *L, int nd, const char *name) { int i; luaL_Buffer msg; /* to build error message */ nd = lua_absindex(L, nd); luaL_buffinit(L, &msg); /* iterate over available searchers to find a loader */ for (i = 1; ; ++i) { if (lua_rawgeti(L, nd, i) == LUA_TNIL) { lua_pop(L, 1); /* remove nil */ luaL_pushresult(&msg); /* create error message */ luaL_error(L, "module '%s' not found:%s", name, lua_tostring(L, -1)); } lua_pushstring(L, name); lua_call(L, 1, 2); /* call it */ if (lua_isfunction(L, -2)) /* did it find a loader? */ return; /* module loader found */ else if (lua_isstring(L, -2)) /* searcher returned error message? */ { lua_pop(L, 1); /* remove extra return */ luaL_addvalue(&msg); /* concatenate error message */ } else lua_pop(L, 2); /* remove both returns */ } } /* * searcher functions are called as * * searcher(name) returns func,arg * */ static int pllua_package_preload_search(lua_State *L) { /* preload searcher works entirely inside the sandbox */ const char *name = luaL_checkstring(L, 1); lua_getfield(L, lua_upvalueindex(1), "preload"); lua_pushstring(L, name); if (lua_gettable(L, -2) == LUA_TNIL) { lua_pushfstring(L, "\n\tno field package.preload['%s']", name); return 1; } lua_pushnil(L); return 2; } static int pllua_package_allowed_search(lua_State *L) { /* * allowed searcher works outside the sandbox; the sandbox can't see its * own "allow" list */ const char *name = luaL_checkstring(L, 1); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_ALLOW); lua_pushstring(L, name); if (lua_gettable(L, -2) == LUA_TNIL) { lua_pushfstring(L, "\n\tno module '%s' in list of allowed modules", name); return 1; } lua_pushnil(L); return 2; } static int pllua_open_trusted_package(lua_State *L) { lua_newtable(L); lua_pushvalue(L, -1); lua_pushcclosure(L, pllua_t_require, 1); lua_setfield(L, -2, "require"); lua_newtable(L); lua_pushvalue(L, -1); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_LOADED); lua_setfield(L, -2, "loaded"); lua_newtable(L); lua_setfield(L, -2, "preload"); lua_newtable(L); /* first entry in searchers list is the preload searcher */ lua_pushvalue(L, -2); lua_pushcclosure(L, pllua_package_preload_search, 1); lua_seti(L, -2, 1); /* second entry in searchers list is the permitted-package searcher */ /* this operates outside the sandbox so we don't close it over sandbox.package */ lua_pushcfunction(L, pllua_package_allowed_search); lua_seti(L, -2, 2); lua_setfield(L, -2, "searchers"); return 1; } /* * These funcs appear as trusted.func outside the sandbox, for management * purposes. * * trusted.require("module", ["newname"], "mode") * -- as if _G.newname = module was done inside the sandbox (the * actual 'require "module"' is done outside) * * trusted.allow("module", ["newname"], "mode", "globname") * -- allow require "newname" to work inside the sandbox * note that "module" WILL be loaded immediately (outside) * * trusted.remove("newname","globname") * -- remove the module from the sandbox; INEFFECTIVE if code has already * been run inside. * * modules "require"d outside of the sandbox are not exposed as global * variables inside it unless specified with require or allow. However, * anything a module stores inside itself, including references to other * modules, will be accessible if the module is. * * CAVEAT SUPERUSER: it will be very hard to ensure that any given loaded * module doesn't expose the real global table, its functions, or dangerous * packages to the untrusted code. */ static int pllua_bind_one_value(lua_State *L) { lua_pushvalue(L, lua_upvalueindex(1)); return 1; } static int pllua_bind_one_call(lua_State *L) { int i; lua_settop(L, 0); for (i = 1; !lua_isnone(L, lua_upvalueindex(i)); ++i) { if (i >= 10 && (i % 10) == 0) luaL_checkstack(L, 20, NULL); lua_pushvalue(L, lua_upvalueindex(i)); } if (i < 2) return 0; lua_call(L, i-2, LUA_MULTRET); return lua_gettop(L); } /* * f(modefunc,requirefunc,modulename) * = return modefunc(requirefunc(modulename)) * * This does the actual out-of-sandbox require, it's split into its own * function so that we can wrap it up as a closure for deferred execution */ static int pllua_do_trusted_require(lua_State *L) { lua_settop(L, 3); lua_call(L, 1, 1); lua_call(L, 1, 1); return 1; } /* * _allow(modname,newname,mode,global,load_now) */ static int pllua_trusted_allow(lua_State *L) { bool load_now = false; lua_settop(L, 5); luaL_checkstring(L, 1); luaL_optstring(L, 2, NULL); if (lua_isnil(L, 2)) { lua_pushvalue(L, 1); lua_replace(L, 2); } if (lua_type(L, 4) == LUA_TBOOLEAN) { if (lua_toboolean(L, 4)) lua_pushvalue(L, 2); else lua_pushnil(L); lua_replace(L, 4); } else luaL_optstring(L, 4, NULL); if (!lua_isnil(L, 4) || lua_toboolean(L, 5)) load_now = true; if (!lua_isfunction(L, 3)) { const char *mode = luaL_optstring(L, 3, "proxy"); lua_getfield(L, lua_upvalueindex(2), mode); if (!lua_isfunction(L, -1)) luaL_error(L, "trusted.modes value is not a function"); lua_replace(L, 3); } lua_pushcfunction(L, pllua_do_trusted_require); lua_pushvalue(L, 3); lua_pushvalue(L, lua_upvalueindex(3)); /* _G.require */ lua_pushvalue(L, 1); if (load_now) { lua_call(L, 3, 1); lua_pushvalue(L, -1); lua_pushcclosure(L, pllua_bind_one_value, 1); } else lua_pushcclosure(L, pllua_bind_one_call, 4); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_ALLOW); lua_pushvalue(L, 2); lua_pushvalue(L, -3); lua_rawset(L, -3); lua_pop(L, 1); if (lua_isnil(L, 4)) return 0; lua_pop(L, 1); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_LOADED); lua_pushvalue(L, 2); lua_pushvalue(L, -3); lua_rawset(L, -3); lua_pop(L, 1); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX); lua_pushvalue(L, 4); lua_pushvalue(L, -3); lua_rawset(L, -3); lua_pop(L, 1); return 0; } static int pllua_trusted_remove(lua_State *L) { lua_settop(L, 2); luaL_checkstring(L, 1); if (lua_type(L, 2) == LUA_TBOOLEAN) { if (lua_toboolean(L, 2)) lua_pushvalue(L, 1); else lua_pushnil(L); lua_replace(L, 2); } else luaL_optstring(L, 2, NULL); /* kill sandbox's _G.globname */ lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX); lua_pushvalue(L, 2); lua_pushnil(L); lua_rawset(L, -3); /* kill ALLOW and LOADED entries for modname */ lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_ALLOW); lua_pushvalue(L, 1); lua_pushnil(L); lua_rawset(L, -3); lua_rawgetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_LOADED); lua_pushvalue(L, 1); lua_pushnil(L); lua_rawset(L, -3); return 0; } /* * upvalue 1 is our own closure, upvalue 2 is the memo table */ static int pllua_trusted_mode_copy_inner(lua_State *L) { lua_settop(L, 1); lua_pushvalue(L, 1); if (lua_rawget(L, lua_upvalueindex(2)) != LUA_TNIL) return 1; lua_pop(L, 1); lua_newtable(L); /* slot 2 */ lua_pushvalue(L, 1); lua_pushvalue(L, 2); lua_rawset(L, lua_upvalueindex(2)); /* * We intentionally raw-iterate rather than pairs()ing */ lua_pushnil(L); while (lua_next(L, 1)) { /* ... key val */ lua_pushvalue(L, -2); lua_insert(L, -2); /* ... key key val */ if (lua_type(L, -1) == LUA_TTABLE) { lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, -2); lua_call(L, 1, 1); } lua_rawset(L, 2); /* ... key */ } return 1; } static int pllua_trusted_mode_scopy(lua_State *L) { lua_settop(L, 1); lua_newtable(L); /* slot 2 */ /* * We intentionally raw-iterate rather than pairs()ing */ lua_pushnil(L); while (lua_next(L, 1)) { /* ... key val */ lua_pushvalue(L, -2); lua_insert(L, -2); /* ... key key val */ lua_rawset(L, 2); /* ... key */ } return 1; } static int pllua_trusted_mode_direct(lua_State *L) { lua_settop(L, 1); return 1; } /* * Proxy a function call. * * Upvalue 1 is the real function to call. * Upvalue 2 is the value to sub for the first arg (self). */ static int pllua_trusted_mode_proxy_wrap(lua_State *L) { lua_pushvalue(L, lua_upvalueindex(2)); if (lua_gettop(L) > 1) lua_replace(L, 1); lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, 1); lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); return lua_gettop(L); } /* * Common code for metatable handling between "proxy" and "sproxy" modes */ static void pllua_trusted_mode_proxy_metatable(lua_State *L, int ot, int mt) { /* * Logic for metatables: * * __index: * always points to the old table, whether or not the old * metatable has it * __newindex: * Points to the old table iff the old metatable has a * __newindex entry, otherwise is not set * __call: * wrapped as a function call if present * __metatable: * copied if present, otherwise set to true * any other key: * just copied, since we can't hope to guess the semantics * */ if (lua_getmetatable(L, ot)) { lua_pushnil(L); while (lua_next(L, -2)) { const char *keyname = lua_tostring(L, -2); /* metatab key val */ if (strcmp(keyname, "__index") == 0) lua_pop(L, 1); else if (strcmp(keyname, "__newindex") == 0) { lua_pushvalue(L, -1); lua_setfield(L, mt, keyname); lua_pop(L, 1); } else if (strcmp(keyname, "__call") == 0) { lua_pushvalue(L, 1); lua_pushcclosure(L, pllua_trusted_mode_proxy_wrap, 2); lua_setfield(L, mt, keyname); } else { lua_pushvalue(L, -2); lua_insert(L, -2); /* ... key key val */ lua_rawset(L, mt); } /* metatab key */ } lua_pop(L, 1); } } static int pllua_trusted_mode_sproxy(lua_State *L) { lua_settop(L, 1); if (lua_type(L, 1) != LUA_TTABLE) return 1; lua_newtable(L); /* slot 2 */ lua_newtable(L); /* slot 3 for now */ lua_pushboolean(L, 1); lua_setfield(L, -2, "__metatable"); pllua_trusted_mode_proxy_metatable(L, 1, 3); lua_pushvalue(L, 1); lua_setfield(L, -2, "__index"); lua_setmetatable(L, 2); return 1; } static int pllua_trusted_mode_proxy_inner(lua_State *L) { lua_settop(L, 1); if (lua_type(L, 1) != LUA_TTABLE) return 1; lua_pushvalue(L, 1); if (lua_rawget(L, lua_upvalueindex(2)) != LUA_TNIL) return 1; lua_pop(L, 1); lua_newtable(L); /* slot 2 */ lua_pushvalue(L, 1); lua_pushvalue(L, 2); lua_rawset(L, lua_upvalueindex(2)); lua_newtable(L); /* slot 3 for now */ lua_pushboolean(L, 1); lua_setfield(L, -2, "__metatable"); pllua_trusted_mode_proxy_metatable(L, 1, 3); lua_pushvalue(L, 1); lua_setfield(L, -2, "__index"); lua_setmetatable(L, 2); /* * We intentionally raw-iterate rather than pairs()ing */ lua_pushnil(L); while (lua_next(L, 1)) { /* ... key val */ if (lua_type(L, -1) == LUA_TTABLE) { lua_pushvalue(L, -2); lua_insert(L, -2); /* ... key key val */ lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, -2); lua_call(L, 1, 1); lua_rawset(L, 2); } else lua_pop(L, 1); /* ... key */ } return 1; } static int pllua_trusted_mode_outer(lua_State *L) { lua_settop(L, 1); if (lua_type(L, 1) != LUA_TTABLE) return 1; lua_pushnil(L); lua_newtable(L); if (lua_toboolean(L, lua_upvalueindex(1))) lua_pushcclosure(L, pllua_trusted_mode_proxy_inner, 2); else lua_pushcclosure(L, pllua_trusted_mode_copy_inner, 2); lua_pushvalue(L, -1); lua_setupvalue(L, -2, 1); lua_insert(L, 1); lua_call(L, 1, 1); return 1; } static struct luaL_Reg trusted_modes_funcs[] = { { "direct", pllua_trusted_mode_direct }, { "scopy", pllua_trusted_mode_scopy }, { "sproxy", pllua_trusted_mode_sproxy }, { NULL, NULL } }; static struct luaL_Reg trusted_funcs[] = { { "_allow", pllua_trusted_allow }, { "remove", pllua_trusted_remove }, { NULL, NULL } }; /* * This is called with the first arg being the "trusted" module table */ static const char *trusted_lua = "local lib = ...\n" "local unpack = table.unpack or unpack\n" "local type, ipairs = type, ipairs\n" "local allow = lib._allow\n" #if LUA_VERSION_NUM >= 502 "_ENV = nil\n" #endif "function lib.allow(mod,new,mode,glob,immed)\n" " if type(mod)==\"string\" then\n" " allow(mod,new,mode,glob,immed)\n" " elseif type(mod)==\"table\" then\n" " for i,v in ipairs(mod) do\n" " local e_mod, e_new, e_mode, e_glob, e_immed\n" " = unpack(type(v)==\"table\" and v or { v },1,5)\n" " if e_glob == nil then e_glob = glob end\n" " if e_immed == nil then e_immed = immed end\n" " allow(e_mod, e_new, e_mode or mode, e_glob, e_immed)\n" " end\n" " end\n" "end\n" "function lib.require(mod,new,mode)\n" " lib.allow(mod,new,mode,true)\n" "end\n" ; static struct luaL_Reg sandbox_funcs[] = { /* from this file */ { "load", pllua_t_load }, /* "require" is set from package.require */ {NULL, NULL} }; /* * Whitelist the standard lua globals that we allow into the sandbox. */ struct global_info { const char *name; const char *libname; }; static struct global_info sandbox_lua_globals[] = { /* base lib */ { "assert", NULL }, { "collectgarbage", NULL }, { "error", NULL }, { "getmetatable", NULL }, { "ipairs", NULL }, { "next", NULL }, { "pairs", NULL }, { "rawequal", NULL }, { "rawlen", NULL }, { "rawget", NULL }, { "rawset", NULL }, { "select", NULL }, { "setmetatable", NULL }, { "tonumber", NULL }, { "tostring", NULL }, { "type", NULL }, { "_VERSION", NULL }, { "_PLVERSION", NULL }, { "_PLREVISION", NULL }, { "_PL_LOAD_TIME", NULL }, { "_PL_IDENT", NULL }, { "_PG_VERSION", NULL }, { "_PG_VERSION_NUM", NULL }, { NULL, "pllua.print" }, { "print", NULL }, { NULL, "pllua.error" }, { "pcall", NULL }, { "xpcall", NULL }, { "lpcall", NULL }, { NULL, "pllua.trusted.package" }, { "require", NULL }, { NULL, NULL } }; /* * List of packages to expose to the sandbox by default * * "mode" should be either "copy" or "proxy" for anything that might get used * by unsandboxed code. "direct" is ok for the trusted OS library because that * is not used outside the sandbox. */ struct module_info { const char *name; const char *newname; const char *mode; const char *globname; }; static struct module_info sandbox_packages_early[] = { { "coroutine", NULL, "copy", "coroutine" }, { "string", NULL, "copy", "string" }, #if LUA_VERSION_NUM == 503 { "utf8", NULL, "copy", "utf8" }, #endif { "table", NULL, "copy", "table" }, { "math", NULL, "copy", "math" }, { "pllua.trusted.os", "os", "direct", "os" }, { "pllua.trusted.package", "package", "direct", "package" }, { "pllua.error", NULL, "copy", NULL }, { NULL, NULL } }; static struct module_info sandbox_packages_late[] = { { "pllua.spi", NULL, "proxy", "spi" }, { "pllua.pgtype", NULL, "proxy", "pgtype" }, { "pllua.elog", NULL, "copy", NULL }, { "pllua.numeric", NULL, "copy", NULL }, { "pllua.jsonb", NULL, "copy", NULL }, { "pllua.time", NULL, "copy", NULL }, { NULL, NULL } }; /* * This isn't really a module but handles the late initialization phase. */ int pllua_open_trusted_late(lua_State *L) { const struct module_info *np; lua_settop(L, 0); luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); lua_getfield(L, -1, "pllua.trusted"); lua_replace(L, 1); for (np = sandbox_packages_late; np->name; ++np) { lua_getfield(L, 1, "_allow"); lua_pushstring(L, np->name); if (np->newname) lua_pushstring(L, np->newname); else lua_pushnil(L); lua_pushstring(L, np->mode); if (np->globname && pllua_do_install_globals) lua_pushstring(L, np->globname); else lua_pushnil(L); lua_pushboolean(L, 1); lua_call(L, 5, 0); } lua_pushvalue(L, 1); return 1; } int pllua_open_trusted(lua_State *L) { const struct global_info *p; const struct module_info *np; lua_settop(L,0); /* create the package table itself: index 1 */ luaL_newlibtable(L, trusted_funcs); lua_pushvalue(L, 1); lua_newtable(L); luaL_setfuncs(L, trusted_modes_funcs, 0); lua_pushboolean(L, 0); lua_pushcclosure(L, pllua_trusted_mode_outer, 1); lua_setfield(L, -2, "copy"); lua_pushboolean(L, 1); lua_pushcclosure(L, pllua_trusted_mode_outer, 1); lua_setfield(L, -2, "proxy"); lua_pushvalue(L, -1); lua_setfield(L, 1, "modes"); lua_getglobal(L, "require"); luaL_setfuncs(L, trusted_funcs, 3); if (luaL_loadbuffer(L, trusted_lua, strlen(trusted_lua), "trusted.lua") == LUA_OK) { lua_pushvalue(L, 1); lua_call(L, 1, 0); } else lua_error(L); /* create the "permitted package" table */ lua_newtable(L); lua_pushvalue(L, -1); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX_ALLOW); lua_setfield(L, 1, "permit"); /* create the infrastructure of the sandbox module system */ luaL_requiref(L, "pllua.trusted.package", pllua_open_trusted_package, 0); lua_pop(L, 1); /* create the trusted sandbox: index 2 */ lua_newtable(L); lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); lua_pushglobaltable(L); for (p = sandbox_lua_globals; p->name || p->libname; ++p) { if (p->libname) { lua_getfield(L, -2, p->libname); lua_replace(L, -2); } if (p->name) { lua_getfield(L, -1, p->name); lua_setfield(L, 2, p->name); } } lua_pop(L, 2); lua_pushvalue(L, 2); lua_setfield(L, 2, "_G"); luaL_setfuncs(L, sandbox_funcs, 0); lua_pushvalue(L, 2); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_TRUSTED_SANDBOX); lua_pushvalue(L, 2); lua_setfield(L, 1, "sandbox"); /* proxy metatable for the sandbox */ lua_newtable(L); lua_pushvalue(L, 2); lua_setfield(L, -2, "__index"); lua_rawsetp(L, LUA_REGISTRYINDEX, PLLUA_SANDBOX_META); /* create the minimal trusted "os" library */ luaL_requiref(L, "pllua.trusted.os", pllua_open_trusted_os, 0); lua_pop(L, 1); /* * require standard modules into the sandbox */ lua_getfield(L, 1, "_allow"); for (np = sandbox_packages_early; np->name; ++np) { lua_pushvalue(L, -1); lua_pushstring(L, np->name); if (np->newname) lua_pushstring(L, np->newname); else lua_pushnil(L); lua_pushstring(L, np->mode); if (np->globname) lua_pushstring(L, np->globname); else lua_pushnil(L); lua_pushboolean(L, 1); lua_call(L, 5, 0); } lua_pop(L, 1); /* * Ugly hack; we can't tell reliably at compile time whether the lua * library we're linked to enables bit32 or not. So just check whether * it exists and if so, run _allow for it as a special case. */ #ifdef LUA_BITLIBNAME lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); lua_getfield(L, -1, LUA_BITLIBNAME); if (!lua_isnil(L, -1)) { lua_getfield(L, 1, "_allow"); lua_pushstring(L, LUA_BITLIBNAME); lua_pushnil(L); lua_pushstring(L, "copy"); lua_pushboolean(L, 1); lua_call(L, 4, 0); } lua_pop(L, 2); #endif /* * global "string" is the metatable for all string objects. We don't * want the sandbox to be able to get it via getmetatable("") */ lua_pushstring(L, ""); if (lua_getmetatable(L, -1)) { lua_pushboolean(L, 1); lua_setfield(L, -2, "__metatable"); lua_pop(L, 2); } else lua_pop(L, 1); /* done */ lua_pushvalue(L, 1); return 1; } pllua-ng-REL_2_0_4/tools/000077500000000000000000000000001347047754200152555ustar00rootroot00000000000000pllua-ng-REL_2_0_4/tools/doc.sh000077500000000000000000000007701347047754200163650ustar00rootroot00000000000000#!/bin/sh printf "\n" for fn; do case "$fn" in *.css) printf '\n";; *.js) printf '\n";; *.meta) cat -- "$fn";; esac done printf "\n" for fn; do shift case "$fn" in *.md) set -- "$@" "$fn";; *.html) cat -- "$fn";; esac done cmark "$@" || exit 1 printf "\n" exit 0 pllua-ng-REL_2_0_4/tools/errcodes.lua000066400000000000000000000004151347047754200175660ustar00rootroot00000000000000-- errcodes.lua local fn = ... for line in io.lines(fn) do if line:match("^[^#%s]") and not line:match("^Section:") then local f1,f2,f3,f4 = line:match("^(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s*$") if f4 then io.write("{\n \"",f4,"\", ",f3,"\n},\n") end end end pllua-ng-REL_2_0_4/tools/functable.lua000066400000000000000000000011511347047754200177210ustar00rootroot00000000000000-- functable.lua local funcnames = {} local matchfuncs = { pllua_pushcfunction = true, pllua_cpcall = true, pllua_initial_protected_call = true, pllua_register_cfunc = true } for i,fn in ipairs{...} do for line in io.lines(fn) do for fn1,pos in line:gmatch("(pllua_[%w_]+)%(()") do if matchfuncs[fn1] then local fn2 = line:match("%s*[%w._]+%s*,%s*(pllua_[%w_]+)",pos) if fn2 ~= nil then funcnames[fn2] = true end end end end end local out = {} for k,_ in pairs(funcnames) do out[1+#out] = k end table.sort(out) for i,v in ipairs(out) do io.write("PLLUA_DECL_CFUNC(",v,")\n") end pllua-ng-REL_2_0_4/tools/logo.lua000066400000000000000000000074151347047754200167270ustar00rootroot00000000000000-- logo.lua -- make_encoder([separator [,blocksize [,alphabet]]]) -- -- Given an alphabet (or default), return an encoding function, -- which is callable as enc(str) local function make_encoder(sep,blksz,b64a) local char, byte = string.char, string.byte local concat = table.concat local unpack = table.unpack or unpack local fdiv = function(p,q) return math.floor(p/q) end -- cope with missing // -- default separator sep = sep or "\n" -- default blocksize blksz = fdiv((blksz or 76), 4) assert(blksz > 0, "invalid blocksize") -- input and output block sizes local iblksz, oblksz = blksz * 3, blksz * 4 -- default b64 alphabet b64a = b64a or "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" assert(#b64a == 65, "invalid alphabet") local pad = byte(b64a, 65) local function b64chr(c) return byte(b64a, 1 + c) end -- precomputed lookup tables: -- given input bytes b1,b2,b3: -- b64a1[b1] = first output byte (from bits b1:111111xx) -- b64a2[b1][b2] = second output byte (from bits b1:xxxxxx11 b2:1111xxxx) -- b64a3[b3][b2] = third output byte (from bits b2:xxxx1111 b3:11xxxxxx) -- b64a4[b3] = fourth output byte (from bits b3:xx111111) -- local b64a1, b64a2, b64a3, b64a4 = {}, {}, {}, {} -- b64a1 is easy for b1 = 0,255 do b64a1[b1] = b64chr(fdiv(b1,4)) end -- b64a2[b1][b2] -- we take only the bottom 2 bits from b1, so there are only 4 distinct b2 tables -- we take the high 4 bits from b2 for b1 = 0,3 do local t = {} for b2 = 0,255 do t[b2] = b64chr(fdiv(b2,16) + (b1 * 16)) end b64a2[b1] = t end for b1 = 4,255 do b64a2[b1] = b64a2[b1 % 4] end -- b64a3[b3][b2] (note reversed order to keep number of tables small) -- we take only the top 2 bits from b3, so there are only 4 distinct b2 tables -- we take the low 4 bits from b2 for b3 = 0,255,64 do local t = {} for b2 = 0,255 do t[b2] = b64chr((b2 % 16)*4 + fdiv(b3,64)) end for i = 0,63 do b64a3[b3+i] = t end end -- b64a4 is easy for i = 0,255 do b64a4[i] = b64chr(i % 64) end return function(str) local chunks, chunk = {}, {} local c = 0 local len = #str for i = 1, len - (len % iblksz), iblksz do -- fastpath loop for j = 1,blksz do local k = j*4 - 3 local b1,b2,b3 = byte(str, i+k-j, i+k-j+2) chunk[k], chunk[k+1], chunk[k+2], chunk[k+3] = b64a1[b1], b64a2[b1][b2], b64a3[b3][b2], b64a4[b3] end c = c + 1 chunks[c] = char(unpack(chunk, 1, oblksz)) end chunk = {} for i = len - (len % iblksz) + 1, len, 3 do local b1,b2,b3 = byte(str, i, i+2) chunk[1+#chunk] = char( b64a1[b1], b64a2[b1][b2 or 0], b2 and b64a3[b3 or 0][b2] or pad, b3 and b64a4[b3] or pad ) end if #chunk > 0 then c = c + 1 chunks[c] = concat(chunk, "") end return concat(chunks, sep, 1, c) end end local mode,fmt,fn = ... local str = "" if mode == "-text" then for line in io.lines(fn) do str = str .. line .. "\x0D\x0A" -- canonical line endings end elseif mode == "-binary" then local file = io.open(fn, "rb") str = file:read("*a") file:close() else error("unknown mode") end if fmt:match("^-icon") then local sz = fmt:match("^-icon=(.*)") local basename = fn:match(".*/([^/]+)$") or fn local ext = fn:match("%.([^.]+)$") local mediatype = { ico = "image/x-icon", png = "image/png", gif = "image/gif", jpg = "image/jpeg" } local typ = mediatype[ext] local b64enc = make_encoder("", 120) io.write([[ ]]) elseif fmt == "-logo" then local b64enc = make_encoder("", 120) io.write([[ #logo { background-image: url("data:image/svg+xml;base64,]], b64enc(str), [["); } ]]) else error("unknown format") end pllua-ng-REL_2_0_4/tools/reorder-o.sh000077500000000000000000000014001347047754200175050ustar00rootroot00000000000000#!/bin/sh cmd="$1" shift 1 findpos() { while :; do [ "$#" -lt 2 ] && { echo "no -o option found" >&2; return 1; } [ "x$1" = "x-o" ] && { echo "$#"; return 0; } shift 1; done } # cmd x x x -o y z z z nx=$(findpos "$@") [ -z "$nx" ] && exit 1 n_before=$(expr "$#" - "$nx") n_after=$(expr "$nx" - 2) outfile=$(shift $n_before; printf '%s:' "$2") outfile="${outfile%?}" while :; do if [ "$n_before" -gt 0 ] then set -- "$@" "$1"; shift 1; n_before=$(expr "$n_before" - 1); elif [ "$n_before" -eq 0 ] then shift 2; n_before="-1"; elif [ "$n_after" -gt 0 ] then set -- "$@" "$1" shift 1 n_after=$(expr "$n_after" - 1) else exec "$cmd" "$@" "$outfile" exit 127 fi done echo "something went badly wrong" >&2; exit 1;