├── .dir-locals.el ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── regr_macos.yml │ └── regression.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── TODO ├── doc ├── building.md ├── css.css ├── endnote.md ├── intro.md ├── logo.svg ├── pllua.md ├── script.js └── template.xsl ├── expected ├── arrays.out ├── event_triggers.out ├── horology-errors.out ├── horology-errors_1.out ├── horology.out ├── jsonb.out ├── lua54.out ├── lua54_1.out ├── numerics.out ├── numerics_1.out ├── paths.out ├── pllua.out ├── pllua_old.out ├── procedures.out ├── rowdatum.out ├── spi.out ├── subxact.out ├── triggers.out ├── triggers_10.out ├── trusted.out └── types.out ├── hstore ├── Makefile ├── expected │ ├── create_ext.out │ └── hstore.out ├── hstore_pllua--1.0.sql ├── hstore_pllua.c ├── hstore_pllua.control ├── hstore_plluau--1.0.sql ├── hstore_plluau.control ├── old_inc │ └── hstore │ │ └── hstore.h └── sql │ ├── create_ext.sql │ └── hstore.sql ├── parallel_schedule ├── pllua.control ├── plluau.control ├── scripts ├── pllua--1.0--2.0.sql ├── pllua--2.0.sql ├── plluau--1.0--2.0.sql └── plluau--2.0.sql ├── serial_schedule ├── sql ├── arrays.sql ├── event_triggers.sql ├── horology-errors.sql ├── horology.sql ├── jsonb.sql ├── lua54.sql ├── numerics.sql ├── paths.sql ├── pllua.sql ├── pllua_old.sql ├── procedures.sql ├── rowdatum.sql ├── spi.sql ├── subxact.sql ├── triggers.sql ├── triggers_10.sql ├── trusted.sql └── types.sql ├── src ├── compat.lua ├── compile.c ├── datum.c ├── elog.c ├── error.c ├── exec.c ├── exports.x ├── globals.c ├── init.c ├── jsonb.c ├── numeric.c ├── objects.c ├── paths.c ├── plerrcodes_old.h ├── pllua.c ├── pllua.h ├── pllua_luajit.h ├── pllua_luaver.h ├── pllua_pgver.h ├── preload.c ├── spi.c ├── time.c ├── trigger.c └── trusted.c └── tools ├── doc.sh ├── errcodes.lua ├── functable.lua ├── logo.lua ├── numeric-tests.mk ├── numeric.mk └── reorder-o.sh /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;; see also src/tools/editors/emacs.samples for more complete settings 2 | 3 | ((c-mode . ((c-basic-offset . 4) 4 | (c-file-style . "bsd") 5 | (fill-column . 78) 6 | (indent-tabs-mode . t) 7 | (tab-width . 4) 8 | (c-file-offsets (case-label . +) (label . -) (statement-case-open . +)) 9 | (eval add-hook 'before-save-hook 'delete-trailing-whitespace nil t))) 10 | (lua-mode . ((indent-tabs-mode . t) 11 | (tab-width . 4) 12 | (eval add-hook 'before-save-hook 'delete-trailing-whitespace nil t))) 13 | (sh-mode . ((indent-tabs-mode . t) 14 | (tab-width . 4))) 15 | (css-mode . ((tab-width . 4) 16 | (eval add-hook 'before-save-hook 'delete-trailing-whitespace nil t))) 17 | (dsssl-mode . ((indent-tabs-mode . nil))) 18 | (nxml-mode . ((indent-tabs-mode . nil) 19 | (eval add-hook 'before-save-hook 'delete-trailing-whitespace nil t))) 20 | (perl-mode . ((perl-indent-level . 4) 21 | (perl-continued-statement-offset . 4) 22 | (perl-continued-brace-offset . 4) 23 | (perl-brace-offset . 0) 24 | (perl-brace-imaginary-offset . 0) 25 | (perl-label-offset . -2) 26 | (indent-tabs-mode . t) 27 | (tab-width . 4) 28 | (eval add-hook 'before-save-hook 'delete-trailing-whitespace nil t))) 29 | (sgml-mode . ((fill-column . 78) 30 | (indent-tabs-mode . nil)))) 31 | 32 | ;; c-file-offsets is not marked safe by default, but you can either 33 | ;; accept the specific value given as safe always, or do something 34 | ;; like this in your .emacs to accept only the simplest offset lists 35 | ;; automatically: 36 | ;; (defun my-safe-c-file-offsets-p (alist) 37 | ;; (catch 'break 38 | ;; (and (listp alist) 39 | ;; (dolist (elt alist t) 40 | ;; (pcase elt 41 | ;; (`(,(pred symbolp) . ,(or `+ `- `++ `-- `* `/)) t) 42 | ;; (`(,(pred symbolp) . ,(or (pred null) (pred integerp))) t) 43 | ;; (`(,(pred symbolp) . [ ,(pred integerp) ]) t) 44 | ;; (_ (throw 'break nil))))))) 45 | ;; (put 'c-file-offsets 'safe-local-variable 'my-safe-c-file-offsets-p) 46 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | [*] 3 | tab_width = 4 4 | 5 | [Makefile] 6 | tab_width = 8 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # .gitattributes 2 | * whitespace=space-before-tab,trailing-space 3 | *.[chly] whitespace=space-before-tab,trailing-space,indent-with-non-tab,tabwidth=4 4 | *.[ch] linguist-language=C 5 | 6 | # Test output files that contain extra whitespace 7 | *.out -whitespace 8 | 9 | # not to be counted as code 10 | .dir-locals.el linguist-documentation 11 | .editorconfig linguist-documentation 12 | -------------------------------------------------------------------------------- /.github/workflows/regr_macos.yml: -------------------------------------------------------------------------------- 1 | name: Build on MacOS 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | tags: [ REL_* ] 7 | pull_request: 8 | branches: [ master ] 9 | 10 | jobs: 11 | build: 12 | runs-on: macos-latest 13 | 14 | defaults: 15 | run: 16 | shell: sh 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: start pg 22 | run: | 23 | brew services start postgresql 24 | 25 | - name: install lua 26 | run: | 27 | brew install lua@5.3 28 | 29 | - name: lua environment 30 | run: | 31 | (echo LUA="/usr/local/opt/lua@5.3/bin/lua" 32 | echo LUAC="/usr/local/opt/lua@5.3/bin/luac" 33 | echo LUA_INCDIR="/usr/local/opt/lua@5.3/include/lua" 34 | echo LUALIB="-L/usr/local/opt/lua@5.3/lib -llua" 35 | ) >>$GITHUB_ENV 36 | 37 | - name: build 38 | run: 39 | make && sudo -E make install 40 | 41 | - name: wait for pg 42 | run: | 43 | n=0 44 | while ! pg_isready; do [ $(( n += 1 )) -gt 10 ] && exit 1; sleep $n; done 45 | 46 | - name: test 47 | run: | 48 | time make installcheck 49 | 50 | - name: build and test hstore 51 | run: | 52 | make -C hstore && sudo -E make -C hstore install && time make -C hstore installcheck 53 | 54 | - name: show output 55 | if: always() 56 | run: | 57 | cat regression.diffs || true 58 | cat hstore/regression.diffs || true 59 | -------------------------------------------------------------------------------- /.github/workflows/regression.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - REL_* 9 | pull_request: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | defaults: 17 | run: 18 | shell: sh 19 | strategy: 20 | matrix: 21 | lua: [ 5.3.6, 5.4.4, 5.4.6, LuaJIT-43d0a1915 ] 22 | pg: [ 9.5, 9.6, 10, 11, 12, 13, 14, 15, 16, 17 ] 23 | include: 24 | - pgrepo: "" 25 | - pg: 17 26 | pgrepo: "-pgdg-snapshot" 27 | env: 28 | PG: ${{ matrix.pg }} 29 | PGREPO: ${{ matrix.pgrepo }} 30 | LUAV: ${{ matrix.lua }} 31 | steps: 32 | - uses: actions/checkout@v3 33 | - name: cleanup pg 34 | run: | 35 | sudo apt-get -y --purge --no-upgrade remove postgresql libpq-dev libpq5 postgresql-client-common postgresql-common 36 | curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - 37 | sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" >/etc/apt/sources.list.d/pgdg.list' 38 | sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg-snapshot main 17" >/etc/apt/sources.list.d/pgdg-snap.list' 39 | sudo apt-get update 40 | sudo rm -rf /var/lib/postgresql 41 | - name: install pg 42 | run: | 43 | sudo apt-get -y \ 44 | -o Dpkg::Options::=--force-confdef \ 45 | -o Dpkg::Options::=--force-confnew \ 46 | ${PGREPO:+-t "$(lsb_release -cs)$PGREPO"} \ 47 | install postgresql-${PG:?} postgresql-contrib-${PG:?} postgresql-server-dev-${PG:?} 48 | - name: lua environment 49 | run: | 50 | (case "$LUAV" in 51 | *-rc*) echo LUA_URL="http://www.lua.org/work/lua-$LUAV.tar.gz";; 52 | *) echo LUA_URL="http://www.lua.org/ftp/lua-$LUAV.tar.gz";; 53 | esac 54 | echo LUA="$PWD/work-lua/src/lua" 55 | echo LUAC="$PWD/work-lua/src/luac" 56 | echo LUA_INCDIR="$PWD/work-lua/src" 57 | echo LUALIB="-L$PWD/work-lua -lmylua" 58 | echo lua_osver="$(lsb_release -rs)" 59 | ) >>$GITHUB_ENV 60 | - name: luajit environment 61 | if: contains(env.LUAV, 'LuaJIT') 62 | run: | 63 | (echo LUAJIT="$PWD/work-lua/src/luajit" 64 | echo LUAJITC="cp" 65 | ) >>$GITHUB_ENV 66 | - name: get cached lua build 67 | id: cache-lua 68 | uses: actions/cache@v3 69 | with: 70 | path: work-lua 71 | key: ${{ runner.os }}-${{ env.lua_osver }}-000002-work-lua-${{ env.LUAV }} 72 | - name: lua install 73 | if: ${{ steps.cache-lua.outputs.cache-hit != 'true' && ! contains(env.LUAV, 'LuaJIT') }} 74 | run: | 75 | mkdir work-lua 76 | wget -O- "${LUA_URL}" | tar -zxf - --strip-components=1 -C work-lua 77 | make -C work-lua/src \ 78 | SYSCFLAGS="-DLUA_USE_POSIX -DLUA_USE_DLOPEN -ULUA_COMPAT_5_2" \ 79 | SYSLIBS="-Wl,-E -ldl" \ 80 | MYCFLAGS="-fPIC" liblua.a lua luac 81 | ln -s src/liblua.a work-lua/libmylua.a 82 | - name: luajit install 83 | if: ${{ steps.cache-lua.outputs.cache-hit != 'true' && contains(env.LUAV, 'LuaJIT') }} 84 | run: | 85 | mkdir work-lua 86 | wget -O- https://github.com/LuaJIT/LuaJIT/archive/"${LUAV#LuaJIT-}".tar.gz \ 87 | | tar -zxf - --strip-components=1 -C work-lua 88 | make -C work-lua/src \ 89 | XCFLAGS="-DLUAJIT_ENABLE_GC64 -DLUAJIT_ENABLE_LUA52COMPAT" \ 90 | CFLAGS="-fPIC -O3" libluajit.a luajit 91 | ln -s src/libluajit.a work-lua/libmylua.a 92 | - name: pg user 93 | run: | 94 | sudo -u postgres createuser -s "$USER" 95 | - name: build and test 96 | run: | 97 | make && sudo -E make install && time make installcheck 98 | - name: build and test hstore 99 | run: | 100 | make -C hstore && sudo -E make -C hstore install && time make -C hstore installcheck 101 | - name: show output 102 | if: always() 103 | run: | 104 | cat regression.diffs || true 105 | cat hstore/regression.diffs || true 106 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.obj 3 | *.bc 4 | *.so 5 | *.so.[0-9] 6 | *.so.[0-9].[0-9] 7 | *.sl 8 | *.sl.[0-9] 9 | *.sl.[0-9].[0-9] 10 | *.dylib 11 | *.dll 12 | *.a 13 | *.mo 14 | objfiles.txt 15 | .deps/ 16 | *.gcno 17 | *.gcda 18 | *.gcov 19 | *.gcov.out 20 | *.luac 21 | lcov.info 22 | coverage/ 23 | /results/ 24 | /hstore/results/ 25 | /pllua_functable.h 26 | /plerrcodes.h 27 | /src/.dir-locals.el 28 | /pllua.html 29 | /icon.ico 30 | /icon-*.png 31 | /icon.meta 32 | /logo.css 33 | /tmpdoc.html 34 | build*/ 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andrew Gierth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for PL/Lua # -*- mode: makefile-gmake -*- 2 | 3 | PG_CONFIG ?= pg_config 4 | 5 | # Lua specific 6 | 7 | # two values can be set here, which only apply to luajit: 8 | # -DNO_LUAJIT disables all use of luajit features 9 | # -DUSE_INT8_CDATA convert sql bigints to cdata int64_t 10 | # The latter is off by default because it has some possibly 11 | # undesirable effects on bigint handling. 12 | 13 | PLLUA_CONFIG_OPTS ?= 14 | 15 | # General 16 | LUA_INCDIR ?= /usr/local/include/lua53 17 | LUALIB ?= -L/usr/local/lib -llua-5.3 18 | LUAC ?= luac53 19 | LUA ?= lua53 20 | 21 | # LuaJIT 22 | #LUA_INCDIR = /usr/local/include/luajit-2.1 23 | #LUALIB = -L/usr/local/lib -lluajit-5.1 24 | #LUAJIT = luajit 25 | 26 | ifdef LUAJIT 27 | LUAJITC ?= $(LUAJIT) -b -g -t raw 28 | LUA = $(LUAJIT) 29 | LUAC = $(REORDER_O) $(LUAJITC) 30 | endif 31 | 32 | LUAVER = $(shell $(LUA) -e 'print(_VERSION:match("^Lua ([0-9.]+)"))') 33 | ifeq ($(filter 5.1 5.3 5.4, $(LUAVER)),) 34 | $(error failed to get valid lua version) 35 | endif 36 | 37 | # if no OBJCOPY or not needed, this can be set to true (or false) 38 | OBJCOPY ?= objcopy 39 | 40 | # We expect $(BIN_LD) -o foo.o foo.luac to create a foo.o with the 41 | # content of foo.luac as a data section (plus appropriate symbols). 42 | # GNU LD and compatible linkers (including recent clang lld) should be 43 | # fine with -r -b binary, but this does break on some ports. 44 | 45 | BIN_LD ?= $(LD) -r -b binary 46 | 47 | # If BIN_ARCH and BIN_FMT are defined, we assume LD_BINARY is broken 48 | # and do this instead. This is apparently needed for linux-mips64el, 49 | # for which BIN_ARCH=mips:isa64r2 BIN_FMT=elf64-tradlittlemips seems 50 | # to work. 51 | 52 | ifdef BIN_ARCH 53 | ifdef BIN_FMT 54 | BIN_LD = $(REORDER_O) $(OBJCOPY) -B $(BIN_ARCH) -I binary -O $(BIN_FMT) 55 | endif 56 | endif 57 | 58 | # should be no need to edit below here 59 | 60 | MODULE_big = pllua 61 | 62 | EXTENSION = pllua plluau 63 | 64 | SQL_SRC = pllua--2.0.sql pllua--1.0--2.0.sql \ 65 | plluau--2.0.sql plluau--1.0--2.0.sql 66 | DATA = $(addprefix scripts/, $(SQL_SRC)) 67 | 68 | DOC_HTML = pllua.html 69 | 70 | ifdef BUILD_DOCS 71 | DOCS = $(DOC_HTML) 72 | ifdef BUILD_ICON 73 | ICON = icon.meta 74 | endif 75 | endif 76 | 77 | objdir := src 78 | 79 | # variables like $(srcdir) and $(MAJORVERSION) are not yet set, but 80 | # are safe to use in recursively-expanded variables since they will be 81 | # set before most values are needed. Can't use them in conditionals 82 | # until after pgxs is loaded though. 83 | 84 | # version-dependent regression tests 85 | REGRESS_V10 := triggers_10 86 | REGRESS_V11 := procedures 87 | 88 | REGRESS_LUA_5.4 := lua54 89 | 90 | EXTRA_REGRESS = $(REGRESS_BY_VERSION) $(REGRESS_LUA_$(LUAVER)) 91 | 92 | define REGRESS_BY_VERSION 93 | $(strip $(foreach v,$(filter REGRESS_V%,$(.VARIABLES)), 94 | $(if $(call version_ge,$(MAJORVERSION),$(subst REGRESS_V,,$(v))), 95 | $($(v))))) 96 | endef 97 | 98 | REGRESS = --schedule=$(srcdir)/serial_schedule $(EXTRA_REGRESS) 99 | REGRESS_PARALLEL = --schedule=$(srcdir)/parallel_schedule $(EXTRA_REGRESS) 100 | 101 | REORDER_O = $(srcdir)/tools/reorder-o.sh 102 | 103 | DOC_MD = css.css script.js intro.md pllua.md building.md endnote.md 104 | 105 | DOC_SRCS = logo.css $(ICON) $(addprefix $(srcdir)/doc/, $(DOC_MD)) 106 | 107 | INCS= pllua.h pllua_pgver.h pllua_luaver.h pllua_luajit.h 108 | 109 | HEADERS= $(addprefix src/, $(INCS)) 110 | 111 | OBJS_C= compile.o datum.o elog.o error.o exec.o globals.o init.o \ 112 | jsonb.o numeric.o objects.o paths.o pllua.o preload.o spi.o \ 113 | time.o trigger.o trusted.o 114 | 115 | SRCS_C = $(addprefix $(srcdir)/src/, $(OBJS_C:.o=.c)) 116 | 117 | OBJS_LUA = compat.o 118 | 119 | SRCS_LUA = $(addprefix $(srcdir)/src/, $(OBJS_LUA:.o=.lua)) 120 | 121 | OBJS = $(addprefix src/, $(OBJS_C)) 122 | 123 | EXTRA_OBJS = $(addprefix src/, $(OBJS_LUA)) 124 | 125 | EXTRA_CLEAN = pllua_functable.h plerrcodes.h \ 126 | $(addprefix src/,$(OBJS_LUA:.o=.luac)) $(EXTRA_OBJS) \ 127 | logo.css tmpdoc.html icon-16.png icon.meta $(DOC_HTML) 128 | 129 | PG_CPPFLAGS = -I$(LUA_INCDIR) $(PLLUA_CONFIG_OPTS) 130 | 131 | SHLIB_LINK = $(EXTRA_OBJS) $(LUALIB) 132 | 133 | # if VPATH is not already set, but the makefile is not in the current 134 | # dir, then assume a vpath build using the makefile's directory as 135 | # source. PGXS will set $(srcdir) accordingly. 136 | ifndef VPATH 137 | ifneq ($(realpath $(CURDIR)),$(realpath $(dir $(firstword $(MAKEFILE_LIST))))) 138 | VPATH := $(dir $(firstword $(MAKEFILE_LIST))) 139 | endif 140 | endif 141 | 142 | mklibdir := $(if $(VPATH),$(VPATH)/tools,tools) 143 | include $(mklibdir)/numeric.mk 144 | 145 | # actually load pgxs 146 | 147 | PGXS := $(shell $(PG_CONFIG) --pgxs) 148 | include $(PGXS) 149 | 150 | # definitions that must follow pgxs 151 | 152 | ifeq ($(call version_ge,$(MAJORVERSION),9.5),) 153 | $(error unsupported PostgreSQL version) 154 | endif 155 | 156 | $(OBJS): $(addprefix $(srcdir)/src/, $(INCS)) 157 | 158 | # for a vpath build, we need src/ to exist in the build dir before 159 | # building any objects. 160 | ifdef VPATH 161 | all: vpath-mkdirs 162 | .PHONY: vpath-mkdirs 163 | $(OBJS) $(EXTRA_OBJS): | vpath-mkdirs 164 | 165 | vpath-mkdirs: 166 | $(MKDIR_P) $(objdir) 167 | endif # VPATH 168 | 169 | all: $(DOCS) 170 | 171 | # explicit deps on generated includes 172 | src/init.o: pllua_functable.h 173 | src/error.o: plerrcodes.h 174 | 175 | $(shlib): $(EXTRA_OBJS) 176 | 177 | %.luac: %.lua 178 | $(LUAC) -o $@ $< 179 | 180 | ifeq ($(PORTNAME),darwin) 181 | # Apple of course has to do its own thing when it comes to object file 182 | # format and linker options. 183 | 184 | _stub.c: 185 | touch $@ 186 | 187 | %.o: %.luac _stub.o 188 | $(LD) -r -sectcreate binary $(subst .,_,$($@ 201 | 202 | ifneq ($(call version_ge,$(MAJORVERSION),11),) 203 | 204 | #in pg 11+, we can get the server's errcodes.txt. 205 | plerrcodes.h: $(datadir)/errcodes.txt $(srcdir)/tools/errcodes.lua 206 | $(LUA) $(srcdir)/tools/errcodes.lua $(datadir)/errcodes.txt >plerrcodes.h 207 | 208 | else 209 | 210 | plerrcodes.h: $(srcdir)/src/plerrcodes_old.h 211 | cp $(srcdir)/src/plerrcodes_old.h plerrcodes.h 212 | 213 | endif 214 | 215 | installcheck-parallel: submake $(REGRESS_PREP) 216 | $(pg_regress_installcheck) $(REGRESS_OPTS) $(REGRESS_PARALLEL) 217 | 218 | logo.css: $(srcdir)/doc/logo.svg $(srcdir)/tools/logo.lua 219 | $(LUA) $(srcdir)/tools/logo.lua -text -logo $(srcdir)/doc/logo.svg >$@ 220 | 221 | # Stripped PNGs are quite a bit smaller than .ico 222 | #icon.ico: $(srcdir)/doc/logo.svg 223 | # convert -size 256x256 -background transparent $(srcdir)/doc/logo.svg \ 224 | # -format ico -define icon:auto-resize=32,16 icon.ico 225 | 226 | icon-16.png: $(srcdir)/doc/logo.svg 227 | convert -size 16x16 -background transparent $(srcdir)/doc/logo.svg \ 228 | -format png -define png:format=png32 \ 229 | -define png:exclude-chunk=all icon-16.png 230 | 231 | icon.meta: icon-16.png 232 | $(LUA) $(srcdir)/tools/logo.lua -binary -icon="16x16" icon-16.png >$@ 233 | 234 | $(DOC_HTML): $(DOC_SRCS) $(srcdir)/doc/template.xsl $(srcdir)/tools/doc.sh 235 | $(srcdir)/tools/doc.sh $(DOC_SRCS) >tmpdoc.html 236 | xsltproc --encoding utf-8 $(srcdir)/doc/template.xsl tmpdoc.html >$@ 237 | rm -- tmpdoc.html 238 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 2 | Stuff to do. 3 | ============ 4 | 5 | * Improve error reporting 6 | 7 | -------------------------------------------------------------------------------- /doc/building.md: -------------------------------------------------------------------------------- 1 | Building PL/Lua 2 | =============== 3 | 4 | GNU Make is required to build, as usual for PostgreSQL extensions. 5 | 6 | This module assumes you have already built Lua itself, either as a 7 | shared library or as an archive library with `-fPIC` (on most 8 | platforms a non-PIC archive library will not work). A shared library 9 | is recommended. 10 | 11 | PL/Lua is developed and tested against LuaJIT 2.1beta, Lua 5.3, and 12 | Lua 5.4. In the case of Lua 5.4, a runtime check is performed to 13 | ensure that version 5.4.2 or later is used, to avoid excessive stack 14 | usage from the stack-based VM of earlier 5.4 releases. 15 | 16 | Building the `pllua` module 17 | --------------------------- 18 | 19 | Lua unfortunately does not provide much in the way of infrastructure 20 | for determining build locations; accordingly, those have to be 21 | specified explicitly to build this module. The following values must 22 | be defined on the `make` command line or in the environment: 23 | 24 | + `LUA_INCDIR`\ 25 | directory containing `lua.h`, `luaconf.h`, `lualib.h` 26 | + `LUALIB`\ 27 | linker options needed to link, typically `-Lsomedir -llua-5.3` 28 | 29 | And if building with standard Lua: 30 | 31 | + `LUAC`\ 32 | name or full path of the luac binary (bytecode compiler) 33 | + `LUA`\ 34 | name or full path of the lua binary 35 | 36 | Or if building with Luajit: 37 | 38 | + `LUAJIT`\ 39 | name or full path of the luajit binary 40 | 41 | In addition, as for all PGXS modules, `PG_CONFIG` must be set to the 42 | name or full path of the `pg_config` binary corresponding to the 43 | PostgreSQL server version being compiled against, unless the correct 44 | `pg_config` is already findable via `$PATH` (which is usually not the 45 | case). 46 | 47 | Example: 48 | 49 | make PG_CONFIG=/usr/lib/postgresql/10/bin/pg_config \ 50 | LUA_INCDIR="/usr/include/lua5.3" \ 51 | LUALIB="-llua5.3" \ 52 | LUAC="luac5.3" LUA="lua5.3" install 53 | 54 | 55 | Building the `hstore_pllua` module 56 | ---------------------------------- 57 | 58 | Currently, the `hstore_pllua` module does not need `LUALIB` on most 59 | platforms (since it will reference lua functions either exported by 60 | `pllua.so` or by a library loaded by `pllua.so`). 61 | 62 | You should specify `LUALIB` if you're using a shared lua library and 63 | your platform isn't exposing symbols from one module's loaded 64 | dependencies to other modules. If you're using a shared library then 65 | specifying `LIBLUA` unnecessarily is harmless. 66 | 67 | Example: 68 | 69 | make -C hstore \ 70 | PG_CONFIG=/usr/lib/postgresql/10/bin/pg_config \ 71 | LUA_INCDIR="/usr/include/lua5.3" \ 72 | LUAC="luac5.3" LUA="lua5.3" install 73 | 74 | 75 | Building the documentation 76 | -------------------------- 77 | 78 | Specifying `BUILD_DOCS=1` will build the HTML documentation from the 79 | Markdown doc sources; this requires `cmark` and `xsltproc`. 80 | 81 | Additionally specifying `BUILD_ICON=1` will include the favicon in the 82 | HTML documentation; this requires ImageMagick's `convert` program. 83 | 84 | 85 | `VPATH` builds 86 | -------------- 87 | 88 | Both modules support building with `VPATH`, which can either be 89 | explicitly set or, if `make -f /path/to/Makefile` is used to specify a 90 | makefile outside the current directory and `VPATH` is not explicitly 91 | set, then `VPATH` will be set to the directory containing the 92 | Makefile. 93 | 94 | 95 | Luajit options 96 | -------------- 97 | 98 | `PLLUA_CONFIG_OPTS` can be used to control certain aspects of pllua's 99 | behavior when built with Luajit. 100 | 101 | + `-DNO_LUAJIT`\ 102 | disables all use of luajit features 103 | + `-DUSE_INT8_CDATA`\ 104 | convert sql bigints to cdata int64_t 105 | 106 | The latter is off by default because it has some possibly undesirable 107 | effects on bigint handling, especially when serializing to JSON. 108 | However, as long as `NO_LUAJIT` was not specified, cdata integers can be 109 | freely returned from functions or passed to SQL type constructors. 110 | 111 | Actual JIT compilation of user-supplied lua code is not affected by 112 | any of these options. 113 | 114 | 115 | Porting options 116 | --------------- 117 | 118 | If you have problems building on an unusual platform, then these 119 | options might be useful. The values shown are the defaults if any. 120 | 121 | + `BIN_LD`\ 122 | `$(LD) -r -b binary` 123 | 124 | The command `$(BIN_LD) -o file.o dir/datafile.ext` is assumed to 125 | produce `file.o` containing a data section populated with the content of 126 | `datafile.ext`, with symbols `_binary_dir_datafile_ext_start` and 127 | `_binary_dir_datafile_ext_end` bracketing the data. The default is 128 | believed to work for most GNU ld and (recent) LLVM lld targets, but it 129 | is known to fail on some non-mainstream architecture distributions. 130 | 131 | The value of `BIN_LD` can be set to any suitable equivalent command. 132 | 133 | + `OBJCOPY`\ 134 | `objcopy` 135 | 136 | The output of `BIN_LD` is passed through `OBJCOPY` to make the data 137 | section read-only, but this is a non-critical operation. If no working 138 | objcopy is available, this can be set to 'false'. 139 | 140 | + `BIN_ARCH`\ 141 | unset 142 | + `BIN_FMT`\ 143 | unset 144 | 145 | If both of these are set, then `BIN_LD` is assumed not to work, and instead 146 | the command 147 | 148 | $(OBJCOPY) -B $(BIN_ARCH) -I binary -O $(BIN_FMT) datafile.ext file.o 149 | 150 | will be used in its place. The following values have been used on 151 | linux-mips64el to work around build failures with `ld -r`: 152 | 153 | BIN_ARCH=mips:isa64r2 BIN_FMT=elf64-tradlittlemips 154 | 155 | + `LUAJITC`\ 156 | `$(LUAJIT) -b -g -t raw` 157 | 158 | On Luajit, the bytecode compile option only works if luajit has been 159 | fully installed. In test environments where only the luajit build dir 160 | is otherwise needed, the bytecode compilation step can be skipped by 161 | setting `LUAJITC="cp"`. (The bytecode compile can also be skipped in 162 | non-luajit builds by setting `LUAC='$(REORDER_O) cp'` but this is not 163 | expected to be useful.) 164 | 165 | 166 | -------------------------------------------------------------------------------- /doc/css.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | font-family: verdana, sans-serif; 6 | color: #000; 7 | background-color: #fff; 8 | } 9 | 10 | .maincolumn { 11 | margin: 0 auto; 12 | width: 95%; 13 | } 14 | @media (min-width:60em) { 15 | .maincolumn { 16 | width: 85%; 17 | } 18 | } 19 | 20 | #logo { 21 | width: 30vw; 22 | height: 30vw; 23 | max-width: 30em; 24 | max-height: 30em; 25 | float: right; 26 | margin-top: 2.5vw; 27 | margin-right: 0; 28 | margin-bottom: 1em; 29 | margin-left: 1em; 30 | background-color: inherit; 31 | background-repeat: no-repeat; 32 | background-attachment: scroll; 33 | background-size: 100% 100% 34 | } 35 | @media (min-width:35em) { 36 | #logo { 37 | margin-top: 1.75em; 38 | } 39 | } 40 | 41 | #logo a { 42 | display: inline-block; 43 | width: 100%; 44 | height: 100%; 45 | } 46 | 47 | #topContainer { 48 | border-bottom: 1px solid #666; 49 | clear: both; 50 | } 51 | 52 | #bodycontent { 53 | clear: both; 54 | } 55 | 56 | #footerContainer { 57 | margin: 3em 0 0 0; 58 | padding: 0.5em 0; 59 | border-top: 2px solid #666; 60 | background-color: #ddd; 61 | color: #000; 62 | } 63 | 64 | dd p { 65 | margin-top: 0.5em; 66 | } 67 | 68 | p, ol, ul, dl, pre { 69 | line-height: 140%; 70 | } 71 | 72 | li.tocentry-1 { 73 | margin-top: 0.5em; 74 | } 75 | 76 | dd { 77 | margin-bottom: 0.5em; 78 | } 79 | 80 | h1 { 81 | font-size: 175%; 82 | } 83 | h2 { 84 | font-size: 150%; 85 | } 86 | h3 { 87 | font-size: 120%; 88 | } 89 | h1, h2, h3 { 90 | margin-top: 1em; 91 | margin-bottom: 0.5em; 92 | } 93 | 94 | @supports ( display: flex ) { 95 | .bodycontent h1, .bodycontent h2 { 96 | display: flex; 97 | flex-direction: row; 98 | flex: none; 99 | } 100 | .bodycontent h1::after, .bodycontent h2::after { 101 | margin-left: 1em; 102 | flex: auto; 103 | align-self: center; 104 | content: "\a0"; 105 | height: 0px; 106 | } 107 | .bodycontent h1::after { 108 | border-top: 2px #ddd solid; 109 | } 110 | .bodycontent h2::after { 111 | border-top: 1px #ddd solid; 112 | } 113 | } 114 | 115 | code { 116 | font-size: 120%; 117 | } 118 | 119 | pre code { 120 | padding: 0; 121 | line-height: 150%; 122 | } 123 | 124 | .bodycontent .shortcode, .bodycontent .longcode { 125 | padding: 2px 3px; 126 | color: #000; 127 | background-color: #eee; 128 | } 129 | 130 | .footer .shortcode, .footer .longcode { 131 | padding: 2px 3px; 132 | color: #000; 133 | background-color: #ccc; 134 | } 135 | 136 | /* prevent line-breaks in short code spans */ 137 | .shortcode { 138 | white-space: pre; 139 | } 140 | 141 | .longcode { 142 | white-space: pre-wrap; 143 | } 144 | 145 | .bodycontent pre.codeblock { 146 | margin: 1em 2em; 147 | padding: 1em; 148 | border-radius: 8px; 149 | border-width: 1px; 150 | border-style: solid; 151 | color: #000; 152 | background-color: #eee; 153 | box-shadow: 3px 3px 5px #ddd; 154 | border-color: #ccc; 155 | overflow: auto; 156 | } 157 | 158 | a[rel~="external"] { 159 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAW0lEQVQYlXWQgQ0AIQgD3cmd2Mmd2Kn/RPtCfZs0JngBSmsqA36d1YfDXqsOkB27wBM0RzWgTT8wFwjFy84FVIghYucCxmcUN+RgwLX8Hk1oTnBJauc5ruDVSQ92Teh+W2MqsAAAAABJRU5ErkJggg==) center right no-repeat; 160 | padding-right: 13px; 161 | } 162 | 163 | /* eof */ 164 | -------------------------------------------------------------------------------- /doc/endnote.md: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /doc/intro.md: -------------------------------------------------------------------------------- 1 | PL/Lua Introduction 2 | =================== 3 | 4 | PL/Lua is a procedural language module for the PostgreSQL database 5 | that allows server-side functions to be written in Lua. 6 | 7 | 8 | Quick start 9 | ----------- 10 | 11 | create extension pllua; 12 | 13 | create function hello(person text) returns text language pllua as $$ 14 | return "Hello, " .. person .. ", from Lua!" 15 | $$; 16 | 17 | select hello('Fred'); 18 | hello 19 | ------------------------ 20 | Hello, Fred, from Lua! 21 | (1 row) 22 | 23 | 24 | Basic Examples 25 | -------------- 26 | 27 | ### Using `print()` for interactive diagnostics 28 | 29 | Anything passed to the `print()` function will be raised as a 30 | notification at `INFO` level, causing `psql` to display it 31 | interactively; program clients will usually just ignore non-error 32 | notices. 33 | 34 | create function print_lua_ver() returns void language pllua as $$ 35 | print(_VERSION) 36 | $$; 37 | select print_lua_ver(); 38 | INFO: Lua 5.3 39 | print_lua_ver 40 | --------------- 41 | 42 | (1 row) 43 | 44 | ### Simple arguments and results 45 | 46 | Simple scalar types (integers, floats, text, bytea, boolean) are 47 | converted to the matching Lua type, and conversely for results. 48 | 49 | create function add2(a integer, b integer) returns integer language pllua 50 | as $$ 51 | return a + b 52 | $$; 53 | 54 | Other data types are passed as userdata objects that can be converted 55 | to strings with `tostring()` or accessed via provided methods and 56 | metamethods. In particular, arrays and records are accessible in most 57 | ways as though they were Lua tables, though they're actually not. 58 | 59 | create type myrow as (a integer, b text[]); 60 | create function foo(rec myrow) returns myrow language pllua as $$ 61 | print("a is", rec.a) 62 | print("b[1] is", rec.b[1]) 63 | print("b[2] is", rec.b[2]) 64 | return { a = 123, b = {"fred","jim"} } 65 | $$; 66 | select * from foo(row(1,array['foo','bar'])::myrow); 67 | INFO: a is 1 68 | INFO: b[1] is foo 69 | INFO: b[2] is bar 70 | a | b 71 | -----+------------ 72 | 123 | {fred,jim} 73 | (1 row) 74 | 75 | ### Sum of an array 76 | 77 | create function array_sum(a integer[]) returns integer language pllua 78 | as $$ 79 | local total = 0 80 | for k,v in pairs(a) do 81 | total = total + v 82 | end 83 | return total 84 | $$; 85 | 86 | The above assume single-dimension arrays with no NULLs. A more generic 87 | method uses the array mapping function provided by the array userdata: 88 | 89 | create function array_sum(a integer[]) returns integer language pllua 90 | as $$ 91 | local total = 0 92 | a{ null = 0, 93 | map = function(v,...) total = total + v end, 94 | discard = true } 95 | return total 96 | $$; 97 | 98 | ### Returning multiple rows (SRFs) 99 | 100 | Functions that return multiple rows (i.e. `RETURNS SETOF ...`) work as 101 | coroutines; each row should be returned by passing it to 102 | `coroutine.yield`, and when done, the function should return no 103 | values. As a special case, if the function does a `return` with values 104 | before doing any yield, it is considered to return 1 row. 105 | 106 | create function val3() returns setof integer language pllua 107 | as $$ 108 | for i = 1,3 do 109 | coroutine.yield(i) 110 | end 111 | $$; 112 | select val3(); 113 | val3 114 | ------ 115 | 1 116 | 2 117 | 3 118 | (3 rows) 119 | 120 | SRFs written in PL/Lua run in value-per-call mode, so the execution of 121 | the function may be (but often will not be) interleaved with other 122 | parts of the query, depending on which part of the query the function 123 | was called from. 124 | 125 | In Lua 5.4, if execution of an SRF is aborted early due to a LIMIT 126 | clause or other form of rescan in the calling query, or if the calling 127 | portal is closed, then any `` variables (including implicit 128 | ones in `for` iterators) in the function are immediately closed. In 129 | earlier Lua versions, the coroutine and any referenced objects are 130 | subject to garbage collection at some indefinite future time. 131 | 132 | ### Simple database queries 133 | 134 | The local environment created for each function is a good place to 135 | cache prepared queries: 136 | 137 | create table objects (id integer primary key, value text); 138 | create function get_value(id integer) returns text language pllua stable 139 | as $$ 140 | local r = q:execute(id) 141 | return r and r[1] and r[1].value or 'value not found' 142 | end 143 | do -- the part below will be executed once before the first call 144 | q = spi.prepare("select value from objects where id=$1") 145 | $$; 146 | 147 | The result of executing a query is a table containing rows (if any) 148 | for select queries, or an integer rowcount for queries that do not 149 | return rows. 150 | 151 | ### Triggers 152 | 153 | create function mytrigger() returns trigger language pllua 154 | as $$ 155 | -- trigger functions are implicitly declared f(trigger,old,new,...) 156 | new.total_cost = new.price * new.qty; 157 | return new 158 | $$; 159 | 160 | ### JSON handling 161 | 162 | Values of type `json` are passed to Lua simply as strings. But the 163 | `jsonb` data type is supported in a more direct fashion. 164 | 165 | `jsonb` values can be mapped to Lua tables in a configurable way, and 166 | Lua tables converted back to `jsonb` values: 167 | 168 | create function add_stuff(val jsonb) returns jsonb language pllua 169 | as $$ 170 | local t = val{} -- convert jsonb to table with default settings 171 | t.newkey = { { foo = 1 }, { bar = 2 } } 172 | return t 173 | $$; 174 | select add_stuff('{"oldkey":123}'); 175 | add_stuff 176 | ----------------------------------------------------- 177 | {"newkey": [{"foo": 1}, {"bar": 2}], "oldkey": 123} 178 | (1 row) 179 | 180 | The above simplistic approach will tend to drop json null values 181 | (since Lua does not store nulls in tables), and loses precision on 182 | numeric values not representable as floats; this can be avoided as 183 | follows: 184 | 185 | create function add_stuff(val jsonb) returns jsonb language pllua 186 | as $$ 187 | local nullval = {} -- use some unique object to mark nulls 188 | local t = val{ null = nullval, pg_numeric = true } 189 | t.newkey = { { foo = 1 }, { bar = 2 } } 190 | return t, { null = nullval } 191 | $$; 192 | select add_stuff('{"oldkey":[147573952589676412928,null]}'); 193 | add_stuff 194 | ------------------------------------------------------------------------------- 195 | {"newkey": [{"foo": 1}, {"bar": 2}], "oldkey": [147573952589676412928, null]} 196 | (1 row) 197 | 198 | Tables that originated from JSON are tagged as to whether they were 199 | originally objects or arrays, so as long as you provide a unique null 200 | value, this form of round-trip conversion should not change anything. 201 | (See the `pllua.jsonb` module documentation for more detail.) 202 | 203 | 204 | -------------------------------------------------------------------------------- /doc/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /doc/script.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | "use strict"; 3 | /* adjust scroll positions */ 4 | var shiftWindow5 = function() { scrollBy(0, -0.05 * window.innerHeight) }; 5 | window.addEventListener("DOMContentLoaded",(function(){ if (location.hash) shiftWindow5(); })); 6 | window.addEventListener("hashchange", shiftWindow5); 7 | /* render the logo into a high-res favicon */ 8 | window.addEventListener("DOMContentLoaded",(function(){ 9 | var logosrc = (window.getComputedStyle(document.getElementById("logo")) 10 | .getPropertyValue("background-image") 11 | .match(/data:[^"")]*/)[0]); 12 | if (logosrc.length) 13 | { 14 | var i = new Image(960,960); 15 | var render1 = function(id,s) { 16 | var c = document.createElement("canvas"); 17 | c.width = s; 18 | c.height = s; 19 | var cxt = c.getContext("2d"); 20 | cxt.drawImage(i, 0, 0, s, s); 21 | var link = document.createElement("link"); 22 | link.id = id; 23 | link.rel = "icon"; 24 | link.type = "image/png"; 25 | link.sizes = s+"x"+s; 26 | link.href = c.toDataURL(); 27 | return link; 28 | }; 29 | var render = function() { 30 | document.head.appendChild(render1("dyn-icon-192.png", 192)); 31 | }; 32 | i.src = logosrc; 33 | if (i.complete) { 34 | render(); 35 | } else { 36 | i.onload = (function(){ if (i.complete) { render(); } }); 37 | } 38 | } 39 | })); 40 | })(); 41 | -------------------------------------------------------------------------------- /expected/event_triggers.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | \set QUIET 0 4 | -- Test event triggers. 5 | create function evtrig() returns event_trigger language pllua as $$ 6 | print(trigger.event, trigger.tag) 7 | $$; 8 | CREATE FUNCTION 9 | create event trigger et1 on ddl_command_start execute procedure evtrig(); 10 | CREATE EVENT TRIGGER 11 | create event trigger et2 on ddl_command_end execute procedure evtrig(); 12 | CREATE EVENT TRIGGER 13 | create event trigger et3 on sql_drop execute procedure evtrig(); 14 | CREATE EVENT TRIGGER 15 | create event trigger et4 on table_rewrite execute procedure evtrig(); 16 | CREATE EVENT TRIGGER 17 | create table evt1 (a text); 18 | INFO: ddl_command_start CREATE TABLE 19 | INFO: ddl_command_end CREATE TABLE 20 | CREATE TABLE 21 | alter table evt1 alter column a type integer using null; 22 | INFO: ddl_command_start ALTER TABLE 23 | INFO: table_rewrite ALTER TABLE 24 | INFO: ddl_command_end ALTER TABLE 25 | ALTER TABLE 26 | drop table evt1; 27 | INFO: ddl_command_start DROP TABLE 28 | INFO: sql_drop DROP TABLE 29 | INFO: ddl_command_end DROP TABLE 30 | DROP TABLE 31 | drop event trigger et1; 32 | DROP EVENT TRIGGER 33 | drop event trigger et2; 34 | DROP EVENT TRIGGER 35 | drop event trigger et3; 36 | DROP EVENT TRIGGER 37 | drop event trigger et4; 38 | DROP EVENT TRIGGER 39 | --end 40 | -------------------------------------------------------------------------------- /expected/horology-errors.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | -- 4 | set timezone = 'GMT'; 5 | set datestyle = 'ISO,YMD'; 6 | -- errors (not worth testing many combinations, they all share a code path) 7 | do language pllua $$ print(pgtype.time('03:45:01.234567').dow) $$; 8 | ERROR: unit "dow" not supported for type time without time zone 9 | --end 10 | -------------------------------------------------------------------------------- /expected/horology-errors_1.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | -- 4 | set timezone = 'GMT'; 5 | set datestyle = 'ISO,YMD'; 6 | -- errors (not worth testing many combinations, they all share a code path) 7 | do language pllua $$ print(pgtype.time('03:45:01.234567').dow) $$; 8 | ERROR: "time" units "dow" not recognized 9 | --end 10 | -------------------------------------------------------------------------------- /expected/lua54.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | -- 4 | -- 5.4-specific features. 5 | -- simple close metamethod 6 | do language pllua $$ 7 | local tc 8 | = setmetatable({}, { __close = function() print("close called") end }) 9 | spi.error("error") 10 | $$; 11 | INFO: close called 12 | ERROR: error 13 | -- close metamethod on cursor 14 | begin; 15 | do language pllua $$ 16 | for r in spi.rows([[ select * from generate_series(1,5) i ]]) do 17 | print(r) 18 | if r.i > 2 then break end 19 | end 20 | $$; 21 | INFO: (1) 22 | INFO: (2) 23 | INFO: (3) 24 | select * from pg_cursors; -- should be empty 25 | name | statement | is_holdable | is_binary | is_scrollable | creation_time 26 | ------+-----------+-------------+-----------+---------------+--------------- 27 | (0 rows) 28 | 29 | commit; 30 | -- lua error in close method 31 | do language pllua $$ 32 | local tc 33 | = setmetatable({}, { __close = function() error("inner error") end }) 34 | error("outer error") 35 | $$; 36 | ERROR: pllua: [string "DO-block"]:3: inner error 37 | -- db access in close method with outer lua error 38 | do language pllua $$ 39 | local tc 40 | = setmetatable({}, { __close = function() print(pgtype.numeric(0)) end }) 41 | error("outer error") 42 | $$; 43 | INFO: 0 44 | ERROR: pllua: [string "DO-block"]:4: outer error 45 | -- db access in close method with outer db error 46 | do language pllua $$ 47 | local tc 48 | = setmetatable({}, { __close = function() print(pgtype.numeric(0)) end }) 49 | spi.error("outer error") 50 | $$; 51 | ERROR: pllua: cannot call into PostgreSQL with pending errors 52 | -- close metamethod in SRF 53 | create function pg_temp.sf1(n integer) returns setof integer 54 | language pllua 55 | as $$ 56 | local x 57 | = setmetatable({}, { __close = function() print("close called") end }) 58 | for i = n, n+3 do 59 | coroutine.yield(i) 60 | end 61 | $$; 62 | select * from (values (1),(2)) v(n), lateral (select * from pg_temp.sf1(v.n) limit 1) s; 63 | INFO: close called 64 | INFO: close called 65 | n | sf1 66 | ---+----- 67 | 1 | 1 68 | 2 | 2 69 | (2 rows) 70 | 71 | -- error in close metamethod in SRF 72 | create function pg_temp.sf2(n integer) returns setof integer 73 | language pllua 74 | as $$ 75 | local x 76 | = setmetatable({}, { __close = function() error("inner error") end }) 77 | for i = n, n+3 do 78 | coroutine.yield(i) 79 | end 80 | $$; 81 | select * from (values (1),(2)) v(n), lateral (select * from pg_temp.sf2(v.n) limit 1) s; 82 | ERROR: pllua: [string "sf2"]:3: inner error 83 | --end 84 | -------------------------------------------------------------------------------- /expected/lua54_1.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | -- 4 | -- 5.4-specific features. 5 | -- simple close metamethod 6 | do language pllua $$ 7 | local tc 8 | = setmetatable({}, { __close = function() print("close called") end }) 9 | spi.error("error") 10 | $$; 11 | INFO: close called 12 | ERROR: error 13 | -- close metamethod on cursor 14 | begin; 15 | do language pllua $$ 16 | for r in spi.rows([[ select * from generate_series(1,5) i ]]) do 17 | print(r) 18 | if r.i > 2 then break end 19 | end 20 | $$; 21 | INFO: (1) 22 | INFO: (2) 23 | INFO: (3) 24 | select * from pg_cursors; -- should be empty 25 | name | statement | is_holdable | is_binary | is_scrollable | creation_time 26 | ------+-----------+-------------+-----------+---------------+--------------- 27 | (0 rows) 28 | 29 | commit; 30 | -- lua error in close method 31 | do language pllua $$ 32 | local tc 33 | = setmetatable({}, { __close = function() error("inner error") end }) 34 | error("outer error") 35 | $$; 36 | WARNING: pllua: error in __close metamethod ([string "DO-block"]:3: inner error) 37 | ERROR: pllua: [string "DO-block"]:4: outer error 38 | -- db access in close method with outer lua error 39 | do language pllua $$ 40 | local tc 41 | = setmetatable({}, { __close = function() print(pgtype.numeric(0)) end }) 42 | error("outer error") 43 | $$; 44 | INFO: 0 45 | ERROR: pllua: [string "DO-block"]:4: outer error 46 | -- db access in close method with outer db error 47 | do language pllua $$ 48 | local tc 49 | = setmetatable({}, { __close = function() print(pgtype.numeric(0)) end }) 50 | spi.error("outer error") 51 | $$; 52 | WARNING: pllua: error in __close metamethod (cannot call into PostgreSQL with pending errors) 53 | ERROR: outer error 54 | -- close metamethod in SRF 55 | create function pg_temp.sf1(n integer) returns setof integer 56 | language pllua 57 | as $$ 58 | local x 59 | = setmetatable({}, { __close = function() print("close called") end }) 60 | for i = n, n+3 do 61 | coroutine.yield(i) 62 | end 63 | $$; 64 | select * from (values (1),(2)) v(n), lateral (select * from pg_temp.sf1(v.n) limit 1) s; 65 | INFO: close called 66 | INFO: close called 67 | n | sf1 68 | ---+----- 69 | 1 | 1 70 | 2 | 2 71 | (2 rows) 72 | 73 | -- error in close metamethod in SRF 74 | create function pg_temp.sf2(n integer) returns setof integer 75 | language pllua 76 | as $$ 77 | local x 78 | = setmetatable({}, { __close = function() error("inner error") end }) 79 | for i = n, n+3 do 80 | coroutine.yield(i) 81 | end 82 | $$; 83 | select * from (values (1),(2)) v(n), lateral (select * from pg_temp.sf2(v.n) limit 1) s; 84 | ERROR: pllua: [string "sf2"]:3: inner error 85 | --end 86 | -------------------------------------------------------------------------------- /expected/numerics.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | -- test numerics 4 | create function lua_numexec(code text, n1 numeric, n2 numeric) 5 | returns text 6 | language pllua 7 | as $$ 8 | local f,e = load("return function(n1,n2) return "..code.." end", code, "t", self) 9 | assert(f,e) 10 | f = f() 11 | assert(f) 12 | return tostring(f(n1,n2)) 13 | end 14 | do 15 | num = require "pllua.numeric" 16 | $$; 17 | create function pg_numexec(code text, n1 numeric, n2 numeric) 18 | returns text 19 | language plpgsql 20 | as $$ 21 | declare 22 | r text; 23 | begin 24 | execute format('select (%s)::text', 25 | regexp_replace(regexp_replace(code, '\mnum\.', '', 'g'), 26 | '\mn([0-9])', '$\1', 'g')) 27 | into r using n1,n2; 28 | return r; 29 | end; 30 | $$; 31 | with 32 | t as (select code, 33 | lua_numexec(code, 5439.123456, -1.9) as lua, 34 | pg_numexec(code, 5439.123456, -1.9) as pg 35 | from unnest(array[ 36 | $$ n1 + n2 $$, $$ n1 - n2 $$, 37 | $$ n1 * n2 $$, $$ n1 / n2 $$, 38 | $$ n1 % n2 $$, $$ n1 ^ n2 $$, 39 | $$ (-n1) + n2 $$, $$ (-n1) - n2 $$, 40 | $$ (-n1) * n2 $$, $$ (-n1) / n2 $$, 41 | $$ (-n1) % n2 $$, $$ (-n1) ^ 3 $$, 42 | $$ (-n1) + (-n2) $$, $$ (-n1) - (-n2) $$, 43 | $$ (-n1) * (-n2) $$, $$ (-n1) / (-n2) $$, 44 | $$ (-n1) % (-n2) $$, $$ (-n1) ^ (-3) $$, 45 | $$ (n1) > (n2) $$, $$ (n1) < (n2) $$, 46 | $$ (n1) >= (n2) $$, $$ (n1) <= (n2) $$, 47 | $$ (n1) > (n2*10000) $$, 48 | $$ (n1) < (n2*10000) $$, 49 | $$ (n1) >= (n2 * -10000) $$, 50 | $$ (n1) <= (n2 * -10000) $$, 51 | $$ num.round(n1) $$, $$ num.round(n2) $$, 52 | $$ num.round(n1,4) $$, $$ num.round(n1,-1) $$, 53 | $$ num.trunc(n1) $$, $$ num.trunc(n2) $$, 54 | $$ num.trunc(n1,4) $$, $$ num.trunc(n1,-1) $$, 55 | $$ num.floor(n1) $$, $$ num.floor(n2) $$, 56 | $$ num.ceil(n1) $$, $$ num.ceil(n2) $$, 57 | $$ num.abs(n1) $$, $$ num.abs(n2) $$, 58 | $$ num.sign(n1) $$, $$ num.sign(n2) $$, 59 | $$ num.sqrt(n1) $$, 60 | $$ num.exp(12.3) $$, 61 | $$ num.exp(n2) $$ 62 | ]) as u(code)) 63 | select (lua = pg) as ok, * from t; 64 | ok | code | lua | pg 65 | ----+-------------------------+--------------------------------+-------------------------------- 66 | t | n1 + n2 | 5437.223456 | 5437.223456 67 | t | n1 - n2 | 5441.023456 | 5441.023456 68 | t | n1 * n2 | -10334.3345664 | -10334.3345664 69 | t | n1 / n2 | -2862.6965557894736842 | -2862.6965557894736842 70 | t | n1 % n2 | 1.323456 | 1.323456 71 | t | n1 ^ n2 | 0.00000007989048519637487 | 0.00000007989048519637487 72 | t | (-n1) + n2 | -5441.023456 | -5441.023456 73 | t | (-n1) - n2 | -5437.223456 | -5437.223456 74 | t | (-n1) * n2 | 10334.3345664 | 10334.3345664 75 | t | (-n1) / n2 | 2862.6965557894736842 | 2862.6965557894736842 76 | t | (-n1) % n2 | -1.323456 | -1.323456 77 | t | (-n1) ^ 3 | -160911376260.906871 | -160911376260.906871 78 | t | (-n1) + (-n2) | -5437.223456 | -5437.223456 79 | t | (-n1) - (-n2) | -5441.023456 | -5441.023456 80 | t | (-n1) * (-n2) | -10334.3345664 | -10334.3345664 81 | t | (-n1) / (-n2) | -2862.6965557894736842 | -2862.6965557894736842 82 | t | (-n1) % (-n2) | -1.323456 | -1.323456 83 | t | (-n1) ^ (-3) | -0.000000000006214601001103663 | -0.000000000006214601001103663 84 | t | (n1) > (n2) | true | true 85 | t | (n1) < (n2) | false | false 86 | t | (n1) >= (n2) | true | true 87 | t | (n1) <= (n2) | false | false 88 | t | (n1) > (n2*10000) | true | true 89 | t | (n1) < (n2*10000) | false | false 90 | t | (n1) >= (n2 * -10000) | false | false 91 | t | (n1) <= (n2 * -10000) | true | true 92 | t | num.round(n1) | 5439 | 5439 93 | t | num.round(n2) | -2 | -2 94 | t | num.round(n1,4) | 5439.1235 | 5439.1235 95 | t | num.round(n1,-1) | 5440 | 5440 96 | t | num.trunc(n1) | 5439 | 5439 97 | t | num.trunc(n2) | -1 | -1 98 | t | num.trunc(n1,4) | 5439.1234 | 5439.1234 99 | t | num.trunc(n1,-1) | 5430 | 5430 100 | t | num.floor(n1) | 5439 | 5439 101 | t | num.floor(n2) | -2 | -2 102 | t | num.ceil(n1) | 5440 | 5440 103 | t | num.ceil(n2) | -1 | -1 104 | t | num.abs(n1) | 5439.123456 | 5439.123456 105 | t | num.abs(n2) | 1.9 | 1.9 106 | t | num.sign(n1) | 1 | 1 107 | t | num.sign(n2) | -1 | -1 108 | t | num.sqrt(n1) | 73.750413259859093 | 73.750413259859093 109 | t | num.exp(12.3) | 219695.98867213773 | 219695.98867213773 110 | t | num.exp(n2) | 0.1495686192226351 | 0.1495686192226351 111 | (45 rows) 112 | 113 | -- calculate pi to 40 places 114 | do language pllua $$ 115 | -- Chudnovsky formula; ~14 digits per round, we use 4 rounds 116 | local num = require 'pllua.numeric' 117 | local prec = 100 -- precision of intermediate values 118 | local function fact(n) 119 | local r = pgtype.numeric(1):round(prec) 120 | for i = 2,n do r = r * i end 121 | return r:round(prec) 122 | end 123 | local c640320 = pgtype.numeric(640320):round(prec) 124 | local c13591409 = pgtype.numeric(13591409):round(prec) 125 | local c545140134 = pgtype.numeric(545140134):round(prec) 126 | local function chn(k) 127 | return (fact(6*k) * (c13591409 + (c545140134 * k))) 128 | / (fact(3*k) * fact(k)^3 * (-c640320)^(3*k)) 129 | end 130 | local function pi() 131 | return (1 / ((chn(0) + chn(1) + chn(2) + chn(3))*12 / num.sqrt(c640320^3))):round(40) 132 | end 133 | print(pi()) 134 | $$; 135 | INFO: 3.1415926535897932384626433832795028841972 136 | -- check sanity of maxinteger/mininteger 137 | do language pllua $$ 138 | local num = require 'pllua.numeric' 139 | local maxi = num.maxinteger 140 | local mini = num.mininteger 141 | print(type(num.tointeger(maxi)), type(num.tointeger(maxi+1))) 142 | print(type(num.tointeger(mini)), type(num.tointeger(mini-1))) 143 | $$ 144 | --end 145 | INFO: number nil 146 | INFO: number nil 147 | -------------------------------------------------------------------------------- /expected/numerics_1.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | -- test numerics 4 | create function lua_numexec(code text, n1 numeric, n2 numeric) 5 | returns text 6 | language pllua 7 | as $$ 8 | local f,e = load("return function(n1,n2) return "..code.." end", code, "t", self) 9 | assert(f,e) 10 | f = f() 11 | assert(f) 12 | return tostring(f(n1,n2)) 13 | end 14 | do 15 | num = require "pllua.numeric" 16 | $$; 17 | create function pg_numexec(code text, n1 numeric, n2 numeric) 18 | returns text 19 | language plpgsql 20 | as $$ 21 | declare 22 | r text; 23 | begin 24 | execute format('select (%s)::text', 25 | regexp_replace(regexp_replace(code, '\mnum\.', '', 'g'), 26 | '\mn([0-9])', '$\1', 'g')) 27 | into r using n1,n2; 28 | return r; 29 | end; 30 | $$; 31 | with 32 | t as (select code, 33 | lua_numexec(code, 5439.123456, -1.9) as lua, 34 | pg_numexec(code, 5439.123456, -1.9) as pg 35 | from unnest(array[ 36 | $$ n1 + n2 $$, $$ n1 - n2 $$, 37 | $$ n1 * n2 $$, $$ n1 / n2 $$, 38 | $$ n1 % n2 $$, $$ n1 ^ n2 $$, 39 | $$ (-n1) + n2 $$, $$ (-n1) - n2 $$, 40 | $$ (-n1) * n2 $$, $$ (-n1) / n2 $$, 41 | $$ (-n1) % n2 $$, $$ (-n1) ^ 3 $$, 42 | $$ (-n1) + (-n2) $$, $$ (-n1) - (-n2) $$, 43 | $$ (-n1) * (-n2) $$, $$ (-n1) / (-n2) $$, 44 | $$ (-n1) % (-n2) $$, $$ (-n1) ^ (-3) $$, 45 | $$ (n1) > (n2) $$, $$ (n1) < (n2) $$, 46 | $$ (n1) >= (n2) $$, $$ (n1) <= (n2) $$, 47 | $$ (n1) > (n2*10000) $$, 48 | $$ (n1) < (n2*10000) $$, 49 | $$ (n1) >= (n2 * -10000) $$, 50 | $$ (n1) <= (n2 * -10000) $$, 51 | $$ num.round(n1) $$, $$ num.round(n2) $$, 52 | $$ num.round(n1,4) $$, $$ num.round(n1,-1) $$, 53 | $$ num.trunc(n1) $$, $$ num.trunc(n2) $$, 54 | $$ num.trunc(n1,4) $$, $$ num.trunc(n1,-1) $$, 55 | $$ num.floor(n1) $$, $$ num.floor(n2) $$, 56 | $$ num.ceil(n1) $$, $$ num.ceil(n2) $$, 57 | $$ num.abs(n1) $$, $$ num.abs(n2) $$, 58 | $$ num.sign(n1) $$, $$ num.sign(n2) $$, 59 | $$ num.sqrt(n1) $$, 60 | $$ num.exp(12.3) $$, 61 | $$ num.exp(n2) $$ 62 | ]) as u(code)) 63 | select (lua = pg) as ok, * from t; 64 | ok | code | lua | pg 65 | ----+-------------------------+--------------------------------+-------------------------------- 66 | t | n1 + n2 | 5437.223456 | 5437.223456 67 | t | n1 - n2 | 5441.023456 | 5441.023456 68 | t | n1 * n2 | -10334.3345664 | -10334.3345664 69 | t | n1 / n2 | -2862.6965557894736842 | -2862.6965557894736842 70 | t | n1 % n2 | 1.323456 | 1.323456 71 | t | n1 ^ n2 | 0.00000007989048519637487 | 0.00000007989048519637487 72 | t | (-n1) + n2 | -5441.023456 | -5441.023456 73 | t | (-n1) - n2 | -5437.223456 | -5437.223456 74 | t | (-n1) * n2 | 10334.3345664 | 10334.3345664 75 | t | (-n1) / n2 | 2862.6965557894736842 | 2862.6965557894736842 76 | t | (-n1) % n2 | -1.323456 | -1.323456 77 | t | (-n1) ^ 3 | -160911376260.9068713240072028 | -160911376260.9068713240072028 78 | t | (-n1) + (-n2) | -5437.223456 | -5437.223456 79 | t | (-n1) - (-n2) | -5441.023456 | -5441.023456 80 | t | (-n1) * (-n2) | -10334.3345664 | -10334.3345664 81 | t | (-n1) / (-n2) | -2862.6965557894736842 | -2862.6965557894736842 82 | t | (-n1) % (-n2) | -1.323456 | -1.323456 83 | t | (-n1) ^ (-3) | -0.0000000000062146 | -0.0000000000062146 84 | t | (n1) > (n2) | true | true 85 | t | (n1) < (n2) | false | false 86 | t | (n1) >= (n2) | true | true 87 | t | (n1) <= (n2) | false | false 88 | t | (n1) > (n2*10000) | true | true 89 | t | (n1) < (n2*10000) | false | false 90 | t | (n1) >= (n2 * -10000) | false | false 91 | t | (n1) <= (n2 * -10000) | true | true 92 | t | num.round(n1) | 5439 | 5439 93 | t | num.round(n2) | -2 | -2 94 | t | num.round(n1,4) | 5439.1235 | 5439.1235 95 | t | num.round(n1,-1) | 5440 | 5440 96 | t | num.trunc(n1) | 5439 | 5439 97 | t | num.trunc(n2) | -1 | -1 98 | t | num.trunc(n1,4) | 5439.1234 | 5439.1234 99 | t | num.trunc(n1,-1) | 5430 | 5430 100 | t | num.floor(n1) | 5439 | 5439 101 | t | num.floor(n2) | -2 | -2 102 | t | num.ceil(n1) | 5440 | 5440 103 | t | num.ceil(n2) | -1 | -1 104 | t | num.abs(n1) | 5439.123456 | 5439.123456 105 | t | num.abs(n2) | 1.9 | 1.9 106 | t | num.sign(n1) | 1 | 1 107 | t | num.sign(n2) | -1 | -1 108 | t | num.sqrt(n1) | 73.750413259859093 | 73.750413259859093 109 | t | num.exp(12.3) | 219695.98867213773 | 219695.98867213773 110 | t | num.exp(n2) | 0.1495686192226351 | 0.1495686192226351 111 | (45 rows) 112 | 113 | -- calculate pi to 40 places 114 | do language pllua $$ 115 | -- Chudnovsky formula; ~14 digits per round, we use 4 rounds 116 | local num = require 'pllua.numeric' 117 | local prec = 100 -- precision of intermediate values 118 | local function fact(n) 119 | local r = pgtype.numeric(1):round(prec) 120 | for i = 2,n do r = r * i end 121 | return r:round(prec) 122 | end 123 | local c640320 = pgtype.numeric(640320):round(prec) 124 | local c13591409 = pgtype.numeric(13591409):round(prec) 125 | local c545140134 = pgtype.numeric(545140134):round(prec) 126 | local function chn(k) 127 | return (fact(6*k) * (c13591409 + (c545140134 * k))) 128 | / (fact(3*k) * fact(k)^3 * (-c640320)^(3*k)) 129 | end 130 | local function pi() 131 | return (1 / ((chn(0) + chn(1) + chn(2) + chn(3))*12 / num.sqrt(c640320^3))):round(40) 132 | end 133 | print(pi()) 134 | $$; 135 | INFO: 3.1415926535897932384626433832795028841972 136 | -- check sanity of maxinteger/mininteger 137 | do language pllua $$ 138 | local num = require 'pllua.numeric' 139 | local maxi = num.maxinteger 140 | local mini = num.mininteger 141 | print(type(num.tointeger(maxi)), type(num.tointeger(maxi+1))) 142 | print(type(num.tointeger(mini)), type(num.tointeger(mini-1))) 143 | $$ 144 | --end 145 | INFO: number nil 146 | INFO: number nil 147 | -------------------------------------------------------------------------------- /expected/paths.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | -- 4 | create function pg_temp.tmp1(n text) returns text 5 | language plluau immutable strict 6 | as $$ return (require "pllua.paths")[n]() $$; 7 | -- some of the dirs might not actually exist, so we test only the 8 | -- important ones. We can't actually test that the dir exists or what 9 | -- the contents are, since many pg versions reject pg_stat_file on 10 | -- absolute paths; so just check that we got some string that looks 11 | -- like a path. 12 | select u.n, f.path ~ '^(?:[[:alpha:]]:)?/' 13 | from unnest(array['bin','lib','libdir','pkglib','share']) 14 | with ordinality as u(n,ord), 15 | pg_temp.tmp1(u.n) f(path) 16 | order by u.ord; 17 | n | ?column? 18 | --------+---------- 19 | bin | t 20 | lib | t 21 | libdir | t 22 | pkglib | t 23 | share | t 24 | (5 rows) 25 | 26 | --end 27 | -------------------------------------------------------------------------------- /expected/procedures.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | -- 4 | -- test procedures, non-atomic DO-blocks, and spi.commit/rollback 5 | -- (all pg11 new features) 6 | create table xatst2 (a integer); 7 | create procedure pg_temp.tp1(a text) 8 | language pllua 9 | as $$ 10 | print("hello world", a) 11 | print(spi.is_atomic() and "atomic context" or "non-atomic context") 12 | $$; 13 | call pg_temp.tp1('foo'); 14 | INFO: hello world foo 15 | INFO: non-atomic context 16 | begin; call pg_temp.tp1('foo'); commit; 17 | INFO: hello world foo 18 | INFO: atomic context 19 | do language pllua $$ 20 | print(spi.is_atomic() and "atomic context" or "non-atomic context") 21 | $$; 22 | INFO: non-atomic context 23 | begin; 24 | do language pllua $$ 25 | print(spi.is_atomic() and "atomic context" or "non-atomic context") 26 | $$; 27 | INFO: atomic context 28 | commit; 29 | create procedure pg_temp.tp2() 30 | language pllua 31 | as $$ 32 | local stmt = spi.prepare([[ insert into xatst2 values ($1) ]]); 33 | stmt:execute(1); 34 | spi.commit(); 35 | stmt:execute(2); 36 | spi.rollback(); 37 | stmt:execute(3); 38 | spi.commit(); 39 | stmt:execute(4); 40 | $$; 41 | call pg_temp.tp2(); 42 | -- should now be three different xids in xatst2, and 3 rows 43 | select count(*), count(distinct age(xmin)) from xatst2; 44 | count | count 45 | -------+------- 46 | 3 | 3 47 | (1 row) 48 | 49 | -- proper handling of open cursors 50 | create procedure pg_temp.tp3() 51 | language pllua 52 | as $$ 53 | local stmt = spi.prepare([[ select i from generate_series(1,10) i ]]); 54 | for r in stmt:rows() do 55 | print(r.i) 56 | spi.commit(); 57 | end 58 | $$; 59 | call pg_temp.tp3(); 60 | INFO: 1 61 | ERROR: pllua: [string "tp3"]:3: cannot iterate a closed cursor 62 | create procedure pg_temp.tp4() 63 | language pllua 64 | as $$ 65 | local stmt = spi.prepare([[ select i from generate_series(1,10) i ]], {}, { hold = true }); 66 | for r in stmt:rows() do 67 | print(r.i) 68 | spi.commit(); 69 | end 70 | $$; 71 | call pg_temp.tp4(); 72 | INFO: 1 73 | INFO: 2 74 | INFO: 3 75 | INFO: 4 76 | INFO: 5 77 | INFO: 6 78 | INFO: 7 79 | INFO: 8 80 | INFO: 9 81 | INFO: 10 82 | -- no commit inside subxact 83 | truncate table xatst2; 84 | do language pllua $$ 85 | local stmt = spi.prepare([[ insert into xatst2 values ($1) ]]); 86 | stmt:execute(1); 87 | spi.commit(); 88 | stmt:execute(2); 89 | print(pcall(function() stmt:execute(3) spi.commit() end)) 90 | -- the commit threw a lua error and the subxact was rolled back, 91 | -- so we should be in the same xact as row 2 92 | stmt:execute(4); 93 | spi.commit(); 94 | $$; 95 | INFO: false [string "DO-block"]:6: cannot commit or rollback from inside a subtransaction 96 | -- should now be two different xids in xatst2, and 3 rows 97 | select count(*), count(distinct age(xmin)) from xatst2; 98 | count | count 99 | -------+------- 100 | 3 | 2 101 | (1 row) 102 | 103 | --end 104 | -------------------------------------------------------------------------------- /expected/spi.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | -- 4 | -- Test of SPI-related functionality. 5 | create temp table tsttab ( 6 | id integer primary key, 7 | a integer, 8 | b text, 9 | c numeric, 10 | d date 11 | ); 12 | insert into tsttab(id, a,b,c,d) 13 | values (1, 1,'foo',2.34,'2017-01-01'), 14 | (2, 2,'bar',2.34,'2017-02-01'), 15 | (3, 3,'baz',2.34,'2017-03-01'), 16 | (4, 4, 'fred',2.34,'2017-04-01'), 17 | (5, 5,'jim',2.34,'2017-05-01'), 18 | (6, 6,'sheila',2.34,'2017-06-01'); 19 | -- basics 20 | do language pllua $$ 21 | local tbl 22 | tbl = spi.execute([[ select 1 as a, 'foo'::text as b ]]) 23 | print(#tbl,tbl[1],type(tbl[1])) 24 | print(tbl[1].a,tbl[1].b) 25 | tbl = spi.execute([[ select i, 'foo'::text as b from generate_series(1,10000) i ]]) 26 | print(#tbl,tbl[1],tbl[10000]) 27 | tbl = spi.execute([[ select * from tsttab order by id ]]) 28 | for i = 1,#tbl do print(tbl[i]) end 29 | $$; 30 | INFO: 1 (1,foo) userdata 31 | INFO: 1 foo 32 | INFO: 10000 (1,foo) (10000,foo) 33 | INFO: (1,1,foo,2.34,01-01-2017) 34 | INFO: (2,2,bar,2.34,02-01-2017) 35 | INFO: (3,3,baz,2.34,03-01-2017) 36 | INFO: (4,4,fred,2.34,04-01-2017) 37 | INFO: (5,5,jim,2.34,05-01-2017) 38 | INFO: (6,6,sheila,2.34,06-01-2017) 39 | -- statements 40 | do language pllua $$ 41 | local stmt,tbl 42 | stmt = spi.prepare([[ select * from tsttab where id=$1 ]], {"integer"}) 43 | tbl = stmt:execute(1) 44 | print(tbl[1]) 45 | -- __call metamethod 46 | tbl = stmt(6) 47 | print(tbl[1]) 48 | stmt = spi.prepare([[ select * from tsttab where id = ANY ($1) order by id ]], 49 | {pgtype.array.integer}) 50 | tbl = stmt:execute(pgtype.array.integer(1,nil,3)) 51 | print(#tbl,tbl[1],tbl[2]) 52 | -- type deduction: 53 | stmt = spi.prepare([[ select 1 + $1 as a, pg_typeof($1) ]]) 54 | tbl = stmt:execute(1) 55 | print(#tbl,tbl[1]) 56 | $$; 57 | INFO: (1,1,foo,2.34,01-01-2017) 58 | INFO: (6,6,sheila,2.34,06-01-2017) 59 | INFO: 2 (1,1,foo,2.34,01-01-2017) (3,3,baz,2.34,03-01-2017) 60 | INFO: 1 (2,integer) 61 | -- iterators 62 | do language pllua $$ 63 | for r in spi.rows([[ select * from tsttab order by id ]]) do 64 | print(r) 65 | end 66 | stmt = spi.prepare([[ select * from tsttab where id = ANY ($1) ]], 67 | {pgtype.array.integer}) 68 | for r in stmt:rows(pgtype.array.integer(1,nil,3)) do 69 | print(r) 70 | end 71 | $$; 72 | INFO: (1,1,foo,2.34,01-01-2017) 73 | INFO: (2,2,bar,2.34,02-01-2017) 74 | INFO: (3,3,baz,2.34,03-01-2017) 75 | INFO: (4,4,fred,2.34,04-01-2017) 76 | INFO: (5,5,jim,2.34,05-01-2017) 77 | INFO: (6,6,sheila,2.34,06-01-2017) 78 | INFO: (1,1,foo,2.34,01-01-2017) 79 | INFO: (3,3,baz,2.34,03-01-2017) 80 | do language pllua $$ 81 | local c = spi.newcursor('curs1') 82 | local stmt = spi.prepare([[ select * from tsttab order by id for update ]]) 83 | c:open(stmt) 84 | for r in c:rows() do 85 | print(r) 86 | if r.id == 3 then 87 | spi.execute([[ update tsttab set c = c + 10 where current of curs1 ]]) 88 | end 89 | end 90 | for r in spi.rows([[ select * from tsttab order by id ]]) do 91 | print(r) 92 | end 93 | spi.execute([[ update tsttab set c = c - 10 where id=3 ]]) 94 | c:close() 95 | $$; 96 | INFO: (1,1,foo,2.34,01-01-2017) 97 | INFO: (2,2,bar,2.34,02-01-2017) 98 | INFO: (3,3,baz,2.34,03-01-2017) 99 | INFO: (4,4,fred,2.34,04-01-2017) 100 | INFO: (5,5,jim,2.34,05-01-2017) 101 | INFO: (6,6,sheila,2.34,06-01-2017) 102 | INFO: (1,1,foo,2.34,01-01-2017) 103 | INFO: (2,2,bar,2.34,02-01-2017) 104 | INFO: (3,3,baz,12.34,03-01-2017) 105 | INFO: (4,4,fred,2.34,04-01-2017) 106 | INFO: (5,5,jim,2.34,05-01-2017) 107 | INFO: (6,6,sheila,2.34,06-01-2017) 108 | -- cursors 109 | begin; 110 | declare foo scroll cursor for select * from tsttab order by id; 111 | do language pllua $$ 112 | local c = spi.findcursor("foo") 113 | local tbl 114 | tbl = c:fetch(1,'next') 115 | print(#tbl,tbl[1]) 116 | tbl = c:fetch(2,'forward') -- same as 'next' 117 | print(#tbl,tbl[1],tbl[2]) 118 | tbl = c:fetch(1,'absolute') 119 | print(#tbl,tbl[1]) 120 | tbl = c:fetch(4,'relative') 121 | print(#tbl,tbl[1]) 122 | tbl = c:fetch(1,'prior') -- same as 'backward' 123 | print(#tbl,tbl[1]) 124 | tbl = c:fetch(1,'backward') 125 | print(#tbl,tbl[1]) 126 | print(c:isopen()) 127 | spi.execute("close foo") 128 | print(c:isopen()) 129 | $$; 130 | INFO: 1 (1,1,foo,2.34,01-01-2017) 131 | INFO: 2 (2,2,bar,2.34,02-01-2017) (3,3,baz,2.34,03-01-2017) 132 | INFO: 1 (1,1,foo,2.34,01-01-2017) 133 | INFO: 1 (5,5,jim,2.34,05-01-2017) 134 | INFO: 1 (4,4,fred,2.34,04-01-2017) 135 | INFO: 1 (3,3,baz,2.34,03-01-2017) 136 | INFO: true 137 | INFO: false 138 | commit; 139 | do language pllua $$ 140 | local c = spi.newcursor("bar") 141 | c:open([[ select * from tsttab where id >= $1 order by id ]], 3) 142 | local tbl 143 | tbl = c:fetch(1,'next') 144 | print(#tbl,tbl[1]) 145 | for k,v in ipairs(spi.execute([[ select name, statement from pg_cursors ]])) do 146 | print(v.name, v.statement) 147 | end 148 | c:close() 149 | for k,v in ipairs(spi.execute([[ select name, statement from pg_cursors ]])) do 150 | print(v.name, v.statement) 151 | end 152 | c:open([[ select * from tsttab where id < $1 order by id desc ]], 3) 153 | tbl = c:fetch(3,'next') 154 | print(#tbl,tbl[1],tbl[2]) 155 | for k,v in ipairs(spi.execute([[ select name, statement from pg_cursors ]])) do 156 | print(v.name, v.statement) 157 | end 158 | c:close() 159 | $$; 160 | INFO: 1 (3,3,baz,2.34,03-01-2017) 161 | INFO: bar select * from tsttab where id >= $1 order by id 162 | INFO: 2 (2,2,bar,2.34,02-01-2017) (1,1,foo,2.34,01-01-2017) 163 | INFO: bar select * from tsttab where id < $1 order by id desc 164 | -- cursor options on statement 165 | do language pllua $$ 166 | local stmt = spi.prepare([[ select * from tsttab where id >= $1 order by id ]], 167 | {"integer"}, { scroll = true, fast_start = true, generic_plan = true }) 168 | local stmt2 = spi.prepare([[ select * from tsttab where id >= $1 order by id ]], 169 | {"integer"}, { no_scroll = true }) 170 | local c = stmt:getcursor(4) 171 | local tbl 172 | tbl = c:fetch(3,'next') 173 | print(#tbl,tbl[1],tbl[2]) 174 | c:move(0,'absolute') 175 | tbl = c:fetch(3,'next') 176 | print(#tbl,tbl[1],tbl[2]) 177 | for k,v in ipairs(spi.execute([[ select name, statement, is_scrollable from pg_cursors ]])) do 178 | print(v.name, v.statement, v.is_scrollable) 179 | end 180 | c:close() 181 | c = stmt2:getcursor(4) 182 | local c2 = spi.findcursor(c:name()) 183 | print(c:name(), rawequal(c,c2)) 184 | for k,v in ipairs(spi.execute([[ select name, statement, is_scrollable from pg_cursors ]])) do 185 | print(v.name, v.statement, v.is_scrollable) 186 | end 187 | c:close() 188 | $$; 189 | INFO: 3 (4,4,fred,2.34,04-01-2017) (5,5,jim,2.34,05-01-2017) 190 | INFO: 3 (4,4,fred,2.34,04-01-2017) (5,5,jim,2.34,05-01-2017) 191 | INFO: select * from tsttab where id >= $1 order by id true 192 | INFO: true 193 | INFO: select * from tsttab where id >= $1 order by id false 194 | -- check missing params are OK 195 | do language pllua $$ 196 | local stmt = spi.prepare([[ select * from generate_series($1::integer, $3) i ]]); 197 | print(stmt:argtype(1):name()) 198 | print(type(stmt:argtype(2))) 199 | print(stmt:argtype(3):name()) 200 | $$; 201 | INFO: integer 202 | INFO: nil 203 | INFO: integer 204 | -- check execute_count 205 | do language pllua $$ 206 | local q = [[ select * from generate_series($1::integer,$2) i ]] 207 | local r1 = spi.execute_count(q, 2, 1, 5) 208 | print(#r1) 209 | local s = spi.prepare(q, {"integer","integer"}) 210 | r1 = s:execute_count(3,1,5) 211 | print(#r1) 212 | $$; 213 | INFO: 2 214 | INFO: 3 215 | -- cursors as parameters and return values 216 | create function do_fetch(c refcursor) returns void language pllua as $$ 217 | while true do 218 | local r = (c:fetch())[1] 219 | if r==nil then break end 220 | print(r) 221 | end 222 | $$; 223 | create function do_exec(q text, n text) returns refcursor language pllua as $$ 224 | local s = spi.prepare(q) 225 | local c = spi.newcursor(n) 226 | return c:open(s):disown() 227 | $$; 228 | begin; 229 | declare mycur cursor for select * from tsttab order by id; 230 | select do_fetch('mycur'); 231 | INFO: (1,1,foo,2.34,01-01-2017) 232 | INFO: (2,2,bar,2.34,02-01-2017) 233 | INFO: (3,3,baz,2.34,03-01-2017) 234 | INFO: (4,4,fred,2.34,04-01-2017) 235 | INFO: (5,5,jim,2.34,05-01-2017) 236 | INFO: (6,6,sheila,2.34,06-01-2017) 237 | do_fetch 238 | ---------- 239 | 240 | (1 row) 241 | 242 | commit; 243 | begin; 244 | select do_exec('select * from tsttab order by id desc', 'mycur2'); 245 | do_exec 246 | --------- 247 | mycur2 248 | (1 row) 249 | 250 | do language pllua $$ collectgarbage() $$; -- check cursor stays open 251 | fetch all from mycur2; 252 | id | a | b | c | d 253 | ----+---+--------+------+------------ 254 | 6 | 6 | sheila | 2.34 | 06-01-2017 255 | 5 | 5 | jim | 2.34 | 05-01-2017 256 | 4 | 4 | fred | 2.34 | 04-01-2017 257 | 3 | 3 | baz | 2.34 | 03-01-2017 258 | 2 | 2 | bar | 2.34 | 02-01-2017 259 | 1 | 1 | foo | 2.34 | 01-01-2017 260 | (6 rows) 261 | 262 | commit; 263 | -- check correct error handling inside coroutines (issue #15) 264 | create function pg_temp.f1() returns setof text language pllua as $$ 265 | local r = spi.rows('syntaxerror') 266 | return 'notreached' 267 | $$; 268 | select * from pg_temp.f1(); 269 | ERROR: syntax error at or near "syntaxerror" at character 1 270 | --end 271 | -------------------------------------------------------------------------------- /expected/subxact.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | -- 4 | create table xatst (a integer); 5 | do language pllua $$ 6 | local stmt = spi.prepare([[ insert into xatst values ($1) ]]); 7 | stmt:execute(1); 8 | pcall(function() stmt:execute(2) end) 9 | stmt:execute(3); 10 | $$; 11 | -- should now be two different xids in xatst, and 3 rows 12 | select count(*), count(distinct age(xmin)) from xatst; 13 | count | count 14 | -------+------- 15 | 3 | 2 16 | (1 row) 17 | 18 | truncate table xatst; 19 | do language pllua $$ 20 | local stmt = spi.prepare([[ insert into xatst values ($1) ]]); 21 | stmt:execute(1); 22 | print(pcall(function() stmt:execute(2) error("foo") end)) 23 | stmt:execute(3); 24 | $$; 25 | INFO: false [string "DO-block"]:4: foo 26 | -- should now be one xid in xatst, and 2 rows 27 | select count(*), count(distinct age(xmin)) from xatst; 28 | count | count 29 | -------+------- 30 | 2 | 1 31 | (1 row) 32 | 33 | truncate table xatst; 34 | do language pllua $$ 35 | local stmt = spi.prepare([[ insert into xatst values ($1) ]]); 36 | stmt:execute(1); 37 | print(pcall(function() stmt:execute(2) spi.error("foo") end)) 38 | stmt:execute(3); 39 | $$; 40 | INFO: false ERROR: XX000 foo 41 | -- should now be one xid in xatst, and 2 rows 42 | select count(*), count(distinct age(xmin)) from xatst; 43 | count | count 44 | -------+------- 45 | 2 | 1 46 | (1 row) 47 | 48 | do language pllua $$ 49 | local function f() for r in spi.rows([[ select * from xatst order by a ]]) do print(r) end end 50 | print(pcall(f)) 51 | $$; 52 | INFO: (1) 53 | INFO: (3) 54 | INFO: true 55 | do language pllua $$ 56 | local function f() for r in spi.rows([[ select * from xatst order by a ]]) do print(r) end end 57 | local function f2() error("foo") end 58 | print(pcall(f2)) 59 | f() 60 | $$; 61 | INFO: false [string "DO-block"]:3: foo 62 | INFO: (1) 63 | INFO: (3) 64 | do language pllua $$ 65 | local function f(e) print("error",e) for r in spi.rows([[ select * from xatst order by a ]]) do print(r) end end 66 | local function f2() error("foo") end 67 | print(xpcall(f2,f)) 68 | $$; 69 | INFO: error [string "DO-block"]:3: foo 70 | INFO: (1) 71 | INFO: (3) 72 | INFO: false nil 73 | truncate table xatst; 74 | do language pllua $$ 75 | local stmt = spi.prepare([[ insert into xatst values ($1) ]]); 76 | local function f(e) print("error",e) stmt:execute(3) end 77 | local function f2() stmt:execute(2) error("foo") end 78 | stmt:execute(1) 79 | print(xpcall(f2,f)) 80 | $$; 81 | INFO: error [string "DO-block"]:4: foo 82 | INFO: false nil 83 | -- should now be one xid in xatst, and 2 rows 84 | select count(*), count(distinct age(xmin)) from xatst; 85 | count | count 86 | -------+------- 87 | 2 | 1 88 | (1 row) 89 | 90 | do language pllua $$ 91 | local function f(e) error("bar") end 92 | local function f2() error("foo") end 93 | print(xpcall(f2,f)) 94 | $$; 95 | INFO: false error in error handling 96 | -- tricky error-in-error cases: 97 | -- 98 | -- pg error inside xpcall handler func needs to abort out to the 99 | -- parent of the xpcall, not the xpcall itself. 100 | begin; 101 | -- we get (harmless) warnings with lua53 but not with luajit for this 102 | -- case. suppress them. 103 | set local client_min_messages = error; 104 | do language pllua $$ 105 | local function f(e) spi.error("nested") end 106 | local function f2() error("foo") end 107 | -- don't print the result because it differs with luajit, all that 108 | -- really matters here is that we don't crash and don't reach the 109 | -- last print 110 | pcall(function() 111 | print("entering xpcall"); 112 | print("inner xpcall", xpcall(f2,f)) 113 | print("should not be reached") 114 | end) 115 | $$; 116 | INFO: entering xpcall 117 | commit; 118 | do language pllua $$ 119 | local level = 0 120 | local function f(e) level = level + 1 if level==1 then print("in error handler",level,e) spi.error("nested") end end 121 | local function f2() error("foo") end 122 | print("outer pcall", 123 | pcall(function() 124 | print("entering xpcall"); 125 | print("inner xpcall", xpcall(f2,f)) 126 | print("should not be reached") 127 | end)) 128 | $$; 129 | INFO: entering xpcall 130 | INFO: in error handler 1 [string "DO-block"]:4: foo 131 | INFO: outer pcall false ERROR: XX000 nested 132 | do language pllua $$ 133 | print(lpcall(function() error("caught") end)) 134 | $$; 135 | INFO: false [string "DO-block"]:2: caught 136 | do language pllua $$ 137 | print(lpcall(function() spi.error("not caught") end)) 138 | $$; 139 | ERROR: not caught 140 | -- make sure PG errors in coroutines are propagated (but not lua errors) 141 | do language pllua $$ 142 | local c = coroutine.create(function() coroutine.yield() error("caught") end) 143 | print(coroutine.resume(c)) 144 | print(coroutine.resume(c)) 145 | $$; 146 | INFO: true 147 | INFO: false [string "DO-block"]:2: caught 148 | do language pllua $$ 149 | local c = coroutine.create(function() coroutine.yield() spi.error("not caught") end) 150 | print(coroutine.resume(c)) 151 | print(coroutine.resume(c)) 152 | $$; 153 | INFO: true 154 | ERROR: not caught 155 | -- error object funcs 156 | do language pllua $$ 157 | local err = require 'pllua.error' 158 | local r,e = pcall(function() spi.error("22003", "foo", "bar", "baz") end) 159 | print(err.type(e), err.category(e), err.errcode(e)) 160 | print(e.severity, e.category, e.errcode, e.sqlstate, e.message, e.detail, e.hint) 161 | local r,e = pcall(function() error("foo") end) 162 | print(err.type(e), err.category(e), err.errcode(e), e) 163 | $$; 164 | INFO: error data_exception numeric_value_out_of_range 165 | INFO: error data_exception numeric_value_out_of_range 22003 foo bar baz 166 | INFO: nil nil nil [string "DO-block"]:6: foo 167 | --end 168 | -------------------------------------------------------------------------------- /expected/triggers_10.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | \set QUIET 0 4 | -- Test pg10+ trigger functionality. 5 | create table trigtst2 ( 6 | id integer primary key, 7 | name text, 8 | flag boolean, 9 | qty integer, 10 | weight numeric 11 | ); 12 | CREATE TABLE 13 | create function ttrig1() returns trigger language pllua 14 | as $$ 15 | print(trigger.name,...) 16 | print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) 17 | for r in spi.rows([[ select * from newtab ]]) do print(r) end 18 | $$; 19 | CREATE FUNCTION 20 | create function ttrig2() returns trigger language pllua 21 | as $$ 22 | print(trigger.name,...) 23 | print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) 24 | for r in spi.rows([[ select 'old', * from oldtab union all select 'new', * from newtab ]]) do print(r) end 25 | $$; 26 | CREATE FUNCTION 27 | create function ttrig3() returns trigger language pllua 28 | as $$ 29 | print(trigger.name,...) 30 | print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) 31 | for r in spi.rows([[ select * from oldtab ]]) do print(r) end 32 | $$; 33 | CREATE FUNCTION 34 | create trigger t1 35 | after insert on trigtst2 36 | referencing new table as newtab 37 | for each statement 38 | execute procedure ttrig1('t1 insert'); 39 | CREATE TRIGGER 40 | create trigger t2 41 | after update on trigtst2 42 | referencing old table as oldtab 43 | new table as newtab 44 | for each statement 45 | execute procedure ttrig2('t2 update'); 46 | CREATE TRIGGER 47 | create trigger t3 48 | after delete on trigtst2 49 | referencing old table as oldtab 50 | for each statement 51 | execute procedure ttrig3('t3 delete'); 52 | CREATE TRIGGER 53 | insert into trigtst2 54 | values (1, 'fred', true, 23, 1.73), 55 | (2, 'jim', false, 11, 3.1), 56 | (3, 'sheila', false, 9, 1.3), 57 | (4, 'dougal', false, 1, 9.3), 58 | (5, 'brian', false, 31, 51.5), 59 | (6, 'ermintrude', true, 91, 52.7), 60 | (7, 'dylan', false, 35, 12.1), 61 | (8, 'florence', false, 23, 5.4), 62 | (9, 'zebedee', false, 199, 7.4); 63 | INFO: t1 t1 insert 64 | INFO: after statement insert trigtst2 65 | INFO: (1,fred,t,23,1.73) 66 | INFO: (2,jim,f,11,3.1) 67 | INFO: (3,sheila,f,9,1.3) 68 | INFO: (4,dougal,f,1,9.3) 69 | INFO: (5,brian,f,31,51.5) 70 | INFO: (6,ermintrude,t,91,52.7) 71 | INFO: (7,dylan,f,35,12.1) 72 | INFO: (8,florence,f,23,5.4) 73 | INFO: (9,zebedee,f,199,7.4) 74 | INSERT 0 9 75 | update trigtst2 set qty = qty + 1; 76 | INFO: t2 t2 update 77 | INFO: after statement update trigtst2 78 | INFO: (old,1,fred,t,23,1.73) 79 | INFO: (old,2,jim,f,11,3.1) 80 | INFO: (old,3,sheila,f,9,1.3) 81 | INFO: (old,4,dougal,f,1,9.3) 82 | INFO: (old,5,brian,f,31,51.5) 83 | INFO: (old,6,ermintrude,t,91,52.7) 84 | INFO: (old,7,dylan,f,35,12.1) 85 | INFO: (old,8,florence,f,23,5.4) 86 | INFO: (old,9,zebedee,f,199,7.4) 87 | INFO: (new,1,fred,t,24,1.73) 88 | INFO: (new,2,jim,f,12,3.1) 89 | INFO: (new,3,sheila,f,10,1.3) 90 | INFO: (new,4,dougal,f,2,9.3) 91 | INFO: (new,5,brian,f,32,51.5) 92 | INFO: (new,6,ermintrude,t,92,52.7) 93 | INFO: (new,7,dylan,f,36,12.1) 94 | INFO: (new,8,florence,f,24,5.4) 95 | INFO: (new,9,zebedee,f,200,7.4) 96 | UPDATE 9 97 | delete from trigtst2 where name = 'sheila'; 98 | INFO: t3 t3 delete 99 | INFO: after statement delete trigtst2 100 | INFO: (3,sheila,f,10,1.3) 101 | DELETE 1 102 | -- 103 | -------------------------------------------------------------------------------- /expected/trusted.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | set pllua.on_trusted_init=$$ 4 | local e = require 'pllua.elog' 5 | package.preload['testmod1'] = function() e.info("testmod1 loaded") return { testfunc = function() print("testfunc1") end } end; 6 | package.preload['testmod2'] = function() e.info("testmod2 loaded") return { testfunc = function() print("testfunc2") end } end; 7 | trusted.allow('testmod1', nil, nil, nil, false); 8 | trusted.allow('testmod2', nil, nil, nil, true); 9 | $$; 10 | -- 11 | do language pllua $$ print("interpreter loaded") $$; 12 | INFO: testmod2 loaded 13 | INFO: interpreter loaded 14 | do language pllua $$ 15 | local m = require 'testmod1' 16 | m.testfunc() 17 | $$; 18 | INFO: testmod1 loaded 19 | INFO: testfunc1 20 | do language pllua $$ 21 | local m = require 'testmod2' 22 | m.testfunc() 23 | $$; 24 | INFO: testfunc2 25 | --end 26 | -------------------------------------------------------------------------------- /hstore/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for PL/Lua + hstore 2 | 3 | PG_CONFIG ?= pg_config 4 | 5 | # Lua specific 6 | 7 | # General 8 | LUA_INCDIR ?= /usr/local/include/lua53 9 | LUALIB ?= -L/usr/local/lib -llua-5.3 10 | 11 | # no need to edit below here 12 | 13 | MODULE_big = hstore_pllua 14 | 15 | EXTENSION = hstore_pllua hstore_plluau 16 | 17 | DATA = hstore_pllua--1.0.sql hstore_plluau--1.0.sql 18 | 19 | OBJS = hstore_pllua.o 20 | 21 | REGRESS = create_ext hstore 22 | 23 | # MAJORVERSION and includedir_server are not defined yet, but will be 24 | # defined before PG_CPPFLAGS is expanded 25 | 26 | # for pg11+, hstore.h will have been installed here as hstore/hstore.h, 27 | # but pllua.h might also have been installed as pllua/pllua.h and we don't 28 | # want to take that in preference to ../src/pllua.h. So we use 29 | # #include "hstore/hstore.h" but #include "pllua.h" 30 | 31 | EXT_INCDIR = $(includedir_server)/extension 32 | 33 | # for pg 9.5/9.6/10, we have a local copy of hstore.h since it happens 34 | # to be the same, barring non-semantic whitespace, between the three 35 | # versions 36 | EXT_INCDIR_OLD = $(srcdir)/old_inc 37 | 38 | PG_CPPFLAGS = -I$(LUA_INCDIR) -I$(srcdir)/../src 39 | PG_CPPFLAGS += -I$(EXT_INCDIR$(if $(call version_ge,$(MAJORVERSION),11),,_OLD)) 40 | 41 | SHLIB_LINK = $(LUALIB) 42 | 43 | # if VPATH is not already set, but the makefile is not in the current 44 | # dir, then assume a vpath build using the makefile's directory as 45 | # source. PGXS will set $(srcdir) accordingly. 46 | ifndef VPATH 47 | ifneq ($(realpath $(CURDIR)),$(realpath $(dir $(firstword $(MAKEFILE_LIST))))) 48 | VPATH := $(dir $(firstword $(MAKEFILE_LIST))) 49 | endif 50 | endif 51 | 52 | mklibdir := $(if $(VPATH),$(VPATH)/../tools,../tools) 53 | include $(mklibdir)/numeric.mk 54 | 55 | PGXS := $(shell $(PG_CONFIG) --pgxs) 56 | include $(PGXS) 57 | 58 | ifeq ($(call version_ge,$(MAJORVERSION),9.5),) 59 | $(error unsupported PostgreSQL version) 60 | endif 61 | -------------------------------------------------------------------------------- /hstore/expected/create_ext.out: -------------------------------------------------------------------------------- 1 | -- 2 | create extension hstore; 3 | create extension pllua; 4 | create extension hstore_pllua; 5 | --end 6 | -------------------------------------------------------------------------------- /hstore/expected/hstore.out: -------------------------------------------------------------------------------- 1 | -- 2 | \set VERBOSITY terse 3 | -- smoke tests 4 | do language pllua $$ 5 | -- these should work even without the transform 6 | local hs = pgtype.hstore('"foo"=>"bar", "baz"=>"quux"') 7 | print(hs) 8 | local res = (spi.execute([[select pg_typeof($1) as type, $1::text as text, $1->'foo' as foo, $1->'baz' as bar]], hs))[1] 9 | print(res.type, res.text, res.foo, res.bar) 10 | -- but these use it: 11 | res = (spi.execute([[select $1 as hs]], hs))[1] 12 | print(type(res.hs)) 13 | do 14 | local ks = {} 15 | for k,v in pairs(res.hs) do ks[1+#ks] = k end 16 | table.sort(ks) 17 | for i,k in ipairs(ks) do print(k,res.hs[k]) end 18 | end 19 | local hs2 = pgtype.hstore({ foo = "bar", baz = "quux" }) 20 | res = (spi.execute([[select pg_typeof($1) as t2]], hs2))[1] 21 | print(res.t2) 22 | res = (spi.execute([[select $1 = $2 as eq]], hs, hs2))[1] 23 | print(res.eq) 24 | $$; 25 | INFO: "baz"=>"quux", "foo"=>"bar" 26 | INFO: hstore "baz"=>"quux", "foo"=>"bar" bar quux 27 | INFO: table 28 | INFO: baz quux 29 | INFO: foo bar 30 | INFO: hstore 31 | INFO: true 32 | -- make sure that non-table types don't crash the transform 33 | do language pllua $$ 34 | print(pgtype.hstore(123)) 35 | $$; 36 | ERROR: pllua: incompatible value type 37 | do language pllua $$ 38 | print(pgtype.hstore(function() end)) 39 | $$; 40 | ERROR: pllua: incompatible value type 41 | --end 42 | -------------------------------------------------------------------------------- /hstore/hstore_pllua--1.0.sql: -------------------------------------------------------------------------------- 1 | /* hstore_pllua--1.0.sql */ 2 | 3 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 4 | \echo Use "CREATE EXTENSION hstore_pllua" to load this file. \quit 5 | 6 | -- force module load; at runtime this doesn't matter because the transforms 7 | -- can't be legitimately called except from within pllua.so, but we want 8 | -- CREATE EXTENSION to work even in a cold session 9 | do language pllua $$ $$; 10 | 11 | CREATE FUNCTION hstore_to_pllua(val internal) RETURNS internal 12 | LANGUAGE C STRICT IMMUTABLE 13 | AS 'MODULE_PATHNAME'; 14 | 15 | CREATE FUNCTION pllua_to_hstore(val internal) RETURNS hstore 16 | LANGUAGE C STRICT IMMUTABLE 17 | AS 'MODULE_PATHNAME'; 18 | 19 | CREATE TRANSFORM FOR hstore LANGUAGE pllua ( 20 | FROM SQL WITH FUNCTION hstore_to_pllua(internal), 21 | TO SQL WITH FUNCTION pllua_to_hstore(internal) 22 | ); 23 | -------------------------------------------------------------------------------- /hstore/hstore_pllua.c: -------------------------------------------------------------------------------- 1 | /* hstore_pllua.c */ 2 | 3 | /* note, we do not support out-of-pllua-tree building */ 4 | #include "pllua.h" 5 | 6 | #include "hstore/hstore.h" 7 | 8 | #include "mb/pg_wchar.h" 9 | 10 | PG_MODULE_MAGIC; 11 | 12 | #ifndef PG_GETARG_HSTORE_P 13 | #define PG_GETARG_HSTORE_P(h_) PG_GETARG_HS(h_) 14 | #endif 15 | 16 | extern void _PG_init(void); 17 | 18 | /* Linkage to functions in hstore module */ 19 | typedef HStore *(*hstoreUpgrade_t) (Datum orig); 20 | static hstoreUpgrade_t hstoreUpgrade_p; 21 | typedef int (*hstoreUniquePairs_t) (Pairs *a, int32 l, int32 *buflen); 22 | static hstoreUniquePairs_t hstoreUniquePairs_p; 23 | typedef HStore *(*hstorePairs_t) (Pairs *pairs, int32 pcount, int32 buflen); 24 | static hstorePairs_t hstorePairs_p; 25 | typedef size_t (*hstoreCheckKeyLen_t) (size_t len); 26 | static hstoreCheckKeyLen_t hstoreCheckKeyLen_p; 27 | typedef size_t (*hstoreCheckValLen_t) (size_t len); 28 | static hstoreCheckValLen_t hstoreCheckValLen_p; 29 | 30 | /* Linkage to functions in pllua module */ 31 | typedef void (*pllua_pcall_t)(lua_State *L, int nargs, int nresults, int msgh); 32 | static pllua_pcall_t pllua_pcall_p; 33 | typedef lua_CFunction pllua_trampoline_t; 34 | static lua_CFunction pllua_trampoline_p; 35 | typedef bool (*pllua_pairs_start_t) (lua_State *L, int nd, bool noerror); 36 | static pllua_pairs_start_t pllua_pairs_start_p; 37 | typedef int (*pllua_pairs_next_t) (lua_State *L); 38 | static pllua_pairs_next_t pllua_pairs_next_p; 39 | typedef const char * (*pllua_tolstring_t) (lua_State *L, int idx, size_t *len); 40 | static pllua_tolstring_t pllua_tolstring_p; 41 | 42 | /* 43 | * Module initialize function: fetch function pointers for cross-module calls. 44 | */ 45 | void 46 | _PG_init(void) 47 | { 48 | #define EXTFUNCS(x_) #x_ 49 | #define EXTFUNCT(xp_) xp_##_t 50 | #define EXTFUNCP(xp_) xp_##_p 51 | #define EXTFUNC(lib_, n_) \ 52 | AssertVariableIsOfType(&n_, EXTFUNCT(n_)); \ 53 | EXTFUNCP(n_) = (EXTFUNCT(n_)) \ 54 | load_external_function(lib_, EXTFUNCS(n_), true, NULL); 55 | 56 | EXTFUNC("$libdir/hstore", hstoreUpgrade); 57 | EXTFUNC("$libdir/hstore", hstoreUniquePairs); 58 | EXTFUNC("$libdir/hstore", hstorePairs); 59 | EXTFUNC("$libdir/hstore", hstoreCheckKeyLen); 60 | EXTFUNC("$libdir/hstore", hstoreCheckValLen); 61 | 62 | EXTFUNC("$libdir/pllua", pllua_pcall); 63 | EXTFUNC("$libdir/pllua", pllua_trampoline); 64 | EXTFUNC("$libdir/pllua", pllua_pairs_start); 65 | EXTFUNC("$libdir/pllua", pllua_pairs_next); 66 | EXTFUNC("$libdir/pllua", pllua_tolstring); 67 | } 68 | 69 | 70 | /* These defines must be after the module init function */ 71 | #define hstoreUpgrade hstoreUpgrade_p 72 | #define hstoreUniquePairs hstoreUniquePairs_p 73 | #define hstorePairs hstorePairs_p 74 | #define hstoreCheckKeyLen hstoreCheckKeyLen_p 75 | #define hstoreCheckValLen hstoreCheckValLen_p 76 | 77 | #define pllua_pcall pllua_pcall_p 78 | #define pllua_trampoline pllua_trampoline_p 79 | #define pllua_pairs_start pllua_pairs_start_p 80 | #define pllua_pairs_next pllua_pairs_next_p 81 | #define pllua_tolstring pllua_tolstring_p 82 | 83 | 84 | static int 85 | hstore_to_pllua_real(lua_State *L) 86 | { 87 | HStore *in = lua_touserdata(L, 1); 88 | int i; 89 | int count = HS_COUNT(in); 90 | char *base = STRPTR(in); 91 | HEntry *entries = ARRPTR(in); 92 | 93 | lua_createtable(L, 0, count); 94 | 95 | for (i = 0; i < count; i++) 96 | { 97 | lua_pushlstring(L, 98 | HSTORE_KEY(entries, base, i), 99 | HSTORE_KEYLEN(entries, i)); 100 | if (HSTORE_VALISNULL(entries, i)) 101 | lua_pushboolean(L, 0); 102 | else 103 | lua_pushlstring(L, 104 | HSTORE_VAL(entries, base, i), 105 | HSTORE_VALLEN(entries, i)); 106 | lua_rawset(L, -3); 107 | } 108 | 109 | return 1; 110 | } 111 | 112 | /* 113 | * equivalent to: 114 | * 115 | * local keys,vals = {},{} 116 | * for k,v in pairs(hs) do keys[#keys+1] = k vals[#vals+1] = v end 117 | * then makes a full userdata with a Pairs array and refs to keys,vals 118 | * 119 | */ 120 | static int 121 | pllua_to_hstore_real(lua_State *L) 122 | { 123 | Pairs *pairs = NULL; 124 | int idx = 0; 125 | int pcount = 0; 126 | bool metaloop; 127 | 128 | /* 129 | * Decline if there isn't exactly 1 arg. 130 | */ 131 | if (lua_gettop(L) != 1) 132 | { 133 | lua_pushnil(L); 134 | lua_pushnil(L); 135 | return 2; 136 | } 137 | 138 | lua_newtable(L); /* index 2: keys */ 139 | lua_newtable(L); /* index 3: vals */ 140 | 141 | metaloop = pllua_pairs_start(L, 1, true); 142 | 143 | /* 144 | * If it doesn't have a pairs metamethod and it's not a plain table, 145 | * then we have to decline it. 146 | */ 147 | if (!metaloop && !lua_istable(L, 1)) 148 | { 149 | /* pairs_start already pushed one nil */ 150 | lua_pushnil(L); 151 | return 2; 152 | } 153 | 154 | while (metaloop ? pllua_pairs_next(L) : lua_next(L, 1)) 155 | { 156 | ++idx; 157 | if (lua_isnil(L, -1) || (lua_isboolean(L, -1) && !lua_toboolean(L, -1))) 158 | { 159 | lua_pop(L, 1); 160 | } 161 | else 162 | { 163 | pllua_tolstring(L, -1, NULL); 164 | lua_rawseti(L, 3, idx); 165 | lua_pop(L, 1); 166 | } 167 | pllua_tolstring(L, -1, NULL); 168 | lua_rawseti(L, 2, idx); 169 | } 170 | 171 | lua_settop(L, 3); 172 | pcount = idx; 173 | lua_pushinteger(L, pcount); /* first result */ 174 | pairs = lua_newuserdata(L, (idx ? idx : 1) * sizeof(Pairs)); 175 | lua_newtable(L); 176 | lua_pushvalue(L, 2); 177 | lua_setfield(L, -2, "keys"); 178 | lua_pushvalue(L, 3); 179 | lua_setfield(L, -2, "values"); 180 | lua_setuservalue(L, -2); 181 | for (idx = 0; idx < pcount; ++idx) 182 | { 183 | lua_rawgeti(L, 2, idx+1); 184 | pairs[idx].key = (char *) lua_tolstring(L, -1, &(pairs[idx].keylen)); 185 | pairs[idx].needfree = false; 186 | lua_pop(L, 1); 187 | 188 | if (lua_rawgeti(L, 3, idx+1) == LUA_TNIL) 189 | { 190 | pairs[idx].val = NULL; 191 | pairs[idx].vallen = 0; 192 | pairs[idx].isnull = true; 193 | } 194 | else 195 | { 196 | pairs[idx].val = (char *) lua_tolstring(L, -1, &(pairs[idx].vallen)); 197 | pairs[idx].isnull = false; 198 | } 199 | lua_pop(L, 1); 200 | } 201 | return 2; 202 | } 203 | 204 | 205 | PG_FUNCTION_INFO_V1(hstore_to_pllua); 206 | 207 | Datum 208 | hstore_to_pllua(PG_FUNCTION_ARGS) 209 | { 210 | HStore *in = PG_GETARG_HSTORE_P(0); 211 | pllua_node *node = (pllua_node *) fcinfo->context; 212 | lua_State *L; 213 | 214 | if (!node || node->type != T_Invalid || node->magic != PLLUA_MAGIC) 215 | elog(ERROR, "hstore_to_pllua must only be called from pllua"); 216 | 217 | L = node->L; 218 | pllua_pushcfunction(L, pllua_trampoline); 219 | lua_pushlightuserdata(L, hstore_to_pllua_real); 220 | lua_pushlightuserdata(L, in); 221 | pllua_pcall(L, 2, 1, 0); 222 | 223 | return (Datum)0; 224 | } 225 | 226 | 227 | PG_FUNCTION_INFO_V1(pllua_to_hstore); 228 | 229 | Datum 230 | pllua_to_hstore(PG_FUNCTION_ARGS) 231 | { 232 | pllua_node *node = (pllua_node *) fcinfo->context; 233 | lua_State *L; 234 | int32 pcount = 0; 235 | HStore *out = NULL; 236 | Pairs *pairs; 237 | 238 | if (!node || node->type != T_Invalid || node->magic != PLLUA_MAGIC) 239 | elog(ERROR, "pllua_to_hstore must only be called from pllua"); 240 | 241 | L = node->L; 242 | pllua_pushcfunction(L, pllua_trampoline); 243 | lua_insert(L, 1); 244 | lua_pushlightuserdata(L, pllua_to_hstore_real); 245 | lua_insert(L, 2); 246 | pllua_pcall(L, lua_gettop(L) - 1, 2, 0); 247 | 248 | /* 249 | * this ptr is the Pairs struct as a Lua full userdata, which carries 250 | * refs to the tables holding the keys and values strings to prevent 251 | * them being GC'd. hstorePairs will copy everything into a new palloc'd 252 | * value, and the storage will be GC'd sometime later after we pop it. 253 | */ 254 | pcount = lua_tointeger(L, -2); 255 | pairs = lua_touserdata(L, -1); 256 | 257 | if (pairs) 258 | { 259 | int i; 260 | int32 buflen; 261 | for (i = 0; i < pcount; ++i) 262 | { 263 | pairs[i].keylen = hstoreCheckKeyLen(pairs[i].keylen); 264 | pairs[i].vallen = hstoreCheckKeyLen(pairs[i].vallen); 265 | pg_verifymbstr(pairs[i].key, pairs[i].keylen, false); 266 | pg_verifymbstr(pairs[i].val, pairs[i].vallen, false); 267 | } 268 | pcount = hstoreUniquePairs(pairs, pcount, &buflen); 269 | out = hstorePairs(pairs, pcount, buflen); 270 | } 271 | 272 | lua_pop(L, 2); 273 | 274 | if (out) 275 | PG_RETURN_POINTER(out); 276 | else 277 | PG_RETURN_NULL(); 278 | } 279 | -------------------------------------------------------------------------------- /hstore/hstore_pllua.control: -------------------------------------------------------------------------------- 1 | # hstore_pllua extension 2 | default_version = '1.0' 3 | comment = 'Hstore transform for Lua' 4 | module_pathname = '$libdir/hstore_pllua' 5 | relocatable = true 6 | requires = 'hstore, pllua' 7 | -------------------------------------------------------------------------------- /hstore/hstore_plluau--1.0.sql: -------------------------------------------------------------------------------- 1 | /* hstore_plluau--1.0.sql */ 2 | 3 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 4 | \echo Use "CREATE EXTENSION hstore_plluau" to load this file. \quit 5 | 6 | -- force module load; at runtime this doesn't matter because the transforms 7 | -- can't be legitimately called except from within pllua.so, but we want 8 | -- CREATE EXTENSION to work even in a cold session 9 | do language plluau $$ $$; 10 | 11 | CREATE FUNCTION hstore_to_plluau(val internal) RETURNS internal 12 | LANGUAGE C STRICT IMMUTABLE 13 | AS 'MODULE_PATHNAME','hstore_to_pllua'; 14 | 15 | CREATE FUNCTION plluau_to_hstore(val internal) RETURNS hstore 16 | LANGUAGE C STRICT IMMUTABLE 17 | AS 'MODULE_PATHNAME','pllua_to_hstore'; 18 | 19 | CREATE TRANSFORM FOR hstore LANGUAGE plluau ( 20 | FROM SQL WITH FUNCTION hstore_to_plluau(internal), 21 | TO SQL WITH FUNCTION plluau_to_hstore(internal) 22 | ); 23 | -------------------------------------------------------------------------------- /hstore/hstore_plluau.control: -------------------------------------------------------------------------------- 1 | # hstore_plluau extension 2 | default_version = '1.0' 3 | comment = 'Hstore transform for untrusted Lua' 4 | module_pathname = '$libdir/hstore_pllua' 5 | relocatable = true 6 | requires = 'hstore, plluau' 7 | -------------------------------------------------------------------------------- /hstore/old_inc/hstore/hstore.h: -------------------------------------------------------------------------------- 1 | /* 2 | * contrib/hstore/hstore.h 3 | */ 4 | #ifndef __HSTORE_H__ 5 | #define __HSTORE_H__ 6 | 7 | #include "fmgr.h" 8 | #include "utils/array.h" 9 | 10 | 11 | /* 12 | * HEntry: there is one of these for each key _and_ value in an hstore 13 | * 14 | * the position offset points to the _end_ so that we can get the length 15 | * by subtraction from the previous entry. the ISFIRST flag lets us tell 16 | * whether there is a previous entry. 17 | */ 18 | typedef struct 19 | { 20 | uint32 entry; 21 | } HEntry; 22 | 23 | #define HENTRY_ISFIRST 0x80000000 24 | #define HENTRY_ISNULL 0x40000000 25 | #define HENTRY_POSMASK 0x3FFFFFFF 26 | 27 | /* note possible multiple evaluations, also access to prior array element */ 28 | #define HSE_ISFIRST(he_) (((he_).entry & HENTRY_ISFIRST) != 0) 29 | #define HSE_ISNULL(he_) (((he_).entry & HENTRY_ISNULL) != 0) 30 | #define HSE_ENDPOS(he_) ((he_).entry & HENTRY_POSMASK) 31 | #define HSE_OFF(he_) (HSE_ISFIRST(he_) ? 0 : HSE_ENDPOS((&(he_))[-1])) 32 | #define HSE_LEN(he_) (HSE_ISFIRST(he_) \ 33 | ? HSE_ENDPOS(he_) \ 34 | : HSE_ENDPOS(he_) - HSE_ENDPOS((&(he_))[-1])) 35 | 36 | /* 37 | * determined by the size of "endpos" (ie HENTRY_POSMASK), though this is a 38 | * bit academic since currently varlenas (and hence both the input and the 39 | * whole hstore) have the same limit 40 | */ 41 | #define HSTORE_MAX_KEY_LEN 0x3FFFFFFF 42 | #define HSTORE_MAX_VALUE_LEN 0x3FFFFFFF 43 | 44 | typedef struct 45 | { 46 | int32 vl_len_; /* varlena header (do not touch directly!) */ 47 | uint32 size_; /* flags and number of items in hstore */ 48 | /* array of HEntry follows */ 49 | } HStore; 50 | 51 | /* 52 | * It's not possible to get more than 2^28 items into an hstore, so we reserve 53 | * the top few bits of the size field. See hstore_compat.c for one reason 54 | * why. Some bits are left for future use here. MaxAllocSize makes the 55 | * practical count limit slightly more than 2^28 / 3, or INT_MAX / 24, the 56 | * limit for an hstore full of 4-byte keys and null values. Therefore, we 57 | * don't explicitly check the format-imposed limit. 58 | */ 59 | #define HS_FLAG_NEWVERSION 0x80000000 60 | 61 | #define HS_COUNT(hsp_) ((hsp_)->size_ & 0x0FFFFFFF) 62 | #define HS_SETCOUNT(hsp_,c_) ((hsp_)->size_ = (c_) | HS_FLAG_NEWVERSION) 63 | 64 | 65 | /* 66 | * "x" comes from an existing HS_COUNT() (as discussed, <= INT_MAX/24) or a 67 | * Pairs array length (due to MaxAllocSize, <= INT_MAX/40). "lenstr" is no 68 | * more than INT_MAX, that extreme case arising in hstore_from_arrays(). 69 | * Therefore, this calculation is limited to about INT_MAX / 5 + INT_MAX. 70 | */ 71 | #define HSHRDSIZE (sizeof(HStore)) 72 | #define CALCDATASIZE(x, lenstr) ( (x) * 2 * sizeof(HEntry) + HSHRDSIZE + (lenstr) ) 73 | 74 | /* note multiple evaluations of x */ 75 | #define ARRPTR(x) ( (HEntry*) ( (HStore*)(x) + 1 ) ) 76 | #define STRPTR(x) ( (char*)(ARRPTR(x) + HS_COUNT((HStore*)(x)) * 2) ) 77 | 78 | /* note multiple/non evaluations */ 79 | #define HSTORE_KEY(arr_,str_,i_) ((str_) + HSE_OFF((arr_)[2*(i_)])) 80 | #define HSTORE_VAL(arr_,str_,i_) ((str_) + HSE_OFF((arr_)[2*(i_)+1])) 81 | #define HSTORE_KEYLEN(arr_,i_) (HSE_LEN((arr_)[2*(i_)])) 82 | #define HSTORE_VALLEN(arr_,i_) (HSE_LEN((arr_)[2*(i_)+1])) 83 | #define HSTORE_VALISNULL(arr_,i_) (HSE_ISNULL((arr_)[2*(i_)+1])) 84 | 85 | /* 86 | * currently, these following macros are the _only_ places that rely 87 | * on internal knowledge of HEntry. Everything else should be using 88 | * the above macros. Exception: the in-place upgrade in hstore_compat.c 89 | * messes with entries directly. 90 | */ 91 | 92 | /* 93 | * copy one key/value pair (which must be contiguous starting at 94 | * sptr_) into an under-construction hstore; dent_ is an HEntry*, 95 | * dbuf_ is the destination's string buffer, dptr_ is the current 96 | * position in the destination. lots of modification and multiple 97 | * evaluation here. 98 | */ 99 | #define HS_COPYITEM(dent_,dbuf_,dptr_,sptr_,klen_,vlen_,vnull_) \ 100 | do { \ 101 | memcpy((dptr_), (sptr_), (klen_)+(vlen_)); \ 102 | (dptr_) += (klen_)+(vlen_); \ 103 | (dent_)++->entry = ((dptr_) - (dbuf_) - (vlen_)) & HENTRY_POSMASK; \ 104 | (dent_)++->entry = ((((dptr_) - (dbuf_)) & HENTRY_POSMASK) \ 105 | | ((vnull_) ? HENTRY_ISNULL : 0)); \ 106 | } while(0) 107 | 108 | /* 109 | * add one key/item pair, from a Pairs structure, into an 110 | * under-construction hstore 111 | */ 112 | #define HS_ADDITEM(dent_,dbuf_,dptr_,pair_) \ 113 | do { \ 114 | memcpy((dptr_), (pair_).key, (pair_).keylen); \ 115 | (dptr_) += (pair_).keylen; \ 116 | (dent_)++->entry = ((dptr_) - (dbuf_)) & HENTRY_POSMASK; \ 117 | if ((pair_).isnull) \ 118 | (dent_)++->entry = ((((dptr_) - (dbuf_)) & HENTRY_POSMASK) \ 119 | | HENTRY_ISNULL); \ 120 | else \ 121 | { \ 122 | memcpy((dptr_), (pair_).val, (pair_).vallen); \ 123 | (dptr_) += (pair_).vallen; \ 124 | (dent_)++->entry = ((dptr_) - (dbuf_)) & HENTRY_POSMASK; \ 125 | } \ 126 | } while (0) 127 | 128 | /* finalize a newly-constructed hstore */ 129 | #define HS_FINALIZE(hsp_,count_,buf_,ptr_) \ 130 | do { \ 131 | int buflen = (ptr_) - (buf_); \ 132 | if ((count_)) \ 133 | ARRPTR(hsp_)[0].entry |= HENTRY_ISFIRST; \ 134 | if ((count_) != HS_COUNT((hsp_))) \ 135 | { \ 136 | HS_SETCOUNT((hsp_),(count_)); \ 137 | memmove(STRPTR(hsp_), (buf_), buflen); \ 138 | } \ 139 | SET_VARSIZE((hsp_), CALCDATASIZE((count_), buflen)); \ 140 | } while (0) 141 | 142 | /* ensure the varlena size of an existing hstore is correct */ 143 | #define HS_FIXSIZE(hsp_,count_) \ 144 | do { \ 145 | int bl = (count_) ? HSE_ENDPOS(ARRPTR(hsp_)[2*(count_)-1]) : 0; \ 146 | SET_VARSIZE((hsp_), CALCDATASIZE((count_),bl)); \ 147 | } while (0) 148 | 149 | /* DatumGetHStoreP includes support for reading old-format hstore values */ 150 | extern HStore *hstoreUpgrade(Datum orig); 151 | 152 | #define DatumGetHStoreP(d) hstoreUpgrade(d) 153 | 154 | #define PG_GETARG_HS(x) DatumGetHStoreP(PG_GETARG_DATUM(x)) 155 | 156 | 157 | /* 158 | * Pairs is a "decompressed" representation of one key/value pair. 159 | * The two strings are not necessarily null-terminated. 160 | */ 161 | typedef struct 162 | { 163 | char *key; 164 | char *val; 165 | size_t keylen; 166 | size_t vallen; 167 | bool isnull; /* value is null? */ 168 | bool needfree; /* need to pfree the value? */ 169 | } Pairs; 170 | 171 | extern int hstoreUniquePairs(Pairs *a, int32 l, int32 *buflen); 172 | extern HStore *hstorePairs(Pairs *pairs, int32 pcount, int32 buflen); 173 | 174 | extern size_t hstoreCheckKeyLen(size_t len); 175 | extern size_t hstoreCheckValLen(size_t len); 176 | 177 | extern int hstoreFindKey(HStore *hs, int *lowbound, char *key, int keylen); 178 | extern Pairs *hstoreArrayToPairs(ArrayType *a, int *npairs); 179 | 180 | #define HStoreContainsStrategyNumber 7 181 | #define HStoreExistsStrategyNumber 9 182 | #define HStoreExistsAnyStrategyNumber 10 183 | #define HStoreExistsAllStrategyNumber 11 184 | #define HStoreOldContainsStrategyNumber 13 /* backwards compatibility */ 185 | 186 | /* 187 | * defining HSTORE_POLLUTE_NAMESPACE=0 will prevent use of old function names; 188 | * for now, we default to on for the benefit of people restoring old dumps 189 | */ 190 | #ifndef HSTORE_POLLUTE_NAMESPACE 191 | #define HSTORE_POLLUTE_NAMESPACE 1 192 | #endif 193 | 194 | #if HSTORE_POLLUTE_NAMESPACE 195 | #define HSTORE_POLLUTE(newname_,oldname_) \ 196 | PG_FUNCTION_INFO_V1(oldname_); \ 197 | Datum newname_(PG_FUNCTION_ARGS); \ 198 | Datum oldname_(PG_FUNCTION_ARGS) { return newname_(fcinfo); } \ 199 | extern int no_such_variable 200 | #else 201 | #define HSTORE_POLLUTE(newname_,oldname_) \ 202 | extern int no_such_variable 203 | #endif 204 | 205 | #endif /* __HSTORE_H__ */ 206 | -------------------------------------------------------------------------------- /hstore/sql/create_ext.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | create extension hstore; 4 | create extension pllua; 5 | create extension hstore_pllua; 6 | 7 | --end 8 | -------------------------------------------------------------------------------- /hstore/sql/hstore.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | 5 | -- smoke tests 6 | 7 | do language pllua $$ 8 | -- these should work even without the transform 9 | local hs = pgtype.hstore('"foo"=>"bar", "baz"=>"quux"') 10 | print(hs) 11 | local res = (spi.execute([[select pg_typeof($1) as type, $1::text as text, $1->'foo' as foo, $1->'baz' as bar]], hs))[1] 12 | print(res.type, res.text, res.foo, res.bar) 13 | -- but these use it: 14 | res = (spi.execute([[select $1 as hs]], hs))[1] 15 | print(type(res.hs)) 16 | do 17 | local ks = {} 18 | for k,v in pairs(res.hs) do ks[1+#ks] = k end 19 | table.sort(ks) 20 | for i,k in ipairs(ks) do print(k,res.hs[k]) end 21 | end 22 | local hs2 = pgtype.hstore({ foo = "bar", baz = "quux" }) 23 | res = (spi.execute([[select pg_typeof($1) as t2]], hs2))[1] 24 | print(res.t2) 25 | res = (spi.execute([[select $1 = $2 as eq]], hs, hs2))[1] 26 | print(res.eq) 27 | $$; 28 | 29 | -- make sure that non-table types don't crash the transform 30 | 31 | do language pllua $$ 32 | print(pgtype.hstore(123)) 33 | $$; 34 | 35 | do language pllua $$ 36 | print(pgtype.hstore(function() end)) 37 | $$; 38 | 39 | --end 40 | -------------------------------------------------------------------------------- /parallel_schedule: -------------------------------------------------------------------------------- 1 | # 2 | # this must be first since it installs the extension 3 | test: pllua 4 | # these should be independent 5 | test: pllua_old arrays numerics paths horology horology-errors rowdatum spi subxact types triggers jsonb trusted 6 | # this must run alone because it messes up output from DDL 7 | test: event_triggers 8 | -------------------------------------------------------------------------------- /pllua.control: -------------------------------------------------------------------------------- 1 | # pllua extension 2 | default_version = '2.0' 3 | comment = 'Lua as a procedural language' 4 | module_pathname = '$libdir/pllua' 5 | relocatable = false 6 | schema = 'pg_catalog' 7 | -------------------------------------------------------------------------------- /plluau.control: -------------------------------------------------------------------------------- 1 | # plluau extension 2 | default_version = '2.0' 3 | comment = 'Lua as an untrusted procedural language' 4 | module_pathname = '$libdir/pllua' 5 | relocatable = false 6 | schema = 'pg_catalog' 7 | -------------------------------------------------------------------------------- /scripts/pllua--1.0--2.0.sql: -------------------------------------------------------------------------------- 1 | \echo Use "ALTER EXTENSION pllua UPDATE TO '2.0'" to load this file. \quit 2 | 3 | -- nothing actually needed here, the version change is cosmetic; 4 | -- but we take the opportunity to fix up some dubious properties 5 | 6 | ALTER FUNCTION pllua_call_handler() VOLATILE CALLED ON NULL INPUT; 7 | ALTER FUNCTION pllua_inline_handler(internal) VOLATILE STRICT; 8 | ALTER FUNCTION pllua_validator(oid) VOLATILE STRICT; 9 | 10 | --end 11 | -------------------------------------------------------------------------------- /scripts/pllua--2.0.sql: -------------------------------------------------------------------------------- 1 | \echo Use "CREATE EXTENSION pllua" to load this file. \quit 2 | 3 | CREATE FUNCTION pllua_call_handler() 4 | RETURNS language_handler AS 'MODULE_PATHNAME', 'pllua_call_handler' 5 | LANGUAGE C; 6 | 7 | CREATE FUNCTION pllua_inline_handler(internal) 8 | RETURNS VOID AS 'MODULE_PATHNAME', 'pllua_inline_handler' 9 | LANGUAGE C STRICT; 10 | 11 | CREATE FUNCTION pllua_validator(oid) 12 | RETURNS VOID AS 'MODULE_PATHNAME', 'pllua_validator' 13 | LANGUAGE C STRICT; 14 | 15 | CREATE TRUSTED LANGUAGE pllua 16 | HANDLER pllua_call_handler 17 | INLINE pllua_inline_handler 18 | VALIDATOR pllua_validator; 19 | 20 | -- 21 | -------------------------------------------------------------------------------- /scripts/plluau--1.0--2.0.sql: -------------------------------------------------------------------------------- 1 | \echo Use "ALTER EXTENSION plluau UPDATE TO '2.0'" to load this file. \quit 2 | 3 | -- nothing actually needed here, the version change is cosmetic 4 | -- but we take the opportunity to fix up some dubious properties 5 | 6 | ALTER FUNCTION plluau_call_handler() VOLATILE CALLED ON NULL INPUT; 7 | ALTER FUNCTION plluau_inline_handler(internal) VOLATILE STRICT; 8 | ALTER FUNCTION plluau_validator(oid) VOLATILE STRICT; 9 | 10 | --end 11 | -------------------------------------------------------------------------------- /scripts/plluau--2.0.sql: -------------------------------------------------------------------------------- 1 | \echo Use "CREATE EXTENSION plluau" to load this file. \quit 2 | 3 | CREATE FUNCTION plluau_call_handler() 4 | RETURNS language_handler AS 'MODULE_PATHNAME', 'plluau_call_handler' 5 | LANGUAGE C; 6 | 7 | CREATE FUNCTION plluau_inline_handler(internal) 8 | RETURNS VOID AS 'MODULE_PATHNAME', 'plluau_inline_handler' 9 | LANGUAGE C STRICT; 10 | 11 | CREATE FUNCTION plluau_validator(oid) 12 | RETURNS VOID AS 'MODULE_PATHNAME', 'plluau_validator' 13 | LANGUAGE C STRICT; 14 | 15 | CREATE LANGUAGE plluau 16 | HANDLER plluau_call_handler 17 | INLINE plluau_inline_handler 18 | VALIDATOR plluau_validator; 19 | 20 | -- 21 | -------------------------------------------------------------------------------- /serial_schedule: -------------------------------------------------------------------------------- 1 | # 2 | test: pllua 3 | test: pllua_old 4 | test: trusted 5 | test: arrays 6 | test: jsonb 7 | test: numerics 8 | test: horology 9 | test: horology-errors 10 | test: paths 11 | test: rowdatum 12 | test: spi 13 | test: subxact 14 | test: types 15 | test: triggers 16 | test: event_triggers 17 | -------------------------------------------------------------------------------- /sql/arrays.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | 5 | -- 6 | 7 | -- 8 | -- By having a real table with both short and long array values, 9 | -- we get to involve both short varlenas and external toast. 10 | -- 11 | create temp table adata (id serial, 12 | a integer[], 13 | b text[], 14 | c numeric[], 15 | d date[]); 16 | insert into adata(a) values 17 | (array[1,2]), 18 | (array[10,20,30,40,50]), 19 | (array(select i from generate_series(1,100) i)), 20 | (array(select i from generate_series(1,100000) i)), 21 | ('{}'); 22 | insert into adata(b) values 23 | ('{}'), 24 | (array['foo','bar','baz']), 25 | (array(select 'val'||i from generate_series(1,100) i)), 26 | (array(select 'val'||i from generate_series(1,10000) i)); 27 | insert into adata(c) values 28 | ('{}'), 29 | (array[1.234,exp(1.0::numeric(32,30)),factorial(27)]), 30 | (array(select i from generate_series(1,100) i)), 31 | (array(select i from generate_series(1,10000) i)); 32 | insert into adata(d) values 33 | ('{}'), 34 | (array[date '2017-12-11', '1968-05-10', '1983-09-26', '1962-10-27']), 35 | (array(select date '2017-01-01' + i from generate_series(0,364,18) i)); 36 | 37 | do language pllua $$ 38 | package.preload['myutil'] = function() 39 | local expect_next = 40 | { string = function(s) 41 | return string.gsub(s, "%d+$", 42 | function(n) 43 | return string.format("%d", n + 1) 44 | end) 45 | end, 46 | number = function(n) return n + 1 end, 47 | [pgtype.numeric] = function(n) return n + 1 end, 48 | } 49 | local function map(a,f) 50 | local r = {} 51 | for i = 1,#a do r[#r+1] = f(a[i]) end 52 | return r 53 | end 54 | local function summarize(a) 55 | if a == nil then return nil end 56 | local expect,first_match,result = nil,nil,{} 57 | for i = 1,#a do 58 | if first_match == nil then 59 | expect,first_match = a[i],i 60 | elseif a[i] ~= expect then 61 | if first_match < i-1 then 62 | result[#result+1] = { a[first_match], a[i-1] } 63 | else 64 | result[#result+1] = a[i-1] 65 | end 66 | expect,first_match = a[i],i 67 | end 68 | --[[ update the "expected" next element ]] 69 | expect = (expect_next[pgtype(expect) or type(expect)] 70 | or function(x) return x end)(expect) 71 | end 72 | if first_match == #a then 73 | result[#result+1] = a[#a] 74 | elseif first_match ~= nil then 75 | result[#result+1] = { a[first_match], a[#a] } 76 | end 77 | return table.concat(map(result, function(e) 78 | if type(e)=='table' then 79 | return string.format("[%s..%s]", 80 | tostring(e[1]), 81 | tostring(e[2])) 82 | else 83 | return tostring(e) 84 | end 85 | end), 86 | ',') 87 | end 88 | return { 89 | map = map, 90 | summarize = summarize 91 | } 92 | end 93 | $$; 94 | 95 | do language pllua $$ 96 | local u = require 'myutil' 97 | for r in spi.rows([[ select a, b, 98 | array_append(a, -1) as xa, 99 | array_append(b, 'wombat') as xb 100 | from adata 101 | where a is not null or b is not null 102 | order by id ]]) 103 | do 104 | print(u.summarize(r.a),u.summarize(r.b)) 105 | print(u.summarize(r.xa),u.summarize(r.xb)) 106 | end 107 | for r in spi.rows([[ select c, 108 | array_append(c, -1.0) as xc 109 | from adata 110 | where c is not null 111 | order by id ]]) 112 | do 113 | print(u.summarize(r.c)) 114 | print(u.summarize(r.xc)) 115 | end 116 | for r in spi.rows([[ select d 117 | from adata 118 | where d is not null 119 | order by id ]]) 120 | do 121 | print(r.d) 122 | end 123 | $$; 124 | 125 | create function af1(a anyarray) 126 | returns text 127 | language pllua 128 | stable 129 | as $$ 130 | return tostring(u.summarize(a)) 131 | end 132 | u = require 'myutil' 133 | do 134 | $$; 135 | 136 | -- array_append returns its result as an expanded datum 137 | 138 | select af1(a) from adata where a is not null order by id; 139 | with t as (select a from adata where a is not null order by id) 140 | select af1(array_append(a, -1)) from t; 141 | 142 | select af1(b) from adata where b is not null order by id; 143 | with t as (select b from adata where b is not null order by id) 144 | select af1(array_append(b, 'wombat')) from t; 145 | 146 | select af1(c) from adata where c is not null order by id; 147 | with t as (select c from adata where c is not null order by id) 148 | select af1(array_append(c, -1.0)) from t; 149 | 150 | select af1(d) from adata where d is not null order by id; 151 | with t as (select d from adata where d is not null order by id) 152 | select af1(array_append(d, date '1970-01-01')) from t; 153 | 154 | -- conversion edge cases 155 | 156 | create function pg_temp.af2() returns integer[] language pllua 157 | as $$ 158 | return nil 159 | $$; 160 | select pg_temp.af2(); 161 | 162 | create function pg_temp.af3() returns integer[] language pllua 163 | as $$ 164 | return 165 | $$; 166 | select pg_temp.af3(); 167 | 168 | create function pg_temp.af4() returns integer[] language pllua 169 | as $$ 170 | return 1,2 171 | $$; 172 | select pg_temp.af4(); 173 | 174 | create function pg_temp.af5() returns integer[] language pllua 175 | as $$ 176 | return pgtype(nil,0)() 177 | $$; 178 | select pg_temp.af5(); 179 | 180 | create function pg_temp.af5b() returns integer[] language pllua 181 | as $$ 182 | return {} 183 | $$; 184 | select pg_temp.af5b(); 185 | 186 | create function pg_temp.af6() returns integer[] language pllua 187 | as $$ 188 | return { 1, nil, 3 } 189 | $$; 190 | select pg_temp.af6(); 191 | 192 | create function pg_temp.af7() returns integer[] language pllua 193 | as $$ 194 | return pgtype.integer(1) 195 | $$; 196 | select pg_temp.af7(); 197 | 198 | create function pg_temp.af8() returns integer[] language pllua 199 | as $$ 200 | return { pgtype.integer(1) } 201 | $$; 202 | select pg_temp.af8(); 203 | 204 | create type acomp1 as (foo integer, bar text); 205 | 206 | create function pg_temp.af9() returns acomp1[] language pllua 207 | as $$ 208 | return { { foo = 1, bar = "zot" } } 209 | $$; 210 | select pg_temp.af9(); 211 | 212 | create function pg_temp.af10() returns acomp1[] language pllua 213 | as $$ 214 | return { pgtype(nil,0):element()(1, "zot") } 215 | $$; 216 | select pg_temp.af10(); 217 | 218 | -- 219 | -------------------------------------------------------------------------------- /sql/event_triggers.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | \set QUIET 0 5 | 6 | -- Test event triggers. 7 | 8 | create function evtrig() returns event_trigger language pllua as $$ 9 | print(trigger.event, trigger.tag) 10 | $$; 11 | 12 | create event trigger et1 on ddl_command_start execute procedure evtrig(); 13 | create event trigger et2 on ddl_command_end execute procedure evtrig(); 14 | create event trigger et3 on sql_drop execute procedure evtrig(); 15 | create event trigger et4 on table_rewrite execute procedure evtrig(); 16 | 17 | create table evt1 (a text); 18 | alter table evt1 alter column a type integer using null; 19 | drop table evt1; 20 | 21 | drop event trigger et1; 22 | drop event trigger et2; 23 | drop event trigger et3; 24 | drop event trigger et4; 25 | 26 | --end 27 | -------------------------------------------------------------------------------- /sql/horology-errors.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | 5 | -- 6 | 7 | set timezone = 'GMT'; 8 | set datestyle = 'ISO,YMD'; 9 | 10 | -- errors (not worth testing many combinations, they all share a code path) 11 | do language pllua $$ print(pgtype.time('03:45:01.234567').dow) $$; 12 | 13 | --end 14 | -------------------------------------------------------------------------------- /sql/jsonb.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | 5 | -- 6 | 7 | do language pllua $$ 8 | local jsonb = require 'pllua.jsonb' 9 | a = { json_test = "This is only a test.", 10 | foo = "If this were real data, it would make more sense.", 11 | piem = [[ 12 | Now I, even I, would celebrate 13 | In rhymes unapt the great 14 | Immortal Syracusan rivaled nevermore, 15 | Who in his wondrous lore, 16 | Passed on before, 17 | Left men his guidance 18 | How to circles mensurate. 19 | ]], 20 | names = { "Dougal", "Florence", "Ermintrude", "Zebedee", 21 | "Brian", "Dylan" }, 22 | mixed = { nil, nil, 123, "foo", nil, true, false, [23] = "fred" }, 23 | empty = {}, 24 | empty2 = {{},{}}, 25 | nested = { "arrayelem", { ["object key"] = "object val", 26 | subobject = { subarray = { { { 123 } } } } } } 27 | } 28 | b = pgtype.jsonb(a) 29 | print(b) 30 | c = pgtype.jsonb(a, { null = false }) 31 | print(c) 32 | d = pgtype.jsonb(a, { map = function(v) if type(v) == "boolean" then v = tostring(v) end return v end }) 33 | print(d) 34 | e = pgtype.jsonb(a, { array_thresh = 1 }) 35 | print(e) 36 | f = pgtype.jsonb(a, { empty_object = true }) 37 | print(f) 38 | 39 | for k,v in pairs(f) do 40 | print(k,type(v),jsonb.type(v),jsonb.type(v,true),v) 41 | if k == "mixed" then 42 | for k2,v2 in pairs(v) do print("",k2,type(v2),jsonb.type(v2),jsonb.type(v2,true),v2) end 43 | end 44 | end 45 | 46 | b { map = print, norecurse = true, pg_numeric = true, discard = true } 47 | 48 | spi.execute([[ create temp table jt1 as select $1 as a ]], b) 49 | 50 | $$; 51 | 52 | select a, pg_typeof(a) from jt1; 53 | 54 | create temp table jt2(id serial, a jsonb); 55 | insert into jt2(a) values ('1'); 56 | insert into jt2(a) values ('"foo"'); 57 | insert into jt2(a) values ('true'); 58 | insert into jt2(a) values ('null'); 59 | insert into jt2(a) values ('{"foo":123}'); 60 | insert into jt2(a) values ('{"foo":null}'); 61 | insert into jt2(a) values ('[10,20,30]'); 62 | insert into jt2(a) values ('{"foo":[2,4,6]}'); 63 | insert into jt2(a) values ('[{"foo":"bar"},{"baz":"foo"},123,null]'); 64 | -- check objects with keys that look like numbers 65 | insert into jt2(a) values ('{"1":"foo", "2":[false,true], "foo":{}}'); 66 | insert into jt2(a) values ('{"1":"foo", "2":[false,true]}'); 67 | 68 | do language pllua $$ 69 | local jsonb = require 'pllua.jsonb' 70 | s = spi.prepare([[ select a from jt2 order by id ]]) 71 | for r in s:rows() do 72 | print(r.a, jsonb.type(r.a)) 73 | b = r.a(function(k,v,...) 74 | if type(v)~="table" then 75 | print("mapfunc",type(k),k,v,...) 76 | else 77 | print("mapfunc",type(k),k,type(v),...) 78 | end 79 | return k,v 80 | end) 81 | print(type(b)) 82 | end 83 | $$; 84 | 85 | create temp table jt3(id integer, a jsonb); 86 | -- first row should be plain, then a couple with compressed values, 87 | -- then a couple with external toast 88 | insert into jt3 select i, ('[' || repeat('"foo",',10*(10^i)::integer) || i || ']')::jsonb from generate_series(1,5) i; 89 | do language pllua $$ 90 | local jsonb = require 'pllua.jsonb' 91 | s = spi.prepare([[ select a from jt3 where id = $1 ]]) 92 | for i = 1,5 do 93 | local r = (s:execute(i))[1] 94 | local a = r.a() 95 | print(jsonb.type(r.a),#a,a[#a]) 96 | end 97 | $$; 98 | 99 | -- test jsonb in jsonb and similar paths 100 | 101 | do language pllua $$ 102 | local jtst1 = pgtype.jsonb('"foo"') -- json scalar 103 | local jtst2 = pgtype.jsonb('{"foo":true,"bar":[1,2,false]}') -- json container 104 | local ts1 = pgtype.timestamp('2017-12-19 12:00:00') 105 | print(pgtype.jsonb({ v1 = jtst1, 106 | v2 = jtst2, 107 | v3 = ts1 })) 108 | $$; 109 | 110 | -- test round-trip conversions 111 | 112 | do language pllua $$ 113 | local j_in = pgtype.jsonb('{"foo":[1,null,false,{"a":null,"b":[]},{},[]]}') 114 | local nvl = {} 115 | local val = j_in{ null = nvl } 116 | local j_out = pgtype.jsonb(val, { null = nvl }) 117 | print(j_in) 118 | print(j_out) 119 | $$; 120 | 121 | --end 122 | -------------------------------------------------------------------------------- /sql/lua54.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | 5 | -- 6 | 7 | -- 5.4-specific features. 8 | 9 | -- simple close metamethod 10 | 11 | do language pllua $$ 12 | local tc 13 | = setmetatable({}, { __close = function() print("close called") end }) 14 | spi.error("error") 15 | $$; 16 | 17 | -- close metamethod on cursor 18 | begin; 19 | do language pllua $$ 20 | for r in spi.rows([[ select * from generate_series(1,5) i ]]) do 21 | print(r) 22 | if r.i > 2 then break end 23 | end 24 | $$; 25 | select * from pg_cursors; -- should be empty 26 | commit; 27 | 28 | -- lua error in close method 29 | do language pllua $$ 30 | local tc 31 | = setmetatable({}, { __close = function() error("inner error") end }) 32 | error("outer error") 33 | $$; 34 | 35 | -- db access in close method with outer lua error 36 | do language pllua $$ 37 | local tc 38 | = setmetatable({}, { __close = function() print(pgtype.numeric(0)) end }) 39 | error("outer error") 40 | $$; 41 | 42 | -- db access in close method with outer db error 43 | do language pllua $$ 44 | local tc 45 | = setmetatable({}, { __close = function() print(pgtype.numeric(0)) end }) 46 | spi.error("outer error") 47 | $$; 48 | 49 | -- close metamethod in SRF 50 | create function pg_temp.sf1(n integer) returns setof integer 51 | language pllua 52 | as $$ 53 | local x 54 | = setmetatable({}, { __close = function() print("close called") end }) 55 | for i = n, n+3 do 56 | coroutine.yield(i) 57 | end 58 | $$; 59 | select * from (values (1),(2)) v(n), lateral (select * from pg_temp.sf1(v.n) limit 1) s; 60 | 61 | -- error in close metamethod in SRF 62 | create function pg_temp.sf2(n integer) returns setof integer 63 | language pllua 64 | as $$ 65 | local x 66 | = setmetatable({}, { __close = function() error("inner error") end }) 67 | for i = n, n+3 do 68 | coroutine.yield(i) 69 | end 70 | $$; 71 | select * from (values (1),(2)) v(n), lateral (select * from pg_temp.sf2(v.n) limit 1) s; 72 | 73 | --end 74 | -------------------------------------------------------------------------------- /sql/numerics.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | 5 | -- test numerics 6 | 7 | create function lua_numexec(code text, n1 numeric, n2 numeric) 8 | returns text 9 | language pllua 10 | as $$ 11 | local f,e = load("return function(n1,n2) return "..code.." end", code, "t", self) 12 | assert(f,e) 13 | f = f() 14 | assert(f) 15 | return tostring(f(n1,n2)) 16 | end 17 | do 18 | num = require "pllua.numeric" 19 | $$; 20 | create function pg_numexec(code text, n1 numeric, n2 numeric) 21 | returns text 22 | language plpgsql 23 | as $$ 24 | declare 25 | r text; 26 | begin 27 | execute format('select (%s)::text', 28 | regexp_replace(regexp_replace(code, '\mnum\.', '', 'g'), 29 | '\mn([0-9])', '$\1', 'g')) 30 | into r using n1,n2; 31 | return r; 32 | end; 33 | $$; 34 | 35 | with 36 | t as (select code, 37 | lua_numexec(code, 5439.123456, -1.9) as lua, 38 | pg_numexec(code, 5439.123456, -1.9) as pg 39 | from unnest(array[ 40 | $$ n1 + n2 $$, $$ n1 - n2 $$, 41 | $$ n1 * n2 $$, $$ n1 / n2 $$, 42 | $$ n1 % n2 $$, $$ n1 ^ n2 $$, 43 | $$ (-n1) + n2 $$, $$ (-n1) - n2 $$, 44 | $$ (-n1) * n2 $$, $$ (-n1) / n2 $$, 45 | $$ (-n1) % n2 $$, $$ (-n1) ^ 3 $$, 46 | $$ (-n1) + (-n2) $$, $$ (-n1) - (-n2) $$, 47 | $$ (-n1) * (-n2) $$, $$ (-n1) / (-n2) $$, 48 | $$ (-n1) % (-n2) $$, $$ (-n1) ^ (-3) $$, 49 | $$ (n1) > (n2) $$, $$ (n1) < (n2) $$, 50 | $$ (n1) >= (n2) $$, $$ (n1) <= (n2) $$, 51 | $$ (n1) > (n2*10000) $$, 52 | $$ (n1) < (n2*10000) $$, 53 | $$ (n1) >= (n2 * -10000) $$, 54 | $$ (n1) <= (n2 * -10000) $$, 55 | $$ num.round(n1) $$, $$ num.round(n2) $$, 56 | $$ num.round(n1,4) $$, $$ num.round(n1,-1) $$, 57 | $$ num.trunc(n1) $$, $$ num.trunc(n2) $$, 58 | $$ num.trunc(n1,4) $$, $$ num.trunc(n1,-1) $$, 59 | $$ num.floor(n1) $$, $$ num.floor(n2) $$, 60 | $$ num.ceil(n1) $$, $$ num.ceil(n2) $$, 61 | $$ num.abs(n1) $$, $$ num.abs(n2) $$, 62 | $$ num.sign(n1) $$, $$ num.sign(n2) $$, 63 | $$ num.sqrt(n1) $$, 64 | $$ num.exp(12.3) $$, 65 | $$ num.exp(n2) $$ 66 | ]) as u(code)) 67 | select (lua = pg) as ok, * from t; 68 | 69 | -- calculate pi to 40 places 70 | 71 | do language pllua $$ 72 | -- Chudnovsky formula; ~14 digits per round, we use 4 rounds 73 | local num = require 'pllua.numeric' 74 | local prec = 100 -- precision of intermediate values 75 | local function fact(n) 76 | local r = pgtype.numeric(1):round(prec) 77 | for i = 2,n do r = r * i end 78 | return r:round(prec) 79 | end 80 | local c640320 = pgtype.numeric(640320):round(prec) 81 | local c13591409 = pgtype.numeric(13591409):round(prec) 82 | local c545140134 = pgtype.numeric(545140134):round(prec) 83 | local function chn(k) 84 | return (fact(6*k) * (c13591409 + (c545140134 * k))) 85 | / (fact(3*k) * fact(k)^3 * (-c640320)^(3*k)) 86 | end 87 | local function pi() 88 | return (1 / ((chn(0) + chn(1) + chn(2) + chn(3))*12 / num.sqrt(c640320^3))):round(40) 89 | end 90 | print(pi()) 91 | $$; 92 | 93 | -- check sanity of maxinteger/mininteger 94 | 95 | do language pllua $$ 96 | local num = require 'pllua.numeric' 97 | local maxi = num.maxinteger 98 | local mini = num.mininteger 99 | print(type(num.tointeger(maxi)), type(num.tointeger(maxi+1))) 100 | print(type(num.tointeger(mini)), type(num.tointeger(mini-1))) 101 | $$ 102 | 103 | --end 104 | -------------------------------------------------------------------------------- /sql/paths.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | 5 | -- 6 | 7 | create function pg_temp.tmp1(n text) returns text 8 | language plluau immutable strict 9 | as $$ return (require "pllua.paths")[n]() $$; 10 | 11 | -- some of the dirs might not actually exist, so we test only the 12 | -- important ones. We can't actually test that the dir exists or what 13 | -- the contents are, since many pg versions reject pg_stat_file on 14 | -- absolute paths; so just check that we got some string that looks 15 | -- like a path. 16 | select u.n, f.path ~ '^(?:[[:alpha:]]:)?/' 17 | from unnest(array['bin','lib','libdir','pkglib','share']) 18 | with ordinality as u(n,ord), 19 | pg_temp.tmp1(u.n) f(path) 20 | order by u.ord; 21 | 22 | --end 23 | -------------------------------------------------------------------------------- /sql/pllua.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | CREATE EXTENSION pllua; 4 | CREATE EXTENSION plluau; 5 | 6 | \set VERBOSITY terse 7 | 8 | -- smoke test 9 | 10 | do language pllua $$ print "hello world!" $$; 11 | do language plluau $$ print "hello world!" $$; 12 | 13 | create function pg_temp.f1() returns text language pllua as $$ return "hello world" $$; 14 | select pg_temp.f1(); 15 | 16 | create function pg_temp.f2() returns text language plluau as $$ return "hello world" $$; 17 | select pg_temp.f2(); 18 | 19 | -- Rest of this file concentrates on simple tests of code paths in 20 | -- compile, exec, and interpreter setup. Tests of other parts of the 21 | -- module are separate. 22 | 23 | -- validator 24 | create function pg_temp."bad name"() returns text language pllua as $$ $$; 25 | create function pg_temp.f3("bad arg" integer) returns text language pllua as $$ $$; 26 | 27 | -- simple params and results (see types.sql for detailed checks) 28 | 29 | create function pg_temp.f4(a integer) returns integer language pllua as $$ return a + 1 $$; 30 | select pg_temp.f4(1); 31 | 32 | create function pg_temp.f5(a text) returns text language pllua as $$ return a.."bar" $$; 33 | select pg_temp.f5('foo'); 34 | 35 | create function pg_temp.f6(a text, b integer) returns text language pllua as $$ return a..b $$; 36 | select pg_temp.f6('foo',1); 37 | 38 | -- try some polymorphism too 39 | create function pg_temp.f7(a anyelement) returns anyelement language pllua as $$ return a $$; 40 | select pg_temp.f7(text 'foo'); 41 | select pg_temp.f7(json '{"foo":1}'); 42 | --select pg_temp.f7(xml 'bar'); -- don't bother with this, might be compiled out 43 | select pg_temp.f7(varchar 'foo'); 44 | select 'x',pg_temp.f7('foo'::char(20)),'x'; 45 | select pg_temp.f7(cstring 'foo'); 46 | select pg_temp.f7(name 'foo'); 47 | select pg_temp.f7(bytea 'foo\000bar'); 48 | select pg_temp.f7(smallint '2'); 49 | select pg_temp.f7(integer '2'); 50 | select pg_temp.f7(bigint '123456789012345'); 51 | select pg_temp.f7(oid '10'); 52 | select pg_temp.f7(oid '4294967295'); 53 | select pg_temp.f7(true); 54 | select pg_temp.f7(false); 55 | select pg_temp.f7(1.5::float8); 56 | select pg_temp.f7(1.5::float4); 57 | 58 | -- variadics 59 | 60 | create function pg_temp.f8(a text, variadic b integer[]) returns void language pllua as $$ print(a,type(b),b) $$; 61 | select pg_temp.f8('foo', 1, 2, 3); 62 | 63 | create function pg_temp.f9(a integer, variadic b text[]) returns void language pllua as $$ print(a,type(b),b) $$; 64 | select pg_temp.f9(1, 'foo', 'bar', 'baz'); 65 | 66 | create function pg_temp.f10(a integer, variadic "any") returns void language pllua as $$ print(a,...) $$; 67 | select pg_temp.f10(1, 'foo', 2, 'baz'); 68 | 69 | -- SRF code paths 70 | 71 | create function pg_temp.f11(a integer) returns setof text 72 | language pllua as $$ return $$; -- 0 rows 73 | select * from pg_temp.f11(1); 74 | 75 | create function pg_temp.f11b(a integer) returns setof text 76 | language pllua as $$ return 'foo' $$; -- 1 row 77 | select * from pg_temp.f11b(1); 78 | 79 | create function pg_temp.f12(a integer) returns setof text 80 | language pllua as $$ coroutine.yield() $$; -- 1 row, null 81 | select * from pg_temp.f12(1); 82 | 83 | create function pg_temp.f13(a integer) returns setof text 84 | language pllua as $$ for i = 1,a do coroutine.yield("row "..i) end $$; 85 | select * from pg_temp.f13(4); 86 | 87 | create function pg_temp.f14(a integer, out x text, out y integer) returns setof record 88 | language pllua as $$ for i = 1,a do coroutine.yield("row "..i, i) end $$; 89 | select * from pg_temp.f14(4); 90 | 91 | create function pg_temp.f15(a integer) returns table(x text, y integer) 92 | language pllua as $$ for i = 1,a do coroutine.yield("row "..i, i) end $$; 93 | select * from pg_temp.f15(4); 94 | 95 | create function pg_temp.f16(a inout integer, x out text) returns setof record 96 | language pllua as $$ for i = 1,a do coroutine.yield(i, "row "..i) end $$; 97 | select * from pg_temp.f16(4); 98 | 99 | -- SRF vs null returns 100 | 101 | create function pg_temp.f16b(a integer) returns table(x text, y integer) 102 | language pllua as $$ coroutine.yield() $$; -- 1 row, null 103 | select * from pg_temp.f16b(1); 104 | 105 | create function pg_temp.f16c(a integer) returns table(x text, y integer) 106 | language pllua as $$ coroutine.yield() for i = 1,a do coroutine.yield('foo',i) end $$; 107 | select * from pg_temp.f16c(3); 108 | 109 | -- compiler and validator code paths 110 | 111 | do language pllua $$ _G.rdepth = 40 $$; -- global var hack 112 | 113 | -- This function will try and call itself at a point where it is visible 114 | -- but has no definition interned yet; the recursive call will likewise 115 | -- not see an interned definition and recurses again. without any limits 116 | -- this would hit a stack depth check somewhere; we eat about 3 levels of 117 | -- C function recursion inside lua each time, and that gets capped at 200. 118 | -- We don't expect this to be actually useful, the test is just that we 119 | -- don't crash. 120 | create function pg_temp.f17(a integer) returns integer language pllua 121 | as $$ 122 | return a 123 | end 124 | do 125 | if _G.rdepth > 0 then 126 | _G.rdepth = _G.rdepth - 1 127 | u = spi.execute("select pg_temp.f17(1)") 128 | end 129 | $$; 130 | select pg_temp.f17(1); 131 | 132 | create type pg_temp.t1 as (a integer, b text); 133 | create function pg_temp.f18(a integer, b text) returns pg_temp.t1 134 | language pllua as $$ return a,b $$; 135 | select * from pg_temp.f18(123,'foo'); 136 | 137 | create function pg_temp.f19(a integer) returns text language pllua as $$ return 'foo '..a $$; 138 | select pg_temp.f19(2); 139 | create or replace function pg_temp.f19(a integer) returns text language pllua as $$ return 'bar '..a $$; 140 | select pg_temp.f19(3); 141 | 142 | -- trusted interpreter setup 143 | 144 | -- check we really do have different interpreters 145 | 146 | -- this is hard because we intentionally isolate trusted-language code 147 | -- from the normal global env of its interpreter, so we would only be 148 | -- able to verify isolation if we were able to break out of the 149 | -- sandbox, which would rather defeat the point. We have to take the 150 | -- outside view, by generating an interpreter-dependent value and 151 | -- checking that it differs. The stringification of a closure, such as 152 | -- server.error, suffices since this contains an interpreter-dependent 153 | -- address (whereas base C functions do not differ between 154 | -- interpreters in recent lua versions). 155 | 156 | create function pg_temp.f20() returns text language pllua as $$ return tostring(spi.error) $$; 157 | create function pg_temp.f21() returns text language plluau as $$ return tostring(spi.error) $$; 158 | select pg_temp.f20() as a intersect select pg_temp.f21(); -- should be empty 159 | 160 | -- check the global table 161 | do language pllua $$ 162 | local gk = { "io", "dofile", "debug" } -- must not exist 163 | for i = 1,#gk do print(gk[i],type(_G[gk[i]])) end 164 | $$; 165 | do language plluau $$ 166 | local gk = { "io", "dofile" } -- probably exist 167 | for i = 1,#gk do print(gk[i],type(_G[gk[i]])) end 168 | $$; 169 | 170 | -- check that trusted gets only the restricted os module, even from 171 | -- require 172 | do language pllua $$ 173 | local os = require 'os' 174 | local gk = { "time", "difftime", "execute", "getenv", "exit" } 175 | for i = 1,#gk do print(gk[i],type(os[gk[i]])) end 176 | $$; 177 | 178 | -- check that trusted can't require dangerous core modules 179 | do language pllua $$ 180 | print((lpcall(require,"debug"))) 181 | print((lpcall(require,"io"))) 182 | $$; 183 | 184 | --end 185 | -------------------------------------------------------------------------------- /sql/procedures.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | 5 | -- 6 | 7 | -- test procedures, non-atomic DO-blocks, and spi.commit/rollback 8 | -- (all pg11 new features) 9 | 10 | create table xatst2 (a integer); 11 | 12 | create procedure pg_temp.tp1(a text) 13 | language pllua 14 | as $$ 15 | print("hello world", a) 16 | print(spi.is_atomic() and "atomic context" or "non-atomic context") 17 | $$; 18 | 19 | call pg_temp.tp1('foo'); 20 | begin; call pg_temp.tp1('foo'); commit; 21 | 22 | do language pllua $$ 23 | print(spi.is_atomic() and "atomic context" or "non-atomic context") 24 | $$; 25 | 26 | begin; 27 | do language pllua $$ 28 | print(spi.is_atomic() and "atomic context" or "non-atomic context") 29 | $$; 30 | commit; 31 | 32 | create procedure pg_temp.tp2() 33 | language pllua 34 | as $$ 35 | local stmt = spi.prepare([[ insert into xatst2 values ($1) ]]); 36 | stmt:execute(1); 37 | spi.commit(); 38 | stmt:execute(2); 39 | spi.rollback(); 40 | stmt:execute(3); 41 | spi.commit(); 42 | stmt:execute(4); 43 | $$; 44 | 45 | call pg_temp.tp2(); 46 | 47 | -- should now be three different xids in xatst2, and 3 rows 48 | select count(*), count(distinct age(xmin)) from xatst2; 49 | 50 | -- proper handling of open cursors 51 | 52 | create procedure pg_temp.tp3() 53 | language pllua 54 | as $$ 55 | local stmt = spi.prepare([[ select i from generate_series(1,10) i ]]); 56 | for r in stmt:rows() do 57 | print(r.i) 58 | spi.commit(); 59 | end 60 | $$; 61 | 62 | call pg_temp.tp3(); 63 | 64 | create procedure pg_temp.tp4() 65 | language pllua 66 | as $$ 67 | local stmt = spi.prepare([[ select i from generate_series(1,10) i ]], {}, { hold = true }); 68 | for r in stmt:rows() do 69 | print(r.i) 70 | spi.commit(); 71 | end 72 | $$; 73 | 74 | call pg_temp.tp4(); 75 | 76 | -- no commit inside subxact 77 | 78 | truncate table xatst2; 79 | 80 | do language pllua $$ 81 | local stmt = spi.prepare([[ insert into xatst2 values ($1) ]]); 82 | stmt:execute(1); 83 | spi.commit(); 84 | stmt:execute(2); 85 | print(pcall(function() stmt:execute(3) spi.commit() end)) 86 | -- the commit threw a lua error and the subxact was rolled back, 87 | -- so we should be in the same xact as row 2 88 | stmt:execute(4); 89 | spi.commit(); 90 | $$; 91 | 92 | -- should now be two different xids in xatst2, and 3 rows 93 | select count(*), count(distinct age(xmin)) from xatst2; 94 | 95 | --end 96 | -------------------------------------------------------------------------------- /sql/rowdatum.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | 5 | -- 6 | 7 | -- tests of operations on nested row values 8 | 9 | create type ntype1 as (fred integer, jim numeric); 10 | create type ntype2 as (thingy text[], wotsit ntype1); 11 | create type ntype3 as (foo text, bar ntype1, baz ntype2); 12 | create type ntype4 as (col0 text, col1 ntype1, col2 ntype2, col3 ntype3); 13 | 14 | -- 15 | do language pllua $$ 16 | local r = pgtype.ntype1(1,2) 17 | print(r.fred, r.jim) -- deform 18 | r.fred = 3 -- explode after deform 19 | print(r.fred, r.jim) 20 | r.jim = 4 -- modify pre-exploded value 21 | print(pgtype.ntype1(r), r.fred, r.jim) 22 | r = pgtype.ntype1(1,2) 23 | r.fred = 3 -- explode before deform 24 | print(r.fred, r.jim) 25 | r.jim = 4 -- modify pre-exploded value 26 | print(pgtype.ntype1(r), r.fred, r.jim) 27 | $$; 28 | 29 | do language pllua $$ 30 | local r0 = pgtype.ntype4("zzz", 31 | { fred = 1, jim = 2 }, 32 | { thingy = {"a","b","c"}, 33 | wotsit = { fred = 3, jim = 4} 34 | }, 35 | { foo = "abcde", 36 | bar = { fred = 5, jim = 6 }, 37 | baz = { thingy = {"x","y","z"}, 38 | wotsit = { fred = 7, jim = 8 } } 39 | }) 40 | local r = pgtype.ntype4(r0) 41 | -- deform from inner to outer 42 | print(r.col3.baz.wotsit.fred) 43 | print(r.col3.foo) 44 | print(r.col1.fred) 45 | -- and from outer to inner 46 | print(r.col2.wotsit.jim) 47 | print(r.col3.bar.jim) 48 | -- start over with un-deformed datum 49 | r = pgtype.ntype4(r0) 50 | --deform from outer to inner 51 | print(r.col1, r.col2, r.col3) 52 | print(r.col1.jim, r.col2.thingy[3], r.col3.foo) 53 | print(r.col2.wotsit.jim, r.col3.bar.jim, r.col3.baz.thingy[3]) 54 | print(r.col3.baz.wotsit.jim) 55 | print(r) print(pgtype.ntype4(r)) 56 | -- start over with un-deformed datum 57 | r = pgtype.ntype4(r0) 58 | -- explode from inner 59 | r.col3.baz.wotsit.jim = 10 60 | r.col2.thingy[2] = "k" 61 | print(r) print(pgtype.ntype4(r)) 62 | -- start over with un-deformed datum 63 | r = pgtype.ntype4(r0) 64 | -- explode from middle 65 | r.col3.foo = "edcba" 66 | r.col1.fred = -1 67 | r.col3.baz.wotsit.jim = 80 68 | r.col3.baz.wotsit.fred = 0 69 | print(r) print(pgtype.ntype4(r)) 70 | -- start over with un-deformed datum 71 | r = pgtype.ntype4(r0) 72 | -- explode from top 73 | r.col0 = "yyy" 74 | r.col3.baz.thingy[1] = "@" 75 | r.col1.jim = 20 76 | print(r) print(pgtype.ntype4(r)) 77 | -- start over with un-deformed datum 78 | r = pgtype.ntype4(r0) 79 | -- partially deform then explode from a deformed element 80 | print(r.col0, r.col2.wotsit.fred) 81 | r.col2.wotsit.jim = 40 82 | r.col1.jim = 0 83 | r.col3.bar.jim = 0 84 | print(r) print(pgtype.ntype4(r)) 85 | -- start over with un-deformed datum 86 | r = pgtype.ntype4(r0) 87 | -- partially deform then explode from an undeformed element 88 | print(r.col0, r.col2.wotsit.fred) 89 | r.col3.bar.jim = 0 90 | r.col3.bar.fred = -1 91 | r.col2.wotsit.fred = 0 92 | r.col0 = "yyy" 93 | r.col1 = { fred = 100, jim = 200 } 94 | print(r) print(pgtype.ntype4(r)) 95 | $$; 96 | 97 | -- various dropped-column scenarios 98 | 99 | create table ntab1 (fred integer, sheila text, jim numeric); 100 | create table ntab2 (thingy text[], wotsit ntab1); 101 | create table ntab3 (foo text, bar ntab1, baz ntab2); 102 | create table ntab4 (col0 text, col1 ntab1, col2 ntab2, col3 ntab3); 103 | 104 | insert into ntab1 values (1, 'foo', 1.23); 105 | insert into ntab2 select array['x','y'], t from ntab1 t; 106 | insert into ntab3 select 'wot', t1, t2 from ntab1 t1, ntab2 t2; 107 | insert into ntab4 select 'rabbit', t1, t2, t3 from ntab1 t1, ntab2 t2, ntab3 t3; 108 | 109 | alter table ntab1 drop column sheila; 110 | 111 | do language pllua $$ 112 | local r = pgtype.ntab1(1,2) 113 | print(r.fred, r.jim) -- deform 114 | r.fred = 3 -- explode after deform 115 | print(r.fred, r.jim) 116 | r.jim = 4 -- modify pre-exploded value 117 | print(pgtype.ntab1(r), r.fred, r.jim) 118 | r = pgtype.ntab1(1,2) 119 | r.fred = 3 -- explode before deform 120 | print(r.fred, r.jim) 121 | r.jim = 4 -- modify pre-exploded value 122 | print(pgtype.ntab1(r), r.fred, r.jim) 123 | $$; 124 | 125 | do language pllua $$ 126 | local tbl = spi.execute([[ select t from ntab4 t, generate_series(1,8) ]]) 127 | local r = tbl[1].t 128 | -- deform from inner to outer 129 | print(r.col3.baz.wotsit.fred) 130 | print(r.col3.foo) 131 | print(r.col1.fred) 132 | -- and from outer to inner 133 | print(r.col2.wotsit.jim) 134 | print(r.col3.bar.jim) 135 | -- start over with un-deformed datum 136 | r = tbl[2].t 137 | --deform from outer to inner 138 | print(r.col1, r.col2, r.col3) 139 | print(r.col1.jim, r.col2.thingy[3], r.col3.foo) 140 | print(r.col2.wotsit.jim, r.col3.bar.jim, r.col3.baz.thingy[3]) 141 | print(r.col3.baz.wotsit.jim) 142 | print(r) print(pgtype.ntab4(r)) 143 | -- start over with un-deformed datum 144 | r = tbl[3].t 145 | -- explode from inner 146 | r.col3.baz.wotsit.jim = 10 147 | r.col2.thingy[2] = "k" 148 | print(r) print(pgtype.ntab4(r)) 149 | -- start over with un-deformed datum 150 | r = tbl[4].t 151 | -- explode from middle 152 | r.col3.foo = "edcba" 153 | r.col1.fred = -1 154 | r.col3.baz.wotsit.jim = 80 155 | r.col3.baz.wotsit.fred = 0 156 | print(r) print(pgtype.ntab4(r)) 157 | -- start over with un-deformed datum 158 | r = tbl[5].t 159 | -- explode from top 160 | r.col0 = "yyy" 161 | r.col3.baz.thingy[1] = "@" 162 | r.col1.jim = 20 163 | print(r) print(pgtype.ntab4(r)) 164 | -- start over with un-deformed datum 165 | r = tbl[6].t 166 | -- partially deform then explode from a deformed element 167 | print(r.col0, r.col2.wotsit.fred) 168 | r.col2.wotsit.jim = 40 169 | r.col1.jim = 0 170 | r.col3.bar.jim = 0 171 | print(r) print(pgtype.ntab4(r)) 172 | -- start over with un-deformed datum 173 | r = tbl[7].t 174 | -- partially deform then explode from an undeformed element 175 | print(r.col0, r.col2.wotsit.fred) 176 | r.col3.bar.jim = 0 177 | r.col3.bar.fred = -1 178 | r.col2.wotsit.fred = 0 179 | r.col0 = "yyy" 180 | r.col1 = { fred = 100, jim = 200 } 181 | print(r) print(pgtype.ntab4(r)) 182 | $$; 183 | 184 | --end 185 | -------------------------------------------------------------------------------- /sql/spi.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | 5 | -- 6 | 7 | -- Test of SPI-related functionality. 8 | 9 | create temp table tsttab ( 10 | id integer primary key, 11 | a integer, 12 | b text, 13 | c numeric, 14 | d date 15 | ); 16 | insert into tsttab(id, a,b,c,d) 17 | values (1, 1,'foo',2.34,'2017-01-01'), 18 | (2, 2,'bar',2.34,'2017-02-01'), 19 | (3, 3,'baz',2.34,'2017-03-01'), 20 | (4, 4, 'fred',2.34,'2017-04-01'), 21 | (5, 5,'jim',2.34,'2017-05-01'), 22 | (6, 6,'sheila',2.34,'2017-06-01'); 23 | 24 | -- basics 25 | 26 | do language pllua $$ 27 | local tbl 28 | tbl = spi.execute([[ select 1 as a, 'foo'::text as b ]]) 29 | print(#tbl,tbl[1],type(tbl[1])) 30 | print(tbl[1].a,tbl[1].b) 31 | tbl = spi.execute([[ select i, 'foo'::text as b from generate_series(1,10000) i ]]) 32 | print(#tbl,tbl[1],tbl[10000]) 33 | tbl = spi.execute([[ select * from tsttab order by id ]]) 34 | for i = 1,#tbl do print(tbl[i]) end 35 | $$; 36 | 37 | -- statements 38 | 39 | do language pllua $$ 40 | local stmt,tbl 41 | stmt = spi.prepare([[ select * from tsttab where id=$1 ]], {"integer"}) 42 | tbl = stmt:execute(1) 43 | print(tbl[1]) 44 | -- __call metamethod 45 | tbl = stmt(6) 46 | print(tbl[1]) 47 | stmt = spi.prepare([[ select * from tsttab where id = ANY ($1) order by id ]], 48 | {pgtype.array.integer}) 49 | tbl = stmt:execute(pgtype.array.integer(1,nil,3)) 50 | print(#tbl,tbl[1],tbl[2]) 51 | -- type deduction: 52 | stmt = spi.prepare([[ select 1 + $1 as a, pg_typeof($1) ]]) 53 | tbl = stmt:execute(1) 54 | print(#tbl,tbl[1]) 55 | $$; 56 | 57 | -- iterators 58 | 59 | do language pllua $$ 60 | for r in spi.rows([[ select * from tsttab order by id ]]) do 61 | print(r) 62 | end 63 | stmt = spi.prepare([[ select * from tsttab where id = ANY ($1) ]], 64 | {pgtype.array.integer}) 65 | for r in stmt:rows(pgtype.array.integer(1,nil,3)) do 66 | print(r) 67 | end 68 | $$; 69 | 70 | do language pllua $$ 71 | local c = spi.newcursor('curs1') 72 | local stmt = spi.prepare([[ select * from tsttab order by id for update ]]) 73 | c:open(stmt) 74 | for r in c:rows() do 75 | print(r) 76 | if r.id == 3 then 77 | spi.execute([[ update tsttab set c = c + 10 where current of curs1 ]]) 78 | end 79 | end 80 | for r in spi.rows([[ select * from tsttab order by id ]]) do 81 | print(r) 82 | end 83 | spi.execute([[ update tsttab set c = c - 10 where id=3 ]]) 84 | c:close() 85 | $$; 86 | 87 | -- cursors 88 | 89 | begin; 90 | declare foo scroll cursor for select * from tsttab order by id; 91 | do language pllua $$ 92 | local c = spi.findcursor("foo") 93 | local tbl 94 | tbl = c:fetch(1,'next') 95 | print(#tbl,tbl[1]) 96 | tbl = c:fetch(2,'forward') -- same as 'next' 97 | print(#tbl,tbl[1],tbl[2]) 98 | tbl = c:fetch(1,'absolute') 99 | print(#tbl,tbl[1]) 100 | tbl = c:fetch(4,'relative') 101 | print(#tbl,tbl[1]) 102 | tbl = c:fetch(1,'prior') -- same as 'backward' 103 | print(#tbl,tbl[1]) 104 | tbl = c:fetch(1,'backward') 105 | print(#tbl,tbl[1]) 106 | print(c:isopen()) 107 | spi.execute("close foo") 108 | print(c:isopen()) 109 | $$; 110 | commit; 111 | 112 | do language pllua $$ 113 | local c = spi.newcursor("bar") 114 | c:open([[ select * from tsttab where id >= $1 order by id ]], 3) 115 | local tbl 116 | tbl = c:fetch(1,'next') 117 | print(#tbl,tbl[1]) 118 | for k,v in ipairs(spi.execute([[ select name, statement from pg_cursors ]])) do 119 | print(v.name, v.statement) 120 | end 121 | c:close() 122 | for k,v in ipairs(spi.execute([[ select name, statement from pg_cursors ]])) do 123 | print(v.name, v.statement) 124 | end 125 | c:open([[ select * from tsttab where id < $1 order by id desc ]], 3) 126 | tbl = c:fetch(3,'next') 127 | print(#tbl,tbl[1],tbl[2]) 128 | for k,v in ipairs(spi.execute([[ select name, statement from pg_cursors ]])) do 129 | print(v.name, v.statement) 130 | end 131 | c:close() 132 | $$; 133 | 134 | -- cursor options on statement 135 | do language pllua $$ 136 | local stmt = spi.prepare([[ select * from tsttab where id >= $1 order by id ]], 137 | {"integer"}, { scroll = true, fast_start = true, generic_plan = true }) 138 | local stmt2 = spi.prepare([[ select * from tsttab where id >= $1 order by id ]], 139 | {"integer"}, { no_scroll = true }) 140 | local c = stmt:getcursor(4) 141 | local tbl 142 | tbl = c:fetch(3,'next') 143 | print(#tbl,tbl[1],tbl[2]) 144 | c:move(0,'absolute') 145 | tbl = c:fetch(3,'next') 146 | print(#tbl,tbl[1],tbl[2]) 147 | for k,v in ipairs(spi.execute([[ select name, statement, is_scrollable from pg_cursors ]])) do 148 | print(v.name, v.statement, v.is_scrollable) 149 | end 150 | c:close() 151 | c = stmt2:getcursor(4) 152 | local c2 = spi.findcursor(c:name()) 153 | print(c:name(), rawequal(c,c2)) 154 | for k,v in ipairs(spi.execute([[ select name, statement, is_scrollable from pg_cursors ]])) do 155 | print(v.name, v.statement, v.is_scrollable) 156 | end 157 | c:close() 158 | $$; 159 | 160 | -- check missing params are OK 161 | do language pllua $$ 162 | local stmt = spi.prepare([[ select * from generate_series($1::integer, $3) i ]]); 163 | print(stmt:argtype(1):name()) 164 | print(type(stmt:argtype(2))) 165 | print(stmt:argtype(3):name()) 166 | $$; 167 | 168 | -- check execute_count 169 | do language pllua $$ 170 | local q = [[ select * from generate_series($1::integer,$2) i ]] 171 | local r1 = spi.execute_count(q, 2, 1, 5) 172 | print(#r1) 173 | local s = spi.prepare(q, {"integer","integer"}) 174 | r1 = s:execute_count(3,1,5) 175 | print(#r1) 176 | $$; 177 | 178 | -- cursors as parameters and return values 179 | 180 | create function do_fetch(c refcursor) returns void language pllua as $$ 181 | while true do 182 | local r = (c:fetch())[1] 183 | if r==nil then break end 184 | print(r) 185 | end 186 | $$; 187 | 188 | create function do_exec(q text, n text) returns refcursor language pllua as $$ 189 | local s = spi.prepare(q) 190 | local c = spi.newcursor(n) 191 | return c:open(s):disown() 192 | $$; 193 | 194 | begin; 195 | declare mycur cursor for select * from tsttab order by id; 196 | select do_fetch('mycur'); 197 | commit; 198 | 199 | begin; 200 | select do_exec('select * from tsttab order by id desc', 'mycur2'); 201 | do language pllua $$ collectgarbage() $$; -- check cursor stays open 202 | fetch all from mycur2; 203 | commit; 204 | 205 | -- check correct error handling inside coroutines (issue #15) 206 | 207 | create function pg_temp.f1() returns setof text language pllua as $$ 208 | local r = spi.rows('syntaxerror') 209 | return 'notreached' 210 | $$; 211 | select * from pg_temp.f1(); 212 | 213 | --end 214 | -------------------------------------------------------------------------------- /sql/subxact.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | 5 | -- 6 | 7 | create table xatst (a integer); 8 | 9 | do language pllua $$ 10 | local stmt = spi.prepare([[ insert into xatst values ($1) ]]); 11 | stmt:execute(1); 12 | pcall(function() stmt:execute(2) end) 13 | stmt:execute(3); 14 | $$; 15 | 16 | -- should now be two different xids in xatst, and 3 rows 17 | select count(*), count(distinct age(xmin)) from xatst; 18 | 19 | truncate table xatst; 20 | 21 | do language pllua $$ 22 | local stmt = spi.prepare([[ insert into xatst values ($1) ]]); 23 | stmt:execute(1); 24 | print(pcall(function() stmt:execute(2) error("foo") end)) 25 | stmt:execute(3); 26 | $$; 27 | 28 | -- should now be one xid in xatst, and 2 rows 29 | select count(*), count(distinct age(xmin)) from xatst; 30 | 31 | truncate table xatst; 32 | 33 | do language pllua $$ 34 | local stmt = spi.prepare([[ insert into xatst values ($1) ]]); 35 | stmt:execute(1); 36 | print(pcall(function() stmt:execute(2) spi.error("foo") end)) 37 | stmt:execute(3); 38 | $$; 39 | 40 | -- should now be one xid in xatst, and 2 rows 41 | select count(*), count(distinct age(xmin)) from xatst; 42 | 43 | do language pllua $$ 44 | local function f() for r in spi.rows([[ select * from xatst order by a ]]) do print(r) end end 45 | print(pcall(f)) 46 | $$; 47 | 48 | do language pllua $$ 49 | local function f() for r in spi.rows([[ select * from xatst order by a ]]) do print(r) end end 50 | local function f2() error("foo") end 51 | print(pcall(f2)) 52 | f() 53 | $$; 54 | 55 | do language pllua $$ 56 | local function f(e) print("error",e) for r in spi.rows([[ select * from xatst order by a ]]) do print(r) end end 57 | local function f2() error("foo") end 58 | print(xpcall(f2,f)) 59 | $$; 60 | 61 | truncate table xatst; 62 | 63 | do language pllua $$ 64 | local stmt = spi.prepare([[ insert into xatst values ($1) ]]); 65 | local function f(e) print("error",e) stmt:execute(3) end 66 | local function f2() stmt:execute(2) error("foo") end 67 | stmt:execute(1) 68 | print(xpcall(f2,f)) 69 | $$; 70 | 71 | -- should now be one xid in xatst, and 2 rows 72 | select count(*), count(distinct age(xmin)) from xatst; 73 | 74 | do language pllua $$ 75 | local function f(e) error("bar") end 76 | local function f2() error("foo") end 77 | print(xpcall(f2,f)) 78 | $$; 79 | 80 | -- tricky error-in-error cases: 81 | -- 82 | -- pg error inside xpcall handler func needs to abort out to the 83 | -- parent of the xpcall, not the xpcall itself. 84 | begin; 85 | -- we get (harmless) warnings with lua53 but not with luajit for this 86 | -- case. suppress them. 87 | set local client_min_messages = error; 88 | do language pllua $$ 89 | local function f(e) spi.error("nested") end 90 | local function f2() error("foo") end 91 | -- don't print the result because it differs with luajit, all that 92 | -- really matters here is that we don't crash and don't reach the 93 | -- last print 94 | pcall(function() 95 | print("entering xpcall"); 96 | print("inner xpcall", xpcall(f2,f)) 97 | print("should not be reached") 98 | end) 99 | $$; 100 | commit; 101 | 102 | do language pllua $$ 103 | local level = 0 104 | local function f(e) level = level + 1 if level==1 then print("in error handler",level,e) spi.error("nested") end end 105 | local function f2() error("foo") end 106 | print("outer pcall", 107 | pcall(function() 108 | print("entering xpcall"); 109 | print("inner xpcall", xpcall(f2,f)) 110 | print("should not be reached") 111 | end)) 112 | $$; 113 | 114 | 115 | do language pllua $$ 116 | print(lpcall(function() error("caught") end)) 117 | $$; 118 | 119 | do language pllua $$ 120 | print(lpcall(function() spi.error("not caught") end)) 121 | $$; 122 | 123 | -- make sure PG errors in coroutines are propagated (but not lua errors) 124 | 125 | do language pllua $$ 126 | local c = coroutine.create(function() coroutine.yield() error("caught") end) 127 | print(coroutine.resume(c)) 128 | print(coroutine.resume(c)) 129 | $$; 130 | 131 | do language pllua $$ 132 | local c = coroutine.create(function() coroutine.yield() spi.error("not caught") end) 133 | print(coroutine.resume(c)) 134 | print(coroutine.resume(c)) 135 | $$; 136 | 137 | -- error object funcs 138 | 139 | do language pllua $$ 140 | local err = require 'pllua.error' 141 | local r,e = pcall(function() spi.error("22003", "foo", "bar", "baz") end) 142 | print(err.type(e), err.category(e), err.errcode(e)) 143 | print(e.severity, e.category, e.errcode, e.sqlstate, e.message, e.detail, e.hint) 144 | local r,e = pcall(function() error("foo") end) 145 | print(err.type(e), err.category(e), err.errcode(e), e) 146 | $$; 147 | 148 | --end 149 | -------------------------------------------------------------------------------- /sql/triggers.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | \set QUIET 0 5 | 6 | -- Test triggers. 7 | -- 8 | -- Don't use pg10-specific stuff here; that goes in triggers_10.sql 9 | 10 | create table trigtst ( 11 | id integer primary key, 12 | name text, 13 | flag boolean, 14 | qty integer, 15 | weight numeric 16 | ); 17 | 18 | create function misctrig() returns trigger language pllua 19 | as $$ 20 | print(trigger.name,...) 21 | print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) 22 | if trigger.level == "row" then 23 | print(old,new) 24 | end 25 | $$; 26 | 27 | create trigger t1 28 | before insert or update or delete on trigtst 29 | for each statement 30 | execute procedure misctrig('foo','bar'); 31 | create trigger t2 32 | after insert or update or delete on trigtst 33 | for each statement 34 | execute procedure misctrig('foo','bar'); 35 | 36 | insert into trigtst 37 | values (1, 'fred', true, 23, 1.73), 38 | (2, 'jim', false, 11, 3.1), 39 | (3, 'sheila', false, 9, 1.3), 40 | (4, 'dougal', false, 1, 9.3), 41 | (5, 'brian', false, 31, 51.5), 42 | (6, 'ermintrude', true, 91, 52.7), 43 | (7, 'dylan', false, 35, 12.1), 44 | (8, 'florence', false, 23, 5.4), 45 | (9, 'zebedee', false, 199, 7.4); 46 | update trigtst set qty = qty + 1; 47 | delete from trigtst where name = 'sheila'; 48 | 49 | create trigger t3 50 | before insert or update or delete on trigtst 51 | for each row 52 | execute procedure misctrig('wot'); 53 | create trigger t4 54 | after insert or update or delete on trigtst 55 | for each row 56 | execute procedure misctrig('wot'); 57 | 58 | insert into trigtst values (3, 'sheila', false, 9, 1.3); 59 | update trigtst set flag = true where name = 'dylan'; 60 | delete from trigtst where name = 'jim'; 61 | -- check result is as expected 62 | select * from trigtst order by id; 63 | 64 | drop trigger t1 on trigtst; 65 | drop trigger t2 on trigtst; 66 | drop trigger t3 on trigtst; 67 | drop trigger t4 on trigtst; 68 | 69 | -- compatible mode: assign to row fields 70 | create function modtrig1() returns trigger language pllua 71 | as $$ 72 | print(trigger.name,trigger.operation,old,new) 73 | trigger.row.weight = 10 * trigger.row.qty 74 | trigger.row.flag = false 75 | print(trigger.name,trigger.operation,old,new) 76 | $$; 77 | 78 | create trigger t1 79 | before insert or update or delete on trigtst 80 | for each row 81 | execute procedure modtrig1(); 82 | 83 | insert into trigtst values (2, 'jim', true, 11, 3.1); 84 | update trigtst set flag = true where name = 'ermintrude'; 85 | delete from trigtst where name = 'fred'; 86 | select * from trigtst order by id; 87 | 88 | drop trigger t1 on trigtst; 89 | 90 | -- compatible mode: assign to row wholesale 91 | create function modtrig2() returns trigger language pllua 92 | as $$ 93 | print(trigger.name,trigger.op,old,new) 94 | local id,name,flag,qty,weight = new.id, new.name, new.flag, new.qty, new.weight 95 | qty = 2 + qty 96 | weight = weight * 2 97 | flag = not flag 98 | trigger.row = { id = id, name = name, flag = flag, qty = qty, weight = weight } 99 | $$; 100 | 101 | create trigger t1 102 | before insert or update on trigtst 103 | for each row 104 | execute procedure modtrig2(); 105 | 106 | insert into trigtst values (1, 'fred', true, 23, 1.73); 107 | update trigtst set flag = true where name = 'zebedee'; 108 | delete from trigtst where name = 'jim'; 109 | select * from trigtst order by id; 110 | 111 | drop trigger t1 on trigtst; 112 | 113 | -- compatible mode: assign to row wholesale with new datum row 114 | create function modtrig3() returns trigger language pllua 115 | as $$ 116 | print(trigger.name,trigger.operation,old,new) 117 | local id,name,flag,qty,weight = new.id, new.name, new.flag, new.qty, new.weight 118 | qty = 2 + qty 119 | weight = weight * 2 120 | flag = not flag 121 | trigger.row = pgtype(new)(id,name,flag,qty,weight) 122 | $$; 123 | 124 | create trigger t1 125 | before insert or update on trigtst 126 | for each row 127 | execute procedure modtrig3(); 128 | 129 | insert into trigtst values (2, 'jim', false, 11, 3.1); 130 | update trigtst set flag = true where name = 'zebedee'; 131 | delete from trigtst where name = 'fred'; 132 | select * from trigtst order by id; 133 | 134 | drop trigger t1 on trigtst; 135 | 136 | -- return value mode 137 | create function modtrig4() returns trigger language pllua 138 | as $$ 139 | print(trigger.name,trigger.operation,old,new) 140 | local id,name,flag,qty,weight = new.id, new.name, new.flag, new.qty, new.weight 141 | qty = 2 + qty 142 | weight = weight * 2 143 | flag = not flag 144 | return { id = id, name = name, flag = flag, qty = qty, weight = weight } 145 | $$; 146 | 147 | create trigger t1 148 | before insert or update on trigtst 149 | for each row 150 | execute procedure modtrig4(); 151 | 152 | insert into trigtst values (1, 'fred', true, 23, 1.73); 153 | update trigtst set flag = false where name = 'dylan'; 154 | delete from trigtst where name = 'jim'; 155 | select * from trigtst order by id; 156 | 157 | drop trigger t1 on trigtst; 158 | 159 | -- return value mode 160 | create function modtrig5() returns trigger language pllua 161 | as $$ 162 | print(trigger.name,trigger.operation,old,new) 163 | local id,name,flag,qty,weight = new.id, new.name, new.flag, new.qty, new.weight 164 | qty = 2 + qty 165 | weight = weight * 2 166 | flag = not flag 167 | return pgtype(new)(id,name,flag,qty,weight) 168 | $$; 169 | 170 | create trigger t1 171 | before insert or update on trigtst 172 | for each row 173 | execute procedure modtrig5(); 174 | 175 | insert into trigtst values (2, 'jim', false, 11, 3.1); 176 | update trigtst set flag = false where name = 'dougal'; 177 | delete from trigtst where name = 'fred'; 178 | select * from trigtst order by id; 179 | 180 | drop trigger t1 on trigtst; 181 | 182 | -- throw error from trigger 183 | create function modtrig6() returns trigger language pllua 184 | as $$ 185 | print(trigger.name,trigger.operation,old,new) 186 | if new.flag ~= old.flag then error("no changing flags") end 187 | $$; 188 | 189 | create trigger t1 190 | before update on trigtst 191 | for each row 192 | execute procedure modtrig6(); 193 | 194 | update trigtst set flag = false where name = 'dougal'; 195 | select * from trigtst order by id; 196 | 197 | drop trigger t1 on trigtst; 198 | 199 | -- throw error from trigger 200 | create function modtrig7() returns trigger language pllua 201 | as $$ 202 | print(trigger.name,trigger.operation,old,new) 203 | if new.flag ~= old.flag then error("no changing flags") end 204 | $$; 205 | 206 | create trigger t1 207 | before update on trigtst 208 | for each row 209 | execute procedure modtrig7(); 210 | 211 | update trigtst set flag = true where name = 'florence'; 212 | select * from trigtst order by id; 213 | 214 | drop trigger t1 on trigtst; 215 | 216 | -- suppress action 1 217 | create function modtrig8() returns trigger language pllua 218 | as $$ 219 | print(trigger.name,trigger.operation,old,new) 220 | if new.flag ~= old.flag then return nil end 221 | $$; 222 | 223 | create trigger t1 224 | before update on trigtst 225 | for each row 226 | execute procedure modtrig8(); 227 | 228 | update trigtst set flag = true where name = 'florence'; 229 | select * from trigtst order by id; 230 | 231 | drop trigger t1 on trigtst; 232 | 233 | -- suppress action 2 234 | create function modtrig9() returns trigger language pllua 235 | as $$ 236 | print(trigger.name,trigger.operation,old,new) 237 | if new.flag ~= old.flag then trigger.row = nil end 238 | $$; 239 | 240 | create trigger t1 241 | before update on trigtst 242 | for each row 243 | execute procedure modtrig9(); 244 | 245 | update trigtst set flag = true where name = 'florence'; 246 | select * from trigtst order by id; 247 | 248 | drop trigger t1 on trigtst; 249 | 250 | -- table with one column exercises several edge cases: 251 | 252 | create table trigtst1col (col integer); 253 | create function modtrig10() returns trigger language pllua 254 | as $$ 255 | print(trigger.name,trigger.operation,old,new) 256 | new.col = 123 257 | $$; 258 | create trigger t1 259 | before insert on trigtst1col 260 | for each row 261 | execute procedure modtrig10(); 262 | insert into trigtst1col values (1); 263 | insert into trigtst1col values (2); 264 | select * from trigtst1col; 265 | 266 | create type t2col as (a integer, b text); 267 | create table trigtst1col2 (col t2col); 268 | create function modtrig11() returns trigger language pllua 269 | as $$ 270 | print(trigger.name,trigger.operation,old,new) 271 | new.col.a = 123 272 | $$; 273 | create trigger t1 274 | before insert on trigtst1col2 275 | for each row 276 | execute procedure modtrig11(); 277 | insert into trigtst1col2 values (row(1,'foo')::t2col); 278 | select * from trigtst1col2; 279 | 280 | -- exercise dropped columns: 281 | 282 | create table trigtst1dcol (col text, dcol text); 283 | alter table trigtst1dcol drop column dcol; 284 | create trigger t1 285 | after insert or update or delete on trigtst1dcol 286 | for each row 287 | execute procedure misctrig('blah'); 288 | insert into trigtst1dcol values ('foo'); 289 | update trigtst1dcol set col = 'bar'; 290 | delete from trigtst1dcol; 291 | 292 | -- 293 | -------------------------------------------------------------------------------- /sql/triggers_10.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | \set QUIET 0 5 | 6 | -- Test pg10+ trigger functionality. 7 | 8 | create table trigtst2 ( 9 | id integer primary key, 10 | name text, 11 | flag boolean, 12 | qty integer, 13 | weight numeric 14 | ); 15 | 16 | create function ttrig1() returns trigger language pllua 17 | as $$ 18 | print(trigger.name,...) 19 | print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) 20 | for r in spi.rows([[ select * from newtab ]]) do print(r) end 21 | $$; 22 | 23 | create function ttrig2() returns trigger language pllua 24 | as $$ 25 | print(trigger.name,...) 26 | print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) 27 | for r in spi.rows([[ select 'old', * from oldtab union all select 'new', * from newtab ]]) do print(r) end 28 | $$; 29 | 30 | create function ttrig3() returns trigger language pllua 31 | as $$ 32 | print(trigger.name,...) 33 | print(trigger.when, trigger.level, trigger.operation, trigger.relation.name) 34 | for r in spi.rows([[ select * from oldtab ]]) do print(r) end 35 | $$; 36 | 37 | create trigger t1 38 | after insert on trigtst2 39 | referencing new table as newtab 40 | for each statement 41 | execute procedure ttrig1('t1 insert'); 42 | 43 | create trigger t2 44 | after update on trigtst2 45 | referencing old table as oldtab 46 | new table as newtab 47 | for each statement 48 | execute procedure ttrig2('t2 update'); 49 | 50 | create trigger t3 51 | after delete on trigtst2 52 | referencing old table as oldtab 53 | for each statement 54 | execute procedure ttrig3('t3 delete'); 55 | 56 | insert into trigtst2 57 | values (1, 'fred', true, 23, 1.73), 58 | (2, 'jim', false, 11, 3.1), 59 | (3, 'sheila', false, 9, 1.3), 60 | (4, 'dougal', false, 1, 9.3), 61 | (5, 'brian', false, 31, 51.5), 62 | (6, 'ermintrude', true, 91, 52.7), 63 | (7, 'dylan', false, 35, 12.1), 64 | (8, 'florence', false, 23, 5.4), 65 | (9, 'zebedee', false, 199, 7.4); 66 | update trigtst2 set qty = qty + 1; 67 | delete from trigtst2 where name = 'sheila'; 68 | 69 | -- 70 | -------------------------------------------------------------------------------- /sql/trusted.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | 5 | set pllua.on_trusted_init=$$ 6 | local e = require 'pllua.elog' 7 | package.preload['testmod1'] = function() e.info("testmod1 loaded") return { testfunc = function() print("testfunc1") end } end; 8 | package.preload['testmod2'] = function() e.info("testmod2 loaded") return { testfunc = function() print("testfunc2") end } end; 9 | trusted.allow('testmod1', nil, nil, nil, false); 10 | trusted.allow('testmod2', nil, nil, nil, true); 11 | $$; 12 | 13 | -- 14 | 15 | do language pllua $$ print("interpreter loaded") $$; 16 | 17 | do language pllua $$ 18 | local m = require 'testmod1' 19 | m.testfunc() 20 | $$; 21 | 22 | do language pllua $$ 23 | local m = require 'testmod2' 24 | m.testfunc() 25 | $$; 26 | 27 | --end 28 | -------------------------------------------------------------------------------- /sql/types.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | \set VERBOSITY terse 4 | 5 | -- 6 | 7 | create type ctype3 as (fred integer, jim numeric); 8 | do $$ 9 | begin 10 | if current_setting('server_version_num')::integer >= 110000 then 11 | execute 'create domain dtype as ctype3 check((VALUE).jim is not null)'; 12 | else 13 | execute 'create type dtype as (fred integer, jim numeric)'; 14 | end if; 15 | end; 16 | $$; 17 | create type ctype2 as (thingy text, wotsit integer); 18 | create type ctype as (foo text, bar ctype2, baz dtype); 19 | create table tdata ( 20 | intcol integer, 21 | textcol text, 22 | charcol char(32), 23 | varcharcol varchar(32), 24 | compcol ctype, 25 | dcompcol dtype 26 | ); 27 | 28 | insert into tdata 29 | values (1, 'row 1', 'padded with blanks', 'not padded', ('x',('y',1111),(111,11.1)), (11,1.1)), 30 | (2, 'row 2', 'padded with blanks', 'not padded', ('x',('y',2222),(222,22.2)), (22,2.2)), 31 | (3, 'row 3', 'padded with blanks', 'not padded', ('x',('y',3333),(333,33.3)), (33,3.3)); 32 | 33 | create function tf1() returns setof tdata language pllua as $f$ 34 | for i = 1,4 do 35 | coroutine.yield({ intcol = i, 36 | textcol = "row "..i, 37 | charcol = "padded with blanks", 38 | varcharcol = "not padded", 39 | compcol = { foo = "x", 40 | bar = { thingy = "y", wotsit = i*1111 }, 41 | baz = { fred = i*111, jim = i*11.1 } 42 | }, 43 | dcompcol = { fred = i*11, jim = i*1.1 } 44 | }) 45 | end 46 | $f$; 47 | 48 | select * from tf1(); 49 | 50 | -- 51 | -- various checks of type handling 52 | -- 53 | 54 | do language pllua $$ print(pgtype(nil,'ctype3')(1,2)) $$; 55 | do language pllua $$ print(pgtype(nil,'ctype3')({1,2})) $$; 56 | do language pllua $$ print(pgtype(nil,'ctype3')(true,true)) $$; 57 | do language pllua $$ print(pgtype(nil,'ctype3')("1","2")) $$; 58 | do language pllua $$ print(pgtype(nil,'ctype3')({fred=1,jim=2})) $$; 59 | do language pllua $$ print(pgtype(nil,'ctype3')({fred=1,jim={}})) $$; 60 | do language pllua $$ print(pgtype(nil,'ctype3')({fred=1,jim=nil})) $$; 61 | --do language pllua $$ print(pgtype(nil,'dtype')({fred=1,jim=nil})) $$; 62 | 63 | create function tf2() returns setof tdata language pllua as $f$ 64 | local t = spi.execute("select * from tdata") 65 | for i,v in ipairs(t) do coroutine.yield(v) end 66 | $f$; 67 | 68 | select * from tf2(); 69 | 70 | do language pllua $$ print(pgtype.ctype3()) $$; 71 | 72 | -- ensure detoasting of nested composites works right 73 | 74 | do language pllua $f$ 75 | for r in spi.rows("select * from tdata") do 76 | print(r.intcol, r.compcol.foo, r.compcol.bar.wotsit, r.dcompcol.jim) 77 | end 78 | $f$; 79 | 80 | do language pllua $$ 81 | a = pgtype.array.integer({{{1,2}},{{3,4}},{{5,6}}},3,1,2) 82 | print(a) 83 | print(#a,#(a[1]),#(a[1][1])) 84 | print(a[3][1][2],a[1][1][1]) 85 | $$; 86 | do language pllua $$ print(pgtype.int4range(123,456)) $$; 87 | do language pllua $$ print(pgtype.int4range()) $$; 88 | do language pllua $$ print(pgtype.int4range(123,456,'(]')) $$; 89 | do language pllua $$ print(pgtype.int4range(nil,456,'(]')) $$; 90 | do language pllua $$ print(pgtype.int4range(nil,nil)) $$; 91 | do language pllua $$ print(pgtype.int4range(123,nil)) $$; 92 | do language pllua $$ print(pgtype.int4range('[12,56]')) $$; 93 | 94 | do language pllua $$ 95 | local r1,r2,r3 = pgtype.numrange('[12,56]'), 96 | pgtype.numrange('empty'), 97 | pgtype.numrange('(12,)') 98 | print(r1.lower,r1.upper,r1.lower_inc,r1.upper_inc,r1.lower_inf,r1.upper_inf,r1.isempty) 99 | print(r2.lower,r2.upper,r2.lower_inc,r2.upper_inc,r2.lower_inf,r2.upper_inf,r2.isempty) 100 | print(r3.lower,r3.upper,r3.lower_inc,r3.upper_inc,r3.lower_inf,r3.upper_inf,r3.isempty) 101 | $$; 102 | 103 | create type myenum as enum ('TRUE', 'FALSE', 'FILE_NOT_FOUND'); 104 | 105 | create function pg_temp.f1(a myenum) returns text language pllua as $$ print(a,type(a)) return a $$; 106 | select pg_temp.f1(x) from unnest(enum_range(null::myenum)) x; 107 | 108 | create function pg_temp.f2() returns myenum language pllua as $$ return 'FILE_NOT_FOUND' $$; 109 | select pg_temp.f2(); 110 | 111 | -- domains 112 | 113 | create domain mydom1 as varchar(3); 114 | create domain mydom2 as varchar(3) check (value in ('foo','bar','baz')); 115 | create domain mydom3 as varchar(3) not null; 116 | create domain mydom4 as varchar(3) not null check (value in ('foo','bar','baz')); 117 | 118 | create function pg_temp.f3(a mydom1) returns void language pllua as $$ 119 | print(pgtype(nil,1):name(), type(a), #a) 120 | $$; 121 | select pg_temp.f3('foo') union all select pg_temp.f3('bar '); 122 | 123 | create function pg_temp.f4d1(a text) returns mydom1 language pllua as $$ 124 | return a 125 | $$; 126 | select pg_temp.f4d1('foo'); 127 | select pg_temp.f4d1('bar '); 128 | select pg_temp.f4d1('toolong'); 129 | select pg_temp.f4d1(null); 130 | 131 | create function pg_temp.f4d2(a text) returns mydom2 language pllua as $$ 132 | return a 133 | $$; 134 | select pg_temp.f4d2('bar '); 135 | select pg_temp.f4d2('bad'); 136 | select pg_temp.f4d2('toolong'); 137 | select pg_temp.f4d2(null); 138 | 139 | create function pg_temp.f4d3(a text) returns mydom3 language pllua as $$ 140 | return a 141 | $$; 142 | select pg_temp.f4d3('bar '); 143 | select pg_temp.f4d3('toolong'); 144 | select pg_temp.f4d3(null); 145 | 146 | create function pg_temp.f4d4(a text) returns mydom4 language pllua as $$ 147 | return a 148 | $$; 149 | select pg_temp.f4d4('bar '); 150 | select pg_temp.f4d4('bad'); 151 | select pg_temp.f4d4('toolong'); 152 | select pg_temp.f4d4(null); 153 | 154 | -- array coercions 155 | 156 | -- relabeltype path 157 | do language pllua $$ 158 | local a = pgtype.array.varchar("foo","bar") 159 | local b = pgtype.array.text(a) 160 | print(b) 161 | $$; 162 | 163 | -- cast function path 164 | do language pllua $$ 165 | local a = pgtype.array.boolean(false,true) 166 | local b = pgtype.array.text(a) 167 | print(b) 168 | $$; 169 | 170 | -- IO path 171 | do language pllua $$ 172 | local a = pgtype.array.integer(10,20) 173 | local b = pgtype.array.text(a) 174 | print(b) 175 | $$; 176 | 177 | -- array typmod coercions 178 | 179 | create temp table atc (a varchar(10)[], b char(10)[]); 180 | do language pllua $$ 181 | local a = pgtype.array.varchar('foo','bar','value_too_long_for_type') 182 | local b = pgtype.atc(a,nil) 183 | $$; 184 | do language pllua $$ 185 | local a = pgtype.array.bpchar('foo','bar','value ') 186 | local b = pgtype.atc(nil,a) 187 | print(b) 188 | $$; 189 | 190 | -- composite type construction edge cases 191 | 192 | do language pllua $$ 193 | print(pgtype.ctype3()) 194 | print(pgtype.ctype3(nil)) 195 | $$; 196 | 197 | do language pllua $$ 198 | print(pgtype.ctype3(1)) -- error 199 | $$; 200 | 201 | do language pllua $$ 202 | print(pgtype.ctype3(1,2)) 203 | $$; 204 | 205 | --end 206 | -------------------------------------------------------------------------------- /src/compat.lua: -------------------------------------------------------------------------------- 1 | -- compat.lua 2 | 3 | --[[ 4 | This is an attempt to emulate the old pllua as closely as possible to 5 | try and make porting easy. 6 | 7 | Configure pllua.on_common_init='require "pllua.compat"' to enable it. 8 | ]] 9 | 10 | do 11 | local pgtype = require 'pllua.pgtype' 12 | 13 | function _G.fromstring(t,s) 14 | return pgtype[t]:fromstring(s) 15 | end 16 | end 17 | 18 | do 19 | local meta = ... 20 | local shared = setmetatable({}, { __index = _G }) 21 | _G.shared = shared 22 | local rawget, rawset = rawget, rawset 23 | local function shared_assign(t,k,v) 24 | rawset(rawget(shared,k) and shared or t, k, v) 25 | end 26 | function _G.setshared(k,v) 27 | if meta.__index ~= shared then 28 | meta.__index = shared 29 | meta.__newindex = shared_assign 30 | end 31 | shared[k] = v 32 | end 33 | end 34 | 35 | do 36 | local pcall = pcall 37 | local error = error 38 | local err = require 'pllua.error' 39 | 40 | _G.subtransaction = err.spcall 41 | 42 | function _G.pgfunc(...) 43 | error('pgfunc is not implemented') 44 | end 45 | end 46 | 47 | do 48 | local e = require 'pllua.elog' 49 | _G.info, _G.log, _G.notice, _G.warning = e.info, e.log, e.notice, e.warning 50 | end 51 | 52 | do 53 | local spi = require 'pllua.spi' 54 | local unpack = table.unpack or unpack 55 | local type = type 56 | local setmetatable = setmetatable 57 | 58 | -- map new result convention to old one 59 | local function fixresult(r) 60 | return type(r)=='table' and #r > 0 and r or nil 61 | end 62 | 63 | -- wrap the SPI cursor object 64 | local curs = { 65 | fetch = function(self,n) 66 | return fixresult(self.curs:fetch(n)) 67 | end; 68 | move = function(self,n) 69 | return fixresult(self.curs:move(n)) 70 | end; 71 | posfetch = function(self,n,rel) 72 | return fixresult(self.curs:fetch(n, rel and 'relative' or 'absolute')) 73 | end; 74 | posmove = function(self,n,rel) 75 | return fixresult(self.curs:move(n, rel and 'relative' or 'absolute')) 76 | end; 77 | close = function(self) 78 | return self.curs:close() 79 | end; 80 | } 81 | local curs_meta = { __index = curs } 82 | local function newcurs(c) 83 | return setmetatable({ curs = c }, curs_meta) 84 | end 85 | 86 | -- wrap the SPI statement object 87 | local plan = { 88 | execute = function(self,args,ronly,count) 89 | local s = self.stmt 90 | local nargs = s:numargs() 91 | return fixresult(s:execute_count(count, unpack(args, 1, nargs))) 92 | end; 93 | getcursor = function(self,args,ronly,name) 94 | local s = self.stmt 95 | local nargs = s:numargs() 96 | local c = spi.newcursor(type(name)=='string' and name or nil) 97 | return newcurs(c:open(s, unpack(args, 1, nargs))) 98 | end; 99 | rows = function(self,args) 100 | local s = self.stmt 101 | local nargs = s:numargs() 102 | return s:rows(unpack(args, 1, nargs)) 103 | end; 104 | issaved = function() return true end; 105 | save = function(self) return self end; 106 | } 107 | local plan_meta = { __index = plan } 108 | local function newplan(s) 109 | return setmetatable({ stmt = s }, plan_meta) 110 | end 111 | 112 | local server = { 113 | execute = function(cmd,ronly,count) 114 | return fixresult(spi.execute_count(cmd,count)) 115 | end; 116 | 117 | rows = spi.rows; 118 | 119 | prepare = function(cmd,argtypes) 120 | return newplan(spi.prepare(cmd,argtypes)) 121 | end; 122 | 123 | find = function(name) 124 | return newcurs(spi.findcursor(name)) 125 | end; 126 | } 127 | 128 | _G.server = server 129 | end 130 | 131 | return true 132 | -------------------------------------------------------------------------------- /src/exports.x: -------------------------------------------------------------------------------- 1 | { 2 | global: 3 | Pg_magic_func; 4 | _PG_init; 5 | pllua_validator; pg_finfo_pllua_validator; 6 | pllua_call_handler; pg_finfo_pllua_call_handler; 7 | pllua_inline_handler; pg_finfo_pllua_inline_handler; 8 | plluau_validator; pg_finfo_plluau_validator; 9 | plluau_call_handler; pg_finfo_plluau_call_handler; 10 | plluau_inline_handler; pg_finfo_plluau_inline_handler; 11 | pllua_rethrow_from_pg; 12 | pllua_pcall_nothrow; 13 | pllua_cpcall; 14 | pllua_pcall; 15 | pllua_trampoline; 16 | 17 | local: *; 18 | }; 19 | -------------------------------------------------------------------------------- /src/globals.c: -------------------------------------------------------------------------------- 1 | /* globals.c */ 2 | 3 | #include "pllua.h" 4 | 5 | bool pllua_ending = false; 6 | 7 | pllua_context_type pllua_context = PLLUA_CONTEXT_PG; 8 | 9 | /* 10 | * Addresses used as lua registry or table keys 11 | * 12 | * Note the key is the address, not the string; the string is only for 13 | * diagnostic purposes. 14 | */ 15 | 16 | char PLLUA_FUNCS[] = "funcs"; 17 | char PLLUA_ACTIVATIONS[] = "activations"; 18 | char PLLUA_TYPES[] = "types"; 19 | char PLLUA_RECORDS[] = "records"; 20 | char PLLUA_PORTALS[] = "cursors"; 21 | char PLLUA_TRUSTED[] = "trusted"; 22 | char PLLUA_USERID[] = "userid"; 23 | char PLLUA_LANG_OID[] = "language oid"; 24 | char PLLUA_FUNCTION_OBJECT[] = "function object"; 25 | char PLLUA_ERROR_OBJECT[] = "error object"; 26 | char PLLUA_IDXLIST_OBJECT[] = "idxlist object"; 27 | char PLLUA_ACTIVATION_OBJECT[] = "activation object"; 28 | char PLLUA_MCONTEXT_OBJECT[] = "memory context object"; 29 | char PLLUA_TYPEINFO_OBJECT[] = "typeinfo object"; 30 | char PLLUA_TYPEINFO_PACKAGE_OBJECT[] = "typeinfo package object"; 31 | char PLLUA_TYPEINFO_PACKAGE_ARRAY_OBJECT[] = "typeinfo package array object"; 32 | char PLLUA_TUPCONV_OBJECT[] = "tupconv object"; 33 | char PLLUA_TRIGGER_OBJECT[] = "trigger object"; 34 | char PLLUA_EVENT_TRIGGER_OBJECT[] = "event trigger object"; 35 | char PLLUA_SPI_STMT_OBJECT[] = "SPI statement object"; 36 | char PLLUA_SPI_CURSOR_OBJECT[] = "SPI cursor object"; 37 | char PLLUA_LAST_ERROR[] = "last error"; 38 | char PLLUA_RECURSIVE_ERROR[] = "recursive error"; 39 | char PLLUA_FUNCTION_MEMBER[] = "function element"; 40 | char PLLUA_MCONTEXT_MEMBER[] = "memory context element"; 41 | char PLLUA_THREAD_MEMBER[] = "thread element"; 42 | char PLLUA_TRUSTED_SANDBOX[] = "sandbox"; 43 | char PLLUA_TRUSTED_SANDBOX_LOADED[] = "sandbox loaded modules"; 44 | char PLLUA_TRUSTED_SANDBOX_ALLOW[] = "sandbox allowed modules"; 45 | char PLLUA_PGFUNC_TABLE_OBJECT[] = "pgfunc table object"; 46 | char PLLUA_TYPECONV_REGISTRY[] = "typeconv registry table"; 47 | char PLLUA_ERRCODES_TABLE[] = "errcodes table"; 48 | char PLLUA_PRINT_SEVERITY[] = "severity level for print() output"; 49 | char PLLUA_GLOBAL_META[] = "global table proxy metatable"; 50 | char PLLUA_SANDBOX_META[] = "sandbox table proxy metatable"; 51 | 52 | #if LUA_VERSION_NUM == 501 53 | int pllua_getsubtable(lua_State *L, int i, const char *name) 54 | { 55 | int abs_i = lua_absindex(L, i); 56 | luaL_checkstack(L, 3, "not enough stack slots"); 57 | lua_pushstring(L, name); 58 | lua_gettable(L, abs_i); 59 | if (lua_istable(L, -1)) 60 | return 1; 61 | lua_pop(L, 1); 62 | lua_newtable(L); 63 | lua_pushstring(L, name); 64 | lua_pushvalue(L, -2); 65 | lua_settable(L, abs_i); 66 | return 0; 67 | } 68 | #if LUAJIT_VERSION_NUM < 20100 69 | /* 70 | * Lua compat funcs 71 | */ 72 | void pllua_setfuncs(lua_State *L, const luaL_Reg *l, int nup) 73 | { 74 | luaL_checkstack(L, nup+1, "too many upvalues"); 75 | for (; l->name != NULL; l++) { /* fill the table with given functions */ 76 | int i; 77 | lua_pushstring(L, l->name); 78 | for (i = 0; i < nup; i++) /* copy upvalues to the top */ 79 | lua_pushvalue(L, -(nup + 1)); 80 | lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ 81 | lua_settable(L, -(nup + 3)); /* table must be below the upvalues, the name and the closure */ 82 | } 83 | lua_pop(L, nup); /* remove upvalues */ 84 | } 85 | #endif 86 | 87 | void pllua_requiref(lua_State *L, const char *modname, 88 | lua_CFunction openf, int glb) 89 | { 90 | luaL_checkstack(L, 3, "not enough stack slots available"); 91 | luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); 92 | if (lua_getfield(L, -1, modname) == LUA_TNIL) { 93 | lua_pop(L, 1); 94 | lua_pushcfunction(L, openf); 95 | lua_pushstring(L, modname); 96 | lua_call(L, 1, 1); 97 | lua_pushvalue(L, -1); 98 | lua_setfield(L, -3, modname); 99 | } 100 | if (glb) { 101 | lua_pushvalue(L, -1); 102 | lua_setglobal(L, modname); 103 | } 104 | lua_replace(L, -2); 105 | } 106 | #endif 107 | -------------------------------------------------------------------------------- /src/paths.c: -------------------------------------------------------------------------------- 1 | /* paths.c */ 2 | 3 | #include "pllua.h" 4 | 5 | #include "miscadmin.h" 6 | 7 | typedef void (pathfunc_type)(const char *, char *); 8 | 9 | static int 10 | pllua_get_path(lua_State *L) 11 | { 12 | pathfunc_type *func = (pathfunc_type *) lua_touserdata(L, lua_upvalueindex(1)); 13 | char path[MAXPGPATH]; 14 | 15 | path[0] = '\0'; 16 | (*func)(my_exec_path, path); 17 | 18 | if (path[0]) 19 | lua_pushstring(L, path); 20 | else 21 | lua_pushnil(L); 22 | 23 | return 1; 24 | } 25 | 26 | static void 27 | get_bin_path(const char *exec_path, char *retpath) 28 | { 29 | char *lastsep; 30 | strlcpy(retpath, exec_path, MAXPGPATH); 31 | lastsep = strrchr(retpath, '/'); 32 | if (lastsep) 33 | *lastsep = '\0'; 34 | else 35 | *retpath = '\0'; 36 | } 37 | 38 | static struct { 39 | const char *name; 40 | pathfunc_type *func; 41 | } path_funcs[] = { 42 | { "bin", get_bin_path }, 43 | { "doc", get_doc_path }, 44 | { "etc", get_etc_path }, 45 | { "html", get_html_path }, 46 | { "include", get_include_path }, 47 | { "includeserver", get_includeserver_path }, 48 | { "lib", get_lib_path }, 49 | { "libdir", get_pkglib_path }, 50 | { "locale", get_locale_path }, 51 | { "man", get_man_path }, 52 | { "pkginclude", get_pkginclude_path }, 53 | { "pkglib", get_pkglib_path }, 54 | { "share", get_share_path }, 55 | { NULL, NULL } 56 | }; 57 | 58 | int pllua_open_paths(lua_State *L) 59 | { 60 | int i; 61 | 62 | lua_settop(L, 0); 63 | 64 | lua_newtable(L); 65 | 66 | for (i = 0; path_funcs[i].name; ++i) 67 | { 68 | lua_pushlightuserdata(L, path_funcs[i].func); 69 | lua_pushcclosure(L, pllua_get_path, 1); 70 | lua_setfield(L, 1, path_funcs[i].name); 71 | } 72 | 73 | return 1; 74 | } 75 | -------------------------------------------------------------------------------- /src/pllua.c: -------------------------------------------------------------------------------- 1 | /* 2 | * pllua.c: PL/Lua NG call handler 3 | * By Andrew "RhodiumToad" Gierth, rhodiumtoad at postgresql.org 4 | * Based in some part on pllua by Luis Carvalho and others 5 | * License: MIT license or PostgreSQL licence 6 | */ 7 | 8 | #include "pllua.h" 9 | 10 | #include "commands/event_trigger.h" 11 | #include "commands/trigger.h" 12 | 13 | PG_MODULE_MAGIC; 14 | 15 | /* 16 | * Exposed interface 17 | * 18 | * see also _PG_init in init.c 19 | */ 20 | PGDLLEXPORT Datum pllua_validator(PG_FUNCTION_ARGS); 21 | PGDLLEXPORT Datum pllua_call_handler(PG_FUNCTION_ARGS); 22 | PGDLLEXPORT Datum pllua_inline_handler(PG_FUNCTION_ARGS); 23 | PGDLLEXPORT Datum plluau_validator(PG_FUNCTION_ARGS); 24 | PGDLLEXPORT Datum plluau_call_handler(PG_FUNCTION_ARGS); 25 | PGDLLEXPORT Datum plluau_inline_handler(PG_FUNCTION_ARGS); 26 | 27 | static Datum pllua_common_call(FunctionCallInfo fcinfo, bool trusted); 28 | static Datum pllua_common_inline(FunctionCallInfo fcinfo, bool trusted); 29 | static Datum pllua_common_validator(FunctionCallInfo fcinfo, bool trusted); 30 | 31 | 32 | /* Trusted entry points */ 33 | 34 | PG_FUNCTION_INFO_V1(pllua_validator); 35 | Datum pllua_validator(PG_FUNCTION_ARGS) 36 | { 37 | return pllua_common_validator(fcinfo, true); 38 | } 39 | 40 | PG_FUNCTION_INFO_V1(pllua_call_handler); 41 | Datum pllua_call_handler(PG_FUNCTION_ARGS) 42 | { 43 | return pllua_common_call(fcinfo, true); 44 | } 45 | 46 | PG_FUNCTION_INFO_V1(pllua_inline_handler); 47 | Datum pllua_inline_handler(PG_FUNCTION_ARGS) 48 | { 49 | return pllua_common_inline(fcinfo, true); 50 | } 51 | 52 | /* Untrusted entry points */ 53 | 54 | PG_FUNCTION_INFO_V1(plluau_validator); 55 | Datum plluau_validator(PG_FUNCTION_ARGS) 56 | { 57 | return pllua_common_validator(fcinfo, false); 58 | } 59 | 60 | PG_FUNCTION_INFO_V1(plluau_call_handler); 61 | Datum plluau_call_handler(PG_FUNCTION_ARGS) 62 | { 63 | return pllua_common_call(fcinfo, false); 64 | } 65 | 66 | PG_FUNCTION_INFO_V1(plluau_inline_handler); 67 | Datum plluau_inline_handler(PG_FUNCTION_ARGS) 68 | { 69 | return pllua_common_inline(fcinfo, false); 70 | } 71 | 72 | /* Common implementations */ 73 | 74 | static void pllua_entry_stack_check(void) 75 | { 76 | check_stack_depth(); 77 | } 78 | 79 | Datum pllua_common_call(FunctionCallInfo fcinfo, bool trusted) 80 | { 81 | pllua_interpreter *volatile interp = NULL; 82 | pllua_activation_record act; 83 | pllua_func_activation *funcact = (fcinfo->flinfo) ? fcinfo->flinfo->fn_extra : NULL; 84 | ErrorContextCallback ecxt; 85 | 86 | pllua_entry_stack_check(); 87 | 88 | act.fcinfo = fcinfo; 89 | act.retval = (Datum) 0; 90 | act.atomic = true; 91 | act.trusted = trusted; 92 | act.cblock = NULL; 93 | act.validate_func = InvalidOid; 94 | act.interp = NULL; 95 | act.active_error = LUA_REFNIL; 96 | act.err_text = NULL; 97 | 98 | #if PG_VERSION_NUM >= 110000 99 | if (fcinfo->context && IsA(fcinfo->context, CallContext)) 100 | act.atomic = castNode(CallContext, fcinfo->context)->atomic; 101 | #endif 102 | 103 | pllua_setcontext(NULL, PLLUA_CONTEXT_PG); 104 | 105 | /* 106 | * this catch block exists to save/restore the error context stack and 107 | * allow cleanup of our internal error state when returning to PG proper 108 | */ 109 | PG_TRY(); 110 | { 111 | ecxt.callback = pllua_error_callback; 112 | ecxt.arg = &act; 113 | ecxt.previous = error_context_stack; 114 | error_context_stack = &ecxt; 115 | 116 | if (funcact && funcact->thread) 117 | act.interp = funcact->interp; 118 | else 119 | act.interp = pllua_getstate(trusted, &act); 120 | 121 | interp = act.interp; 122 | 123 | if (funcact && funcact->thread) 124 | { 125 | /* 126 | * We're resuming a value-per-call SRF, so we bypass almost 127 | * everything since we don't want to, for example, compile a new 128 | * version of the function halfway through a result set. We know 129 | * we're in a non-first-row condition if there's an existing thread 130 | * in the function activation. 131 | */ 132 | pllua_initial_protected_call(act.interp, pllua_resume_function, &act); 133 | } 134 | else if (CALLED_AS_TRIGGER(fcinfo)) 135 | pllua_initial_protected_call(act.interp, pllua_call_trigger, &act); 136 | else if (CALLED_AS_EVENT_TRIGGER(fcinfo)) 137 | pllua_initial_protected_call(act.interp, pllua_call_event_trigger, &act); 138 | else 139 | pllua_initial_protected_call(act.interp, pllua_call_function, &act); 140 | } 141 | PG_CATCH(); 142 | { 143 | if (interp) 144 | pllua_error_cleanup(interp, &act); 145 | PG_RE_THROW(); 146 | } 147 | PG_END_TRY(); 148 | 149 | return act.retval; 150 | } 151 | 152 | Datum pllua_common_validator(FunctionCallInfo fcinfo, bool trusted) 153 | { 154 | pllua_interpreter *volatile interp = NULL; 155 | pllua_activation_record act; 156 | Oid funcoid = PG_GETARG_OID(0); 157 | ErrorContextCallback ecxt; 158 | 159 | /* security checks */ 160 | if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) 161 | PG_RETURN_VOID(); 162 | 163 | /* 164 | * This doesn't need a stack check because the validator is careful 165 | * not to execute user-supplied Lua code. 166 | */ 167 | 168 | act.fcinfo = NULL; 169 | act.retval = (Datum) 0; 170 | act.atomic = true; 171 | act.trusted = trusted; 172 | act.cblock = NULL; 173 | act.validate_func = funcoid; 174 | act.interp = NULL; 175 | act.active_error = LUA_REFNIL; 176 | act.err_text = NULL; 177 | 178 | pllua_setcontext(NULL, PLLUA_CONTEXT_PG); 179 | 180 | /* 181 | * this catch block exists to save/restore the error context stack and 182 | * allow cleanup of our internal error state when returning to PG proper 183 | */ 184 | PG_TRY(); 185 | { 186 | ecxt.callback = pllua_error_callback; 187 | ecxt.arg = &act; 188 | ecxt.previous = error_context_stack; 189 | error_context_stack = &ecxt; 190 | 191 | interp = act.interp = pllua_getstate(trusted, &act); 192 | 193 | pllua_initial_protected_call(act.interp, pllua_validate, &act); 194 | } 195 | PG_CATCH(); 196 | { 197 | if (interp) 198 | pllua_error_cleanup(interp, &act); 199 | PG_RE_THROW(); 200 | } 201 | PG_END_TRY(); 202 | 203 | PG_RETURN_VOID(); 204 | } 205 | 206 | Datum pllua_common_inline(FunctionCallInfo fcinfo, bool trusted) 207 | { 208 | pllua_interpreter *volatile interp = NULL; 209 | pllua_activation_record act; 210 | ErrorContextCallback ecxt; 211 | 212 | pllua_entry_stack_check(); 213 | 214 | act.fcinfo = NULL; 215 | act.retval = (Datum) 0; 216 | act.atomic = true; 217 | act.trusted = trusted; 218 | act.cblock = (InlineCodeBlock *) PG_GETARG_POINTER(0); 219 | act.validate_func = InvalidOid; 220 | act.interp = NULL; 221 | act.active_error = LUA_REFNIL; 222 | act.err_text = "inline block entry"; 223 | 224 | #if PG_VERSION_NUM >= 110000 225 | act.atomic = act.cblock->atomic; 226 | #endif 227 | 228 | pllua_setcontext(NULL, PLLUA_CONTEXT_PG); 229 | 230 | /* probably excess paranoia */ 231 | if (act.cblock->langIsTrusted != act.trusted) 232 | elog(ERROR, "trusted state mismatch"); 233 | 234 | /* 235 | * this catch block exists to save/restore the error context stack and 236 | * allow cleanup of our internal error state when returning to PG proper 237 | */ 238 | PG_TRY(); 239 | { 240 | ecxt.callback = pllua_error_callback; 241 | ecxt.arg = &act; 242 | ecxt.previous = error_context_stack; 243 | error_context_stack = &ecxt; 244 | 245 | interp = act.interp = pllua_getstate(trusted, &act); 246 | 247 | pllua_initial_protected_call(act.interp, pllua_call_inline, &act); 248 | } 249 | PG_CATCH(); 250 | { 251 | if (interp) 252 | pllua_error_cleanup(interp, &act); 253 | PG_RE_THROW(); 254 | } 255 | PG_END_TRY(); 256 | 257 | PG_RETURN_VOID(); 258 | } 259 | -------------------------------------------------------------------------------- /src/pllua_luajit.h: -------------------------------------------------------------------------------- 1 | /* pllua_luajit.h */ 2 | 3 | #ifndef PLLUA_LUAJIT_H 4 | #define PLLUA_LUAJIT_H 5 | 6 | #include 7 | 8 | /* 9 | * these should be the largest and smallest integers representable exactly in 10 | * a lua number (note, NOT necessarily a lua_Integer); must be compile-time 11 | * constant. 12 | */ 13 | #define PLLUA_MAX_INT_NUM (INT64CONST(9007199254740991)) 14 | #define PLLUA_MIN_INT_NUM (-INT64CONST(9007199254740991)) 15 | 16 | /* 17 | * these must be the exact bounds of a lua_Integer. 18 | */ 19 | #define LUA_MAXINTEGER PTRDIFF_MAX 20 | #define LUA_MININTEGER PTRDIFF_MIN 21 | 22 | #include "pllua_luaver.h" 23 | 24 | /* 25 | * Parts of the lua 5.1 compatibility cruft here is derived from the 26 | * lua-compat-5.3 project, which is licensed under the same terms as this 27 | * project and carries the following copyright: 28 | * 29 | * Copyright (c) 2015 Kepler Project. 30 | */ 31 | extern const char *luaL_tolstring(lua_State *L, int nd, size_t *len); 32 | 33 | static inline int lua_absindex(lua_State *L, int nd) 34 | { 35 | return (nd < 0 && nd > LUA_REGISTRYINDEX) ? nd + lua_gettop(L) + 1 : nd; 36 | } 37 | 38 | static inline int lua_rawgetp(lua_State *L, int nd, void *p) 39 | { 40 | int tnd = lua_absindex(L, nd); 41 | lua_pushlightuserdata(L, p); 42 | lua_rawget(L, tnd); 43 | return lua_type(L, -1); 44 | } 45 | 46 | static inline void lua_rawsetp(lua_State *L, int nd, void *p) 47 | { 48 | int tnd = lua_absindex(L, nd); 49 | lua_pushlightuserdata(L, p); 50 | lua_insert(L, -2); 51 | lua_rawset(L, tnd); 52 | } 53 | 54 | static inline int lua_geti(lua_State *L, int nd, lua_Integer i) 55 | { 56 | int tnd = lua_absindex(L, nd); 57 | lua_pushinteger(L, i); 58 | lua_gettable(L, tnd); 59 | return lua_type(L, -1); 60 | } 61 | 62 | static inline void lua_seti(lua_State *L, int nd, lua_Integer i) 63 | { 64 | int tnd = lua_absindex(L, nd); 65 | lua_pushinteger(L, i); 66 | lua_insert(L, -2); 67 | lua_settable(L, tnd); 68 | } 69 | 70 | #define lua_rawgeti(L_,nd_,i_) ((lua_rawgeti)(L_,nd_,i_),lua_type(L_, -1)) 71 | 72 | #define lua_rawget(L_,nd_) ((lua_rawget)(L_,nd_),lua_type(L_, -1)) 73 | 74 | #define lua_getfield(L_,nd_,i_) ((lua_getfield)(L_,nd_,i_),lua_type(L_, -1)) 75 | 76 | #define lua_gettable(L_,nd_) ((lua_gettable)(L_,nd_),lua_type(L_, -1)) 77 | 78 | #define luaL_getmetafield(L_,nd_,f_) ((luaL_getmetafield)(L_,nd_,f_) ? lua_type(L_, -1) : LUA_TNIL) 79 | 80 | /* luajit 2.1's version of this one is ok. */ 81 | #if LUAJIT_VERSION_NUM < 20100 82 | static inline lua_Number lua_tonumberx(lua_State *L, int i, int *isnum) 83 | { 84 | lua_Number n = lua_tonumber(L, i); 85 | if (isnum != NULL) { 86 | *isnum = (n != 0 || lua_isnumber(L, i)); 87 | } 88 | return n; 89 | } 90 | #endif 91 | 92 | /* 93 | * but for these we need to kill luajit's version and use ours: we depend on 94 | * isinteger/tointegerx/checkinteger accepting only actually integral values, 95 | * not rounded/truncated floats. 96 | */ 97 | #if LUAJIT_VERSION_NUM >= 20100 98 | #define lua_isinteger pllua_isinteger 99 | #define lua_tointegerx pllua_tointegerx 100 | #endif 101 | 102 | /* no luajit version of these is usable: */ 103 | #define luaL_checkinteger pllua_checkinteger 104 | #define luaL_optinteger pllua_optinteger 105 | 106 | static inline bool lua_isinteger(lua_State *L, int nd) 107 | { 108 | if (lua_type(L, nd) == LUA_TNUMBER) 109 | { 110 | lua_Number n = lua_tonumber(L, nd); 111 | lua_Integer i = lua_tointeger(L, nd); 112 | if (i == n) 113 | return 1; 114 | } 115 | return 0; 116 | } 117 | 118 | static inline lua_Integer lua_tointegerx(lua_State *L, int i, int *isint) 119 | { 120 | lua_Integer n = lua_tointeger(L, i); 121 | if (isint != NULL) { 122 | int isnum = 0; 123 | /* be careful, it might not be a number at all */ 124 | if (n == lua_tonumberx(L, i, &isnum)) 125 | *isint = isnum; 126 | else 127 | *isint = 0; 128 | } 129 | return n; 130 | } 131 | 132 | static inline lua_Integer luaL_checkinteger(lua_State *L, int i) 133 | { 134 | int isint = 0; 135 | lua_Integer res = lua_tointegerx(L, i, &isint); 136 | if (!isint) 137 | luaL_argerror(L, i, "integer"); 138 | return res; 139 | } 140 | 141 | static inline lua_Integer luaL_optinteger(lua_State *L, int i, lua_Integer def) 142 | { 143 | if (lua_isnoneornil(L, i)) 144 | return def; 145 | else 146 | return luaL_checkinteger(L, i); 147 | } 148 | 149 | /* 150 | * Miscellaneous functions 151 | */ 152 | #define lua_getuservalue(L_,nd_) (lua_getfenv(L_,nd_), lua_type(L_,-1)) 153 | #define lua_setuservalue(L_,nd_) lua_setfenv(L_,nd_) 154 | 155 | #define lua_pushglobaltable(L_) lua_pushvalue((L_), LUA_GLOBALSINDEX) 156 | 157 | #if LUAJIT_VERSION_NUM < 20100 158 | #define luaL_setfuncs(L_,f_,u_) pllua_setfuncs(L_,f_,u_) 159 | void pllua_setfuncs(lua_State *L, const luaL_Reg *reg, int nup); 160 | #endif 161 | 162 | #define luaL_getsubtable(L_,i_,n_) pllua_getsubtable(L_,i_,n_) 163 | int pllua_getsubtable(lua_State *L, int i, const char *name); 164 | 165 | #define luaL_requiref(L_,m_,f_,g_) pllua_requiref(L_,m_,f_,g_) 166 | void pllua_requiref(lua_State *L, const char *modname, lua_CFunction openf, int glb); 167 | 168 | #ifndef luaL_newlibtable 169 | #define luaL_newlibtable(L, l) \ 170 | (lua_createtable((L), 0, sizeof((l))/sizeof(*(l))-1)) 171 | #define luaL_newlib(L, l) \ 172 | (luaL_newlibtable((L), (l)), luaL_register((L), NULL, (l))) 173 | #endif 174 | 175 | #define LUA_OK 0 176 | 177 | #if LUAJIT_VERSION_NUM > 0 && !defined(NO_LUAJIT) 178 | #define LUA_TCDATA 10 179 | #endif 180 | 181 | #endif 182 | -------------------------------------------------------------------------------- /src/pllua_luaver.h: -------------------------------------------------------------------------------- 1 | /* pllua_luaver.h */ 2 | 3 | #ifndef PLLUA_LUAVER_H 4 | #define PLLUA_LUAVER_H 5 | 6 | /* 7 | * Fail if the version is exactly 5.4.5, due to incompatible API changes; we 8 | * check this here as well as at runtime because we'll get compile errors. 9 | */ 10 | #ifdef LUA_VERSION_RELEASE_NUM 11 | #if LUA_VERSION_RELEASE_NUM == 50405 12 | #error Unsupported Lua version (5.4.5 not supported due to API break) 13 | #endif 14 | #endif 15 | 16 | #ifndef LUAJIT_VERSION_NUM 17 | #define LUAJIT_VERSION_NUM 0 18 | #endif 19 | 20 | #ifndef PLLUA_MAX_INT_NUM 21 | #define PLLUA_MAX_INT_NUM LUA_MAXINTEGER 22 | #endif 23 | #ifndef PLLUA_MIN_INT_NUM 24 | #define PLLUA_MIN_INT_NUM LUA_MININTEGER 25 | #endif 26 | 27 | /* 28 | * This must be able to push a value not outside PLLUA_*_INT_NUM as whatever 29 | * passes for an integer in Lua. 30 | */ 31 | #if LUA_MAXINTEGER >= PLLUA_MAX_INT_NUM 32 | #define pllua_pushbigint(L_, v_) lua_pushinteger((L_), (lua_Integer)(v_)) 33 | #else 34 | #define pllua_pushbigint(L_, v_) lua_pushnumber((L_), (lua_Number)(v_)) 35 | #endif 36 | 37 | /* 38 | * We can only use 5.1 environments in a compatible way to 5.2+ uservalues if 39 | * we forcibly set an environment on every userdata we create. 40 | */ 41 | #if LUA_VERSION_NUM == 501 42 | #define MANDATORY_USERVALUE 1 43 | #else 44 | #define MANDATORY_USERVALUE 0 45 | #endif 46 | 47 | /* 48 | * pllua_pushcfunction must absolutely not throw error. 49 | * 50 | * In Lua 5.4+ (and also 5.3.5+, but we don't check that) it's safe to just use 51 | * lua_pushcfunction, but in 5.1 (by design) and 5.3.[34] (due to bugs) we must 52 | * instead arrange to store all the function values in the registry, and use a 53 | * rawget to fetch them. 54 | */ 55 | #if LUA_VERSION_NUM > 503 56 | 57 | #define pllua_pushcfunction(L_,f_) lua_pushcfunction(L_,f_) 58 | 59 | #else 60 | 61 | #define pllua_pushcfunction(L_,f_) do { \ 62 | int rc_ PG_USED_FOR_ASSERTS_ONLY; \ 63 | rc_ = lua_rawgetp((L_),LUA_REGISTRYINDEX,(f_)); \ 64 | Assert(rc_==LUA_TFUNCTION); } while(0) 65 | 66 | #endif 67 | 68 | /* 69 | * used to label functions that need registration despite not being 70 | * directly passed to pcall or cpcall; the first arg is unused 71 | */ 72 | #define pllua_register_cfunc(L_, f_) (f_) 73 | 74 | /* 75 | * Function to use to set an environment on a code chunk. 76 | */ 77 | #if LUA_VERSION_NUM == 501 78 | #define pllua_set_environment(L_,i_) lua_setfenv(L, i_) 79 | #else 80 | #define pllua_set_environment(L_,i_) lua_setupvalue(L_, i_, 1) 81 | #endif 82 | 83 | /* 84 | * Handle API differences for lua_resume by emulating the 5.4 API on earlier 85 | * versions. Also fake out the warning system on earlier versions, and provide 86 | * a minimal emulation of that works for normal exits (leaving error 87 | * exits to be cleaned up by the GC, but that can't be helped). 88 | */ 89 | #if LUA_VERSION_NUM < 504 90 | 91 | static inline int 92 | pllua_resume(lua_State *L, lua_State *from, int nargs, int *nret) 93 | { 94 | #if LUA_VERSION_NUM == 501 95 | int rc = (lua_resume)(L, nargs); 96 | #else 97 | int rc = (lua_resume)(L, from, nargs); 98 | #endif 99 | *nret = lua_gettop(L); 100 | return rc; 101 | } 102 | #define lua_resume(L_,f_,a_,r_) (pllua_resume(L_,f_,a_,r_)) 103 | 104 | #define lua_setwarnf(L_, f_, p_) ((void)(f_)) 105 | 106 | #define lua_setcstacklimit(L_, n_) (200) 107 | 108 | #define lua_resetthread(L_) (LUA_OK) 109 | 110 | #define PLLUA_WARNBUF_SIZE 4 111 | 112 | #define lua_toclose(L_, i_) ((void)0) 113 | 114 | static inline void 115 | pllua_closevar(lua_State *L, int idx) 116 | { 117 | if (lua_toboolean(L, idx) 118 | && luaL_callmeta(L, idx, "__close")) 119 | lua_pop(L, 1); 120 | } 121 | 122 | #else 123 | 124 | #define PLLUA_WARNBUF_SIZE 1000 125 | 126 | #define pllua_closevar(L_, i_) ((void)0) 127 | 128 | #endif /* LUA_VERSION_NUM < 504 */ 129 | 130 | #endif 131 | -------------------------------------------------------------------------------- /src/pllua_pgver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * pllua_pgver.h 3 | */ 4 | 5 | #ifndef PLLUA_PGVER_H 6 | #define PLLUA_PGVER_H 7 | 8 | /* PG version cruft */ 9 | 10 | /* 11 | * It sucks to depend on point release version, but some values we want to use 12 | * in #if directives are not compile-time constant in older point releases. 13 | * So for unfixed point releases, forcibly fix things here. 14 | * 15 | * Fortunately this matters only at compile time. 16 | */ 17 | #if (PG_VERSION_NUM >= 90500 && PG_VERSION_NUM < 90510) \ 18 | || (PG_VERSION_NUM >= 90600 && PG_VERSION_NUM < 90606) \ 19 | || (PG_VERSION_NUM >= 100000 && PG_VERSION_NUM < 100001) 20 | 21 | #undef INT64CONST 22 | #undef UINT64CONST 23 | #if defined(HAVE_LONG_INT_64) 24 | #define INT64CONST(x) (x##L) 25 | #define UINT64CONST(x) (x##UL) 26 | #elif defined(HAVE_LONG_LONG_INT_64) 27 | #define INT64CONST(x) (x##LL) 28 | #define UINT64CONST(x) (x##ULL) 29 | #else 30 | #error must have a working 64-bit integer datatype 31 | #endif 32 | 33 | #ifdef PG_INT64_MIN 34 | #undef PG_INT64_MIN 35 | #endif 36 | #ifdef PG_INT64_MAX 37 | #undef PG_INT64_MAX 38 | #endif 39 | 40 | #endif /* CONST_MAX_HACK */ 41 | 42 | #ifndef PG_INT64_MIN 43 | #define PG_INT64_MIN (-INT64CONST(0x7FFFFFFFFFFFFFFF) - 1) 44 | #endif 45 | #ifndef PG_INT64_MAX 46 | #define PG_INT64_MAX INT64CONST(0x7FFFFFFFFFFFFFFF) 47 | #endif 48 | 49 | /* catalog OID #defines changed in 14. */ 50 | #if PG_VERSION_NUM < 140000 51 | #define EVENT_TRIGGEROID EVTTRIGGEROID 52 | #endif 53 | 54 | /* float4 is always by value in pg >= 13. */ 55 | #if PG_VERSION_NUM >= 130000 56 | #ifndef USE_FLOAT4_BYVAL 57 | #define USE_FLOAT4_BYVAL 1 58 | #endif 59 | #endif 60 | 61 | /* 62 | * CommandTag in pg13+ is an enum, not a string. GetCommandTagName returns the 63 | * old name, but obviously didn't exist in previous versions. 64 | */ 65 | #if PG_VERSION_NUM < 130000 66 | #define GetCommandTagName(t_) (t_) 67 | #endif 68 | 69 | /* RIP, oids. */ 70 | #if PG_VERSION_NUM >= 120000 71 | #define TupleDescHasOids(tupdesc) (false) 72 | #define IsObjectIdAttributeNumber(a) (false) 73 | /* 74 | * since hasoid is always supposed to be false thanks to the above, any 75 | * references to the below macros should be unreachable 76 | */ 77 | #define HeapTupleHeaderGetOid(h) (AssertMacro(false), InvalidOid) 78 | #define HeapTupleHeaderSetOid(h,o) do { (void) (h); (void) (o); Assert(false); } while (0) 79 | #define HeapTupleSetOid(h,o) do { (void) (h); (void) (o); Assert(false); } while (0) 80 | #else 81 | #define TupleDescHasOids(tupdesc) ((tupdesc)->tdhasoid) 82 | #define IsObjectIdAttributeNumber(a) ((a) == ObjectIdAttributeNumber) 83 | #endif 84 | 85 | /* cope with variable-length fcinfo in pg12 */ 86 | #if PG_VERSION_NUM < 120000 87 | #define LOCAL_FCINFO(name_,nargs_) \ 88 | FunctionCallInfoData name_##data; \ 89 | FunctionCallInfo name_ = &name_##data 90 | 91 | #define LFCI_ARG_VALUE(fci_,n_) ((fci_)->arg[n_]) 92 | #define LFCI_ARGISNULL(fci_,n_) ((fci_)->argnull[n_]) 93 | #else 94 | #define LFCI_ARG_VALUE(fci_,n_) ((fci_)->args[n_].value) 95 | #define LFCI_ARGISNULL(fci_,n_) ((fci_)->args[n_].isnull) 96 | #endif 97 | 98 | /* TupleDesc structure change */ 99 | #if PG_VERSION_NUM < 100000 100 | #define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) 101 | #endif 102 | 103 | /* AllocSetContextCreate API changes */ 104 | #if PG_VERSION_NUM < 110000 105 | #define AllocSetContextCreateInternal AllocSetContextCreate 106 | #elif PG_VERSION_NUM < 120000 107 | #define AllocSetContextCreateInternal AllocSetContextCreateExtended 108 | #endif 109 | /* 110 | * Protect against backpatching or lack thereof. 111 | */ 112 | #ifndef ALLOCSET_DEFAULT_SIZES 113 | #define ALLOCSET_DEFAULT_SIZES \ 114 | ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE 115 | #endif 116 | #ifndef ALLOCSET_SMALL_SIZES 117 | #define ALLOCSET_SMALL_SIZES \ 118 | ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE 119 | #endif 120 | #ifndef ALLOCSET_START_SMALL_SIZES 121 | #define ALLOCSET_START_SMALL_SIZES \ 122 | ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE 123 | #endif 124 | 125 | /* We want a way to do noinline, but old PGs don't have it. */ 126 | 127 | #ifndef __has_builtin 128 | #define __has_builtin(x_) 0 129 | #endif 130 | #ifndef __has_attribute 131 | #define __has_attribute(x_) 0 132 | #endif 133 | 134 | #if defined(pg_noinline) 135 | #define pllua_noinline pg_noinline 136 | #elif __has_attribute(noinline) || (defined(__GNUC__) && __GNUC__ > 2) || defined(__SUNPRO_C) || defined(__IBMC__) 137 | #define pllua_noinline __attribute__((noinline)) 138 | /* msvc via declspec */ 139 | #elif defined(_MSC_VER) 140 | #define pllua_noinline __declspec(noinline) 141 | #else 142 | #define pllua_noinline 143 | #endif 144 | 145 | /* and likewise for unlikely() */ 146 | #if !defined(unlikely) 147 | 148 | #if !defined(__builtin_expect) && !defined(__GNUC__) && !__has_builtin(__builtin_expect) 149 | #define __builtin_expect(x_,y_) (x_) 150 | #endif 151 | #define likely(x) (__builtin_expect(!!(x), 1)) 152 | #define unlikely(x) (__builtin_expect(!!(x), 0)) 153 | 154 | #endif /* unlikely */ 155 | 156 | #endif /* PLLUA_PGVER_H */ 157 | -------------------------------------------------------------------------------- /src/preload.c: -------------------------------------------------------------------------------- 1 | /* preload.c */ 2 | 3 | /* 4 | * Preloading of lua modules built into the binary. 5 | */ 6 | 7 | #include "pllua.h" 8 | 9 | /* 10 | * Apple has to be different, of course. 11 | */ 12 | #ifdef __darwin__ 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | static void 19 | pllua_darwin_get_chunk(lua_State *L, 20 | const char *name, 21 | const void **ptr, 22 | size_t *sz) 23 | { 24 | void *tptr; 25 | Dl_info info; 26 | if (!dladdr(&pllua_darwin_get_chunk, &info)) 27 | luaL_error(L, "dladdr failed: %s", dlerror()); 28 | tptr = getsectiondata(info.dli_fbase, "binary", name, sz); 29 | if (!tptr) 30 | luaL_error(L, "getsectiondata failed"); 31 | *ptr = tptr; 32 | return; 33 | } 34 | 35 | #define GETCHUNK(L_, chunk_,start_,sz_) \ 36 | pllua_darwin_get_chunk(L_, #chunk_, &(start_), &(sz_)) 37 | 38 | #else 39 | 40 | extern const char _binary_src_compat_luac_start[]; 41 | extern const char _binary_src_compat_luac_end[]; 42 | 43 | #define MKSYM(a_,b_,c_) a_##b_##c_ 44 | 45 | #define GETCHUNK(L_, chunk_,start_,sz_) \ 46 | do { (start_) = MKSYM(_binary_src_,chunk_,_start); \ 47 | (sz_) = MKSYM(_binary_src_,chunk_,_end) \ 48 | - MKSYM(_binary_src_,chunk_,_start); \ 49 | } while (0) 50 | 51 | #endif 52 | 53 | static void 54 | pllua_load_binary_chunk(lua_State *L, const char *chunkname, const char *start, size_t len) 55 | { 56 | int rc = luaL_loadbuffer(L, start, len, chunkname); 57 | if (rc) 58 | lua_error(L); 59 | } 60 | 61 | /* 62 | * Upvalue 1 is the metatable to use with the environment 63 | */ 64 | int 65 | pllua_preload_compat(lua_State *L) 66 | { 67 | const void *ptr; 68 | size_t sz; 69 | GETCHUNK(L, compat_luac, ptr, sz); 70 | pllua_load_binary_chunk(L, "compat.lua", ptr, sz); 71 | lua_newtable(L); 72 | lua_pushvalue(L, lua_upvalueindex(1)); 73 | lua_setmetatable(L, -2); 74 | pllua_set_environment(L, -2); 75 | lua_pushvalue(L, lua_upvalueindex(1)); 76 | lua_call(L, 1, 1); 77 | return 1; 78 | } 79 | -------------------------------------------------------------------------------- /tools/doc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | printf "\n" 4 | 5 | for fn; do 6 | case "$fn" in 7 | *.css) printf '\n";; 10 | *.js) printf '\n";; 13 | *.meta) cat -- "$fn";; 14 | esac 15 | done 16 | 17 | printf "\n" 18 | 19 | for fn; do 20 | shift 21 | case "$fn" in 22 | *.md) set -- "$@" "$fn";; 23 | *.html) cat -- "$fn";; 24 | esac 25 | done 26 | 27 | cmark --unsafe "$@" || exit 1 28 | 29 | printf "\n" 30 | 31 | exit 0 32 | -------------------------------------------------------------------------------- /tools/errcodes.lua: -------------------------------------------------------------------------------- 1 | -- errcodes.lua 2 | 3 | local fn = ... 4 | for line in io.lines(fn) do 5 | if line:match("^[^#%s]") and not line:match("^Section:") then 6 | local f1,f2,f3,f4 = line:match("^(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s*$") 7 | if f4 then 8 | io.write("{\n \"",f4,"\", ",f3,"\n},\n") 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /tools/functable.lua: -------------------------------------------------------------------------------- 1 | -- functable.lua 2 | 3 | local funcnames = {} 4 | local matchfuncs = { 5 | pllua_pushcfunction = true, 6 | pllua_cpcall = true, 7 | pllua_initial_protected_call = true, 8 | pllua_register_cfunc = true 9 | } 10 | 11 | for i,fn in ipairs{...} do 12 | for line in io.lines(fn) do 13 | for fn1,pos in line:gmatch("(pllua_[%w_]+)%(()") do 14 | if matchfuncs[fn1] then 15 | local fn2 = line:match("%s*[%w._]+%s*,%s*(pllua_[%w_]+)",pos) 16 | if fn2 ~= nil then 17 | funcnames[fn2] = true 18 | end 19 | end 20 | end 21 | end 22 | end 23 | 24 | local out = {} 25 | for k,_ in pairs(funcnames) do 26 | out[1+#out] = k 27 | end 28 | table.sort(out) 29 | for i,v in ipairs(out) do 30 | io.write("PLLUA_DECL_CFUNC(",v,")\n") 31 | end 32 | -------------------------------------------------------------------------------- /tools/logo.lua: -------------------------------------------------------------------------------- 1 | -- logo.lua 2 | 3 | -- make_encoder([separator [,blocksize [,alphabet]]]) 4 | -- 5 | -- Given an alphabet (or default), return an encoding function, 6 | -- which is callable as enc(str) 7 | 8 | local function make_encoder(sep,blksz,b64a) 9 | local char, byte = string.char, string.byte 10 | local concat = table.concat 11 | local unpack = table.unpack or unpack 12 | local fdiv = function(p,q) return math.floor(p/q) end -- cope with missing // 13 | 14 | -- default separator 15 | sep = sep or "\n" 16 | 17 | -- default blocksize 18 | blksz = fdiv((blksz or 76), 4) 19 | assert(blksz > 0, "invalid blocksize") 20 | -- input and output block sizes 21 | local iblksz, oblksz = blksz * 3, blksz * 4 22 | 23 | -- default b64 alphabet 24 | b64a = b64a or "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" 25 | assert(#b64a == 65, "invalid alphabet") 26 | local pad = byte(b64a, 65) 27 | local function b64chr(c) 28 | return byte(b64a, 1 + c) 29 | end 30 | 31 | -- precomputed lookup tables: 32 | -- given input bytes b1,b2,b3: 33 | -- b64a1[b1] = first output byte (from bits b1:111111xx) 34 | -- b64a2[b1][b2] = second output byte (from bits b1:xxxxxx11 b2:1111xxxx) 35 | -- b64a3[b3][b2] = third output byte (from bits b2:xxxx1111 b3:11xxxxxx) 36 | -- b64a4[b3] = fourth output byte (from bits b3:xx111111) 37 | -- 38 | local b64a1, b64a2, b64a3, b64a4 = {}, {}, {}, {} 39 | -- b64a1 is easy 40 | for b1 = 0,255 do 41 | b64a1[b1] = b64chr(fdiv(b1,4)) 42 | end 43 | -- b64a2[b1][b2] 44 | -- we take only the bottom 2 bits from b1, so there are only 4 distinct b2 tables 45 | -- we take the high 4 bits from b2 46 | for b1 = 0,3 do 47 | local t = {} 48 | for b2 = 0,255 do 49 | t[b2] = b64chr(fdiv(b2,16) + (b1 * 16)) 50 | end 51 | b64a2[b1] = t 52 | end 53 | for b1 = 4,255 do 54 | b64a2[b1] = b64a2[b1 % 4] 55 | end 56 | -- b64a3[b3][b2] (note reversed order to keep number of tables small) 57 | -- we take only the top 2 bits from b3, so there are only 4 distinct b2 tables 58 | -- we take the low 4 bits from b2 59 | for b3 = 0,255,64 do 60 | local t = {} 61 | for b2 = 0,255 do 62 | t[b2] = b64chr((b2 % 16)*4 + fdiv(b3,64)) 63 | end 64 | for i = 0,63 do 65 | b64a3[b3+i] = t 66 | end 67 | end 68 | -- b64a4 is easy 69 | for i = 0,255 do 70 | b64a4[i] = b64chr(i % 64) 71 | end 72 | 73 | return function(str) 74 | local chunks, chunk = {}, {} 75 | local c = 0 76 | local len = #str 77 | for i = 1, len - (len % iblksz), iblksz do 78 | -- fastpath loop 79 | for j = 1,blksz do 80 | local k = j*4 - 3 81 | local b1,b2,b3 = byte(str, i+k-j, i+k-j+2) 82 | chunk[k], chunk[k+1], chunk[k+2], chunk[k+3] 83 | = b64a1[b1], b64a2[b1][b2], b64a3[b3][b2], b64a4[b3] 84 | end 85 | c = c + 1 86 | chunks[c] = char(unpack(chunk, 1, oblksz)) 87 | end 88 | chunk = {} 89 | for i = len - (len % iblksz) + 1, len, 3 do 90 | local b1,b2,b3 = byte(str, i, i+2) 91 | chunk[1+#chunk] = char( b64a1[b1], 92 | b64a2[b1][b2 or 0], 93 | b2 and b64a3[b3 or 0][b2] or pad, 94 | b3 and b64a4[b3] or pad ) 95 | end 96 | if #chunk > 0 then 97 | c = c + 1 98 | chunks[c] = concat(chunk, "") 99 | end 100 | return concat(chunks, sep, 1, c) 101 | end 102 | end 103 | 104 | local mode,fmt,fn = ... 105 | local str = "" 106 | if mode == "-text" then 107 | for line in io.lines(fn) do 108 | str = str .. line .. "\x0D\x0A" -- canonical line endings 109 | end 110 | elseif mode == "-binary" then 111 | local file = io.open(fn, "rb") 112 | str = file:read("*a") 113 | file:close() 114 | else 115 | error("unknown mode") 116 | end 117 | 118 | if fmt:match("^-icon") then 119 | local sz = fmt:match("^-icon=(.*)") 120 | local basename = fn:match(".*/([^/]+)$") or fn 121 | local ext = fn:match("%.([^.]+)$") 122 | local mediatype = { ico = "image/x-icon", 123 | png = "image/png", 124 | gif = "image/gif", 125 | jpg = "image/jpeg" } 126 | local typ = mediatype[ext] 127 | local b64enc = make_encoder("", 120) 128 | io.write([[ 132 | ]]) 133 | elseif fmt == "-logo" then 134 | local b64enc = make_encoder("", 120) 135 | io.write([[ 136 | #logo { 137 | background-image: url("data:image/svg+xml;base64,]], b64enc(str), [["); 138 | } 139 | ]]) 140 | else 141 | error("unknown format") 142 | end 143 | -------------------------------------------------------------------------------- /tools/numeric-tests.mk: -------------------------------------------------------------------------------- 1 | ## 2 | 3 | include tools/numeric.mk 4 | 5 | define assert 6 | $(if $1,,$(error assert failed: $2)) 7 | endef 8 | 9 | define assert_n 10 | $(if $1,$(error assert_n failed: $2),) 11 | endef 12 | 13 | .PHONY: tools_test_numeric 14 | 15 | tools_test_numeric: 16 | $(call assert_n,$(call version_ge,9.0,9.1)) 17 | $(call assert,$(call version_ge,9.2,9.1)) 18 | $(call assert,$(call version_ge,9.0,9.0)) 19 | $(call assert_n,$(call version_ge,9.4,13)) 20 | -------------------------------------------------------------------------------- /tools/numeric.mk: -------------------------------------------------------------------------------- 1 | ##-- 2 | ## Hacks for numeric comparisons. 3 | 4 | # $(call version_ge,a,b) 5 | # true iff a >= b when treated as vectors of integers separated by . or space 6 | version_ge = $(strip $(call vge_2,$(subst ., ,$(1)),$(subst ., ,$(2)))) 7 | 8 | # if a is empty, result is true if b is empty otherwise false. 9 | # if b is empty, result is true. 10 | # if neither is empty, compare first words; if equal, strip one word and recurse. 11 | define vge_2 12 | $(if $(strip $(1)), 13 | $(if $(strip $(2)), 14 | $(if $(call numeric_lt,$(firstword $(2)),$(firstword $(1))), 15 | t, 16 | $(if $(call numeric_lt,$(firstword $(1)),$(firstword $(2))), 17 | , 18 | $(call vge_2,$(wordlist 2,$(words $(1)),$(1)),$(wordlist 2,$(words $(2)),$(2))))), 19 | t), 20 | $(if $(strip $(2)),,t)) 21 | endef 22 | 23 | # $(call numeric_le,a,b) - true if a <= b, i.e. either a is 0 or a can index a [1..b] list 24 | numeric_le = $(if $(filter 0,$(1)),t,$(word $(1),$(call nwords,$(2),))) 25 | 26 | # $(call numeric_lt,a,b) - true if a < b, i.e. !(b <= a) 27 | numeric_lt = $(if $(call numeric_le,$(2),$(1)),,t) 28 | 29 | # list of N words 30 | nwords = $(if $(filter-out 0,$(1)),$(if $(word $(1),$(2)),$(2),$(call nwords,$(1),$(2) t))) 31 | 32 | ## end of hacks. 33 | ##-- 34 | -------------------------------------------------------------------------------- /tools/reorder-o.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cmd="$1" 4 | shift 1 5 | 6 | findpos() { 7 | while :; do 8 | [ "$#" -lt 2 ] && { echo "no -o option found" >&2; return 1; } 9 | [ "x$1" = "x-o" ] && { echo "$#"; return 0; } 10 | shift 1; 11 | done 12 | } 13 | 14 | # cmd x x x -o y z z z 15 | 16 | nx=$(findpos "$@") 17 | [ -z "$nx" ] && exit 1 18 | 19 | n_before=$(expr "$#" - "$nx") 20 | n_after=$(expr "$nx" - 2) 21 | outfile=$(shift $n_before; printf '%s:' "$2") 22 | outfile="${outfile%?}" 23 | 24 | while :; do 25 | if [ "$n_before" -gt 0 ] 26 | then 27 | set -- "$@" "$1"; 28 | shift 1; 29 | n_before=$(expr "$n_before" - 1); 30 | elif [ "$n_before" -eq 0 ] 31 | then 32 | shift 2; 33 | n_before="-1"; 34 | elif [ "$n_after" -gt 0 ] 35 | then 36 | set -- "$@" "$1" 37 | shift 1 38 | n_after=$(expr "$n_after" - 1) 39 | else 40 | exec "$cmd" "$@" "$outfile" 41 | exit 127 42 | fi 43 | done 44 | echo "something went badly wrong" >&2; exit 1; 45 | --------------------------------------------------------------------------------