From ba55fce24118747330a3abc063e600aa641ae39e Mon Sep 17 00:00:00 2001 From: Marc Munro Date: Fri, 22 Jul 2011 17:27:05 -0700 Subject: [PATCH] First release to git --- COPYRIGHT | 9 + GNUmakefile | 134 ++ LICENSE | 20 + demo/GNUmakefile | 14 + demo/Makefile | 38 + demo/veil_demo.png | Bin 0 -> 9446 bytes demo/veil_demo.xml | 27 + docs/Doxyfile | 1298 +++++++++++++++++++ docs/GNUmakefile | 14 + docs/Makefile | 36 + docs/demo.xml | 123 ++ regress/GNUmakefile | 13 + regress/Makefile | 27 + regress/regress.log | 1936 ++++++++++++++++++++++++++++ regress/regress.sh | 1195 +++++++++++++++++ regress/regress_data.sql | 51 + regress/regress_tables.sql | 46 + src/GNUmakefile | 14 + src/Makefile | 43 + src/veil_bitmap.c | 605 +++++++++ src/veil_bitmap.d | 4 + src/veil_config.c | 107 ++ src/veil_config.d | 5 + src/veil_datatypes.c | 66 + src/veil_datatypes.d | 4 + src/veil_datatypes.h | 387 ++++++ src/veil_demo.sql | 423 ++++++ src/veil_funcs.h | 137 ++ src/veil_interface.c | 2506 ++++++++++++++++++++++++++++++++++++ src/veil_interface.d | 5 + src/veil_interface.sqs | 743 +++++++++++ src/veil_mainpage.c | 2229 ++++++++++++++++++++++++++++++++ src/veil_mainpage.d | 2 + src/veil_query.c | 302 +++++ src/veil_query.d | 5 + src/veil_serialise.c | 958 ++++++++++++++ src/veil_serialise.d | 5 + src/veil_shmem.c | 710 ++++++++++ src/veil_shmem.d | 6 + src/veil_shmem.h | 260 ++++ src/veil_utils.c | 62 + src/veil_utils.d | 4 + src/veil_variables.c | 327 +++++ src/veil_variables.d | 4 + src/veil_version.h | 20 + tools/psql_funcs.sh | 216 ++++ veil--1.0.sql | 743 +++++++++++ veil.control | 16 + veil_demo--1.0.sql | 1265 ++++++++++++++++++ veil_demo.control | 16 + veil_demo.mk | 16 + 51 files changed, 17196 insertions(+) create mode 100644 COPYRIGHT create mode 100644 GNUmakefile create mode 100644 LICENSE create mode 100644 demo/GNUmakefile create mode 100644 demo/Makefile create mode 100644 demo/veil_demo.png create mode 100644 demo/veil_demo.xml create mode 100644 docs/Doxyfile create mode 100644 docs/GNUmakefile create mode 100644 docs/Makefile create mode 100644 docs/demo.xml create mode 100644 regress/GNUmakefile create mode 100644 regress/Makefile create mode 100644 regress/regress.log create mode 100755 regress/regress.sh create mode 100644 regress/regress_data.sql create mode 100644 regress/regress_tables.sql create mode 100644 src/GNUmakefile create mode 100644 src/Makefile create mode 100644 src/veil_bitmap.c create mode 100644 src/veil_bitmap.d create mode 100644 src/veil_config.c create mode 100644 src/veil_config.d create mode 100644 src/veil_datatypes.c create mode 100644 src/veil_datatypes.d create mode 100644 src/veil_datatypes.h create mode 100644 src/veil_demo.sql create mode 100644 src/veil_funcs.h create mode 100644 src/veil_interface.c create mode 100644 src/veil_interface.d create mode 100644 src/veil_interface.sqs create mode 100644 src/veil_mainpage.c create mode 100644 src/veil_mainpage.d create mode 100644 src/veil_query.c create mode 100644 src/veil_query.d create mode 100644 src/veil_serialise.c create mode 100644 src/veil_serialise.d create mode 100644 src/veil_shmem.c create mode 100644 src/veil_shmem.d create mode 100644 src/veil_shmem.h create mode 100644 src/veil_utils.c create mode 100644 src/veil_utils.d create mode 100644 src/veil_variables.c create mode 100644 src/veil_variables.d create mode 100644 src/veil_version.h create mode 100644 tools/psql_funcs.sh create mode 100644 veil--1.0.sql create mode 100644 veil.control create mode 100644 veil_demo--1.0.sql create mode 100644 veil_demo.control create mode 100644 veil_demo.mk diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..2f6cb89 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,9 @@ +Veil - A data security API for the PostgreSQL Database Management System + +Copyright (c) 2005 - 2011 + Marc Munro, + Munro Information Services Ltd, + Vancouver BC, Canada + +This software is released under the BSD License as described in the +associated LICENSE file distributed with this software. diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..5ef14d0 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,134 @@ +# GNUmakefile +# +# PGXS-based makefile for Veil +# +# Copyright (c) 2005 - 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# +# For a list of targets use make help. +# + +all: + +SUBDIRS = src regress docs demo +include $(SUBDIRS:%=%/Makefile) + +BUILD_DIR = $(shell pwd) +MODULE_big = veil +OBJS = $(SOURCES:%.c=%.o) +DEPS = $(SOURCES:%.c=%.d) +EXTRA_CLEAN = $(SRC_CLEAN) +EXTENSION=veil +MODULEDIR=extension/veil +VEIL_VERSION = $(shell \ + grep default_version veil.control | sed 's/[^0-9.]*\([0-9.]*\).*/\1/') + +VEIL_CONTROL = veil--$(VEIL_VERSION).sql + +DATA = $(wildcard veil--*.sql) +ALLDOCS = $(wildcard docs/html/*) +# Only define DOCS (for the install target) if there are some. +ifneq "$(ALLDOCS)" "" + DOCS = $(ALLDOCS) +endif + +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +include $(DEPS) + + +# Build per-source dependency files for inclusion +# This ignores header files and any other non-local files (such as +# postgres include files). Since I don't know if this will work +# on non-Unix platforms, we will ship veil with the dep files +# in place). This target is mostly for maintainers who may wish +# to rebuild dep files. +%.d: %.c + @echo Recreating $@ + @$(SHELL) -ec "$(CC) -MM -MT $*.o $(CPPFLAGS) $< | \ + xargs -n 1 | grep '^[^/]' | \ + sed -e '1,$$ s/$$/ \\\\/' -e '$$ s/ \\\\$$//' \ + -e '2,$$ s/^/ /' | \ + sed 's!$*.o!& $@!g'" > $@ + +# Target used by recursive call from deps target below. This ensures +# that make deps always rebuilds the dep files even if they are up to date. +make_deps: $(DEPS) + +# Target that rebuilds all dep files unconditionally. There should be a +# simpler way to do this using .PHONY but I can't figure out how. +deps: + rm -f $(DEPS) + $(MAKE) MAKEFLAGS="$(MAKEFLAGS)" make_deps + +# Recursive make is used to cope with pgxs' inability to build more than +# one library per makefile. We recursively use this makefile to build +# the veil_trial shared library. +all: $(DATA) + +# Define some variables for the following tarball targets. +tarball tarball_clean: VEIL_DIR=veil_$(VEIL_VERSION) + +# Create a version numbered tarball of source (including deps), tools, +# and possibly docs. +tarball: + @rm -rf $(VEIL_DIR) + @if test ! -r docs/html/index.html; then \ + echo "You may want to make the docs first"; fi + @mkdir -p $(VEIL_DIR)/src $(VEIL_DIR)/regress + @cp CO* LI* GNU* veil--*sql veil.control veil_demo* $(VEIL_DIR) + @cp src/*akefile src/*.[cdh] src/*sqs $(VEIL_DIR)/src + @cp regress/*akefile regress/*.sh regress/*sql $(VEIL_DIR)/regress + @ln -s `pwd`/tools $(VEIL_DIR)/tools + @ln -s `pwd`/demo $(VEIL_DIR)/demo + @ln -s `pwd`/docs $(VEIL_DIR)/docs + @echo Creating veil_$(VEIL_VERSION).tgz... + @tar czhf veil_$(VEIL_VERSION).tgz $(VEIL_DIR) + @rm -rf $(VEIL_DIR) + +# Cleanup after creating a tarball +tarball_clean: + rm -rf $(VEIL_DIR) veil_$(VEIL_VERSION).tgz + +# Ensure that tarball tmp files and dirs are removed by the clean target +clean: tarball_clean + +ifndef VARIANT +# Explicit target for veil_trial library. Only required for non-variant +# builds as the target is otherwise defined automatically. +$(addsuffix $(DLSUFFIX), veil_trial): $(TRIAL_SOURCES) + @if [ "x$(VARIANT)" = "x" ]; then \ + $(MAKE) MAKEFLAGS="$(MAKEFLAGS)" VARIANT=veil_trial $@; \ + fi +endif + +# Install veil_demo as well as veil +install: demo_install +demo_install: + $(MAKE) MAKEFLAGS=$(MAKEFLAGS) -f veil_demo.mk install + +# Uninstall veil_demo as well as veil +uninstall: demo_uninstall +demo_uninstall: + $(MAKE) MAKEFLAGS=$(MAKEFLAGS) -f veil_demo.mk uninstall + +# Provide a list of the targets buildable by this makefile. +list help: + @echo -e "\n\ + Major targets for this makefile are:\n\n\ + all - Build veil libraries, without docs\n\ + check - Build veil and run regression tests\n\ + clean - remove target and object files\n\ + deps - Recreate the xxx.d dependency files\n\ + docs - Build veil html docs (requires doxygen and dot)\n\ + help - show this list of major targets\n\ + install - Install veil\n\ + list - show this list of major targets\n\ + regress - same as check\n\ + regress_clean - clean up after regression tests - (drop regressdb)\n\ + tarball - create a tarball of the sources and documents\n\ + uninstall - Undo the install\n\ +\n\ +" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cca2a5c --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/demo/GNUmakefile b/demo/GNUmakefile new file mode 100644 index 0000000..bd5dd5c --- /dev/null +++ b/demo/GNUmakefile @@ -0,0 +1,14 @@ +# ---------- +# GNUmakefile +# +# Copyright (c) 2005 - 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# +# ---------- +# + +all: + +%:: + cd ..; $(MAKE) MAKEFLAGS="$(MAKEFLAGS)" $@ \ No newline at end of file diff --git a/demo/Makefile b/demo/Makefile new file mode 100644 index 0000000..01d5004 --- /dev/null +++ b/demo/Makefile @@ -0,0 +1,38 @@ +# Makefile +# +# Makefile for the demo database components +# +# Copyright (c) 2005 - 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# +# Do not attempt to use this makefile directly: its targets are available +# and should be built from the main GNUmakefile in the parent directory. + +# ---------- +# +.PHONY: demo_all demo_clean demo dropdemo + +DEMO_DIR = demo +DEMO_GARBAGE = $(garbage:%=$(DEMO_DIR)/%) + +# This generates the xml definition of the demo database directly from the +# database. Or would, if it worked properly. +#demo.xml: $(shell ls demo/*sql) +# -$(DB2XML) | awk '//' > $@ + +# Autograph from cf consulting. Generates an input file for dot from an +# xml schema definition +AUTOGRAPH_XSL = $(HOME)/bin/autograph.xsl +AUTOGRAPH = $(XSLTPROC) --xinclude $(AUTOGRAPH_XSL) + +demo/veil_demo.png: demo/veil_demo.xml + $(AUTOGRAPH) demo/veil_demo.xml > demo/demo.dot + $(DOT) $@ demo/demo.dot + @rm -f demo/demo.dot + +# Clean this directory and ensure regression test db is removed. +demo_clean: + rm -f $(DEMO_GARBAGE) $(DEMO_DIR)/demo_build.log + +demo_distclean: demo_clean diff --git a/demo/veil_demo.png b/demo/veil_demo.png new file mode 100644 index 0000000000000000000000000000000000000000..490967257313e7303531a4e9aebdbf1a9919b23d GIT binary patch literal 9446 zcmch7XH-*9*DoNwSU^ERKtVv74J05<$s^&xgXxO?uYx{weF|0XV0GBp8Y#>_L?)Z!=LDCu`zQqQ&CZ| zX+KamprSejP*G7AF`hcnJl(d@IC;oDd2FO{bbMS>U1)A*SW*5ZF)>nJ9>&Yd8;P^D+r2+!d9P_DwbXustpBm7pSz`y6PCPv-p=guyAN_BC{lDPGoI?ZWV~^0w zNhzDJi*J!e97rZHpkl>zTEiGnF)#A{i{MbD5)Cx_4-~DI`{QI~W(xA6p;83+xAzV% zY?iXQY+VB;ZGNV2-@_Fi9m~lZ3<8c9kQ^K*{wkZk-jcNG*+V<0-{ZWiYb;a$t-)jh zWglwh{i`b`)xnY$@H%0`2?r0=+txW_X3k+W?+x)ylv4lZhSm`e&@qDpotvlbky}N{ z$;l8>+ypezL)Uhup|M@g7>i&>R zeRd!&7F@nS;P{upT>$4%GfV0_V^+EQe1Epq6(Lx3lsNTZ*v!oR$!nPI34c|DV(V!8 z7MB)0SuVjTxPq^V&%W~TSb`5#D}RU2Y5V%^`i_5m(E;{bU4@0x`J-0~j!{+3ro&3k zE;c3;Bj0RtH}|i_jv#YCT059EFfpGPV>a=%g`{S-Q2V3>6zbs8g?~jPfyB2JyFB3y z{>1`%{$qC*`iSFL@xPH4ih1!5FyT!=wxIyApccG(g5b+S%e|<*TYU{)Iu_ zf7H);!ZktC?PT^Rujzl<_|M`jh@fNbZIvJ==l^M>^87{vXh(1`pKZHNPX6>goXXRc z2I$a!O@FrS8aX*N_W_ls#L1MjKhT_QyFyNme{+t?bCw2Jx~{q#Dr*~pd4B%OhRWrF zMz}zP={8l&6M`g@2Cx0CUwtPOe)>aqX8J2FwDCx-+U5NeGYv47m)D>4ChmJ9+G|PF zTaq9mNzS*xI$N4bYcqpR$=E#;t|!hkfPbE+Y~Mx0hctjiUUfxvYf@Mv+Ca3#`Ru|? za=yXuM;0h#1RZo zFiR67LC$}m%?U)@ZsR}>hEvNdt%_1~v%? zQ>)Sd%s%0^bwBTtS-_SN4Y5sjKQ{Le%pdHNH0y>7y?j3c%VJuk-RGlJ(>s?3*4%xQos ztP8IkF@IZ#tXY8OjLULQ6DMnag`0e6SV_G>))Z!SFTp-*ZRAN>F_ovqTJ<;bM7tx4 zg!?A?>7nbiW1w&4Ug@9&=g#Xlf(eyTkm{>I&lNCQAv?YHljIG1RFk2G$xST)Tibjp36ckUmI$@4kH z5shnBO63Hc73@2k*1(V<3uH+*X6C#0_cct*3gnw6j%`bk=W|`SLfNh4+6Xw6Pdb`h zx>Cr>k++@q(@M}zWCw9p_t#k0k|uQKee}1Ibb#!T7r9mMU?nw2z^R(njV=!hC7gzI z>Ba<>B2Wx~2#wpCPt#y1#?Gp&lcRsr9YG*H7$L3>`c$8$0`_U#n0?BXsDN6mnckN| z7vEDz{5g7m`22IB15asy&tE!iZ8e!S%Cue}XEusW9-^GK}N+PE1Vm-6(wZ3*(;JF_a& zwAQ=Z{muADa|hBhej5KtgOdKQb-hK_R`%Rn6H78+-4s2P9iECwd{uh}_)^z;kr@eR z1Y5YIfYoLmWTdb?s!1DbDe+gvBOUG;+csaI}*=9}E`f13VKJoRZ%`^^c@wgkxw zghsUbKWZ|k4Iz3cPRx^>%;-VT5jan-Im-e#Q87T*RH*@$HF7fTf7Bd+DAku_Oq+H$ zrIG^-U;{mgJB2*~oZwrRQHHtJ1r<%jO4E;NKTBvP0&vW)@6n5zUW+kWS}b()M<{2; zn@$Pkfprlr#K2u^Lt>G&`NwB@R~RF7psj2wfQ8q_mkQVmdGs3NoWqb@SK%AxSBg?A z8}l0Bo8x;MGoYzV4|(OT4dYhM$*2&_PWqmP68P@%l2yk#?9kQ#K&^(aX zmpx^rbIwnR!6lRkDp}t2*S$0e7$cII)VEDKh#<{T93nKWAKNV=z24vY`(9 zbHNvOzK65K76xvuq$FCjYLS1aNaZBC|E^m=`PN)VsqUEd`W(%F$Hb2nZX zZ~ui2KET-UElLJ-jqs{^wp`z7bv5S`A0gM4OUWPD+1koYDkrmc~h(CVTY1h zbgZXO?!!)MmrQ)VW9%LqsQ=<`sQca%uECuBiCcmWo0nHoF<2)>$&R)Z2M0kZheNYA z&eyHFT7lcGgqKx{H}GNCRV|B4v6V93ifi-J50ZvKNpJi?@W~#^wBR1Y9#Whh`fZE) z+EvH_73S~wxhh1X_So*>Mhe>BmFK0pay^26{%1T(OH-Vf8(F5d5mETE7x-dYAjhFc=PGvN)zh1V#cj(f#TiML;Tscy6+!uyx`Mw&fghrUYxYAoiv4+-`JxzT zhsrqmOA5zQ4g)JNRyB0`Z2LmbN=!^A;OHM8aU#5z3;OASN|wL#3Hyg9NnDx~H8NzZ znC|NpWEYT#%l8PPbP`34D}z%^(O|Ws^s&8`~- z8-6v!-iV5Cj*3?2n@aYIR)8Cs)g7Cz4Zr>!{(bL$fYY=9#>HnJfGC z5S>hdI>py3P*%hDk46I1bclE<>j@bzJly{HmSdSdx+{>=xHz+LYj~x3@R|Lb3&Aa_ zw2g>XUR=>;804^c{u0i`QH z3&lv*Jfz4HqPN779Hv!4;}-IZsynUI3*Hh+gWv5I(oAJeU$>Nd=#*4^vbBhGI2?N- zAf6Qv3poX_e}08|`vy6{co~_%NC!%u0JDJQ_Y8zYq4PBV&Q0NK`DbLw!sq^-29b8Y zxevX-f>Zw;@d_~ZYM>bn(yE>FZW)mK#a{Q&N>slYT`V)u7H_!kF67-{|6(S!gxzZh zZjEd8Bbpwid=ob>SgXlYjLFbUt@;B!@=5xDhE{DIDEQ5yNGd-z=%N?*vu;~upCYlV zBD|A|W=O-N;$KMoBVON!R$Qv*k9fn}buP?i7gw$HaK~E9p*G#MORJFhNx!q}%?rV? z9zU3*8@iLf?6cI?4IjM}6cvTGD4OZ|SyuJERF_w;MToUqpq&&mq|%#HZj{u)4Tu7u zzQbUr(Wl^r5V#(lx%0j-Lg960U5(!g{)46H8Y1y7Hp<_EnYl5DFl?xTs7RNrT+0`+ zVp>C_N)^8GSH69ksnK!V#85@1!ZPFIdeRY9%4wiYbS99db-dsEq(gO~f;YFPt#US` zmKl(iUw<5GbibK}T!S7On2IFpHzd0XHRH{HGO}|!yv}2Pp8PH2-p=2}rMLk$ofKIG z{5kOpknVs6nN~uRl5o}dIqtXUm$ge^!8v(giobaK_E~9XanaAGc&`nG2}No#o-Rk$ zAB)>J`s2T^{EQG!z=`RvDF+f9+UUJPtOSI4Z8AyU?GLXtJ>-t} z<%Q075v?Tp#?_<~dy~QWrg6}z-Gmu5&kwg(gK(Ui1)o{K2kU|D&wAk4Q$^+x*22ep;|B>?bsZ5cj^tx>DEkh=e}!DC}#FmEkDq;lpmmuW(8^}bmV+AEM{TkZ}3&z zoM%aCo(KL-MfcY;@*(mcpX;>eroy(X$~I0LvtIU#r6Q_l9|)qX!SY0=tQ#%(rPr1M z1M*5p&q!mnT=>tIU>;qDHMF?xZ{H7+-qu~W9Xz|%tgZ)r2kdhVI(ZJ?G2~W&bMJza zuG*;SR?_u^L^!p{@p)jTbXN2_0#bC$c$T=cZLIf1bOTC+o#t>-frEu~XEA_)diAf+5*eia^hzT246y1tMcIzP4SI9cc3g!Vw-n#A=4Ep-ll zNu@tn=VX)31W}H5m7kIBxbE$GnVs~Vb`*Hp>`j~!z$lyfSPaGTpf$j7^vCQyMwRh5 z5%1lkZh6?i*a$az;E7;51^^|?U|v2=m=e~YnD}1|w9@OG@e#gwS*N>2!H1F$s=Ug2 zbkx9XE812^b48Y~@j*e|*p+ z3HjV9Bh{#UcHEK%Zz~CW`EUce&VJTx`Mk53G&eAqa#N;SXj%v6!8I$XHwB)l#sGv6& zUakpm$a#P7YV|vknXXzbX4DH`LK?DW2tbS;f)yN>f9}D91>;z0@mI#oo=s3WcQI?%pqWKKwl&tsmDRGM^M*jAxLWKKui!x<^~?ZXQ&tvRLM zVb<-<%343kiwyi3PsH@{<)^jN(R=zg4#a$mCWwf=Ns%N!(WtQ~Y2v{t+<3C;F1X&> zuznRo1DoX~bfiSR+0>em>fVTZ_MC z39T;qQ%C>sEXOV)yVt$IvhLSHG&M{giV4z~)A85JcnQ;gacV&m+y(dtbZ5Y3#z19I z_RPQSe=Ezso+>-c#InsG!hhojoYr?S`Cb(vb+>u+qLP zw@dDH3}5Yt5g-Uq=22-2F#EKF+mDUaQ8@?pHzQD$m*$-=XNqY`gocU`c=B<7?|}<@;Lvm@YMr{Al$b#h0%Vve)O|@nku{MzOseJfu~&*)gp| zeAf?ZKq=*gmGJm(W$8#e=r&SLu8AsTYa(5~L|~u6)iZhTS$2765&=VxxQhr zVEJ@whEmDxNTKCw{dF4^9LDA5j~qc7O9g2BW>2xM>_A)1LOaLCPqD+BfsW(qw7_$( zda#{`NS%LoO}Og*wL5~?0zqm^X(+=bxwPG?1^MMEv%hrb+Q?(s?Y~aViscAVF-a~SXMt!cnR$D1ZWqA#{?8!lHa=v>H zsJ8#89MuLsyIG6zHiyP#xD^LJ71T?hZ?ESsuFO1*R9bFtTw1^>Gem6B{~aqo4LnAm zqC;TJOhE+I@0CA!DoEsu!ZUyI@A@&15d>TE{5mS~1MLg{GMAx5C)PWbt-F3_@&I5o6CCg21;=RYtI(xZKOG@)b+yCRNu9cpmdu>hHZyHz=ADnPfMGwqfSNIYII zX&N`#*qvr|dP)!Ss=S;vZELBr|34?aEvW{x(n0IEGcZ@A^h1W~_4 z%K3$UX!PLF3LaiyrhAXJ;=nm4qU-JPw60Qru8Gf%3vT%43&@1PJ#|}?9n&@@U5d{{ zXpCzp49IllY$u^SVH?ui_d30u_z6U3Q2Uff3%a*bq&jlb7TVTjXOZO%%=xDXU(`K zht^(Pdgg8Y9@Z3R{2KH1`;R>Wp9d2v;czbFwtKVz*nx`a;5oKjZa)Y~el8^;XoVI=pcM zHrP$TLBuZJsfu*BpgyRl!#a6utdfo$VO=&H^Xqc#%4uu%@C2A$jg)YDOGD-rnQnH* zc|ee_ma46Cmu6I%cmu>8=Cz_n|B6msM)37S7?tJAWc-j7iSD1Sz#(o{ydY^;7|PJd z-sIu;Tyuc@e}nw&`knX?y`?gUV(TqF_kL|K-_?1T*LT?SGIdwU@PQ{)TQi)0^CFlQ z*)sD?f}yfZ=~`167AgGB&1Zk*lA)j1T&UV@Xv|2Wjmr}{3L}!jU|!o2V03U3V#3%H ziPsxHczK1VXLb)TcaV3Jy@UB$S}eOym6xCJ$mTI!@WrNE*}j5ARKxd3--hSBC5y6m ztt%1Sk-F_X;`vLph^+g9h zQq84kz66vg!yB zB)1aKQvKJ#Q97Y+GznEM<~$R2#S9`utFnW|R$_BFlQpFGdm)p;l182{wq(T1uw}3% z!o3s3vY!IlA{y~O8sQ!i1Ra$-GPF=ep6ESaA$sWKwE2G|wshcU{=NxZV2z6y=FQCe z^~m+6s0wxFrD}zqHcYWgVa*DtC?(%H*SGM1TM*?Ck@Lwc^17~1&5z3DTfn|H6XW9e z*E!Tmy_;3TrsZLOJt-jS26zE|Hv!0avDLE}r9SI+Vd73;&0>wWu-}0 z?t|+J{F5~$rivr=iBoQdLt#XF4IuM#7^awzt%>P(2z*!m3T6R)9qT8XD8ejmi2D;+ zB|$g(FYhs=ozr&IfcBb6lEy?wOU`k=f0Fsh<;+&GL()!rgrj_$Xx3<@tBKmEZDaA7 z6f2EwM}(u1d72`RI7*JzQnB(ci2Sml0c>Lu?j_bgvqO z@fTNx@oz?2RQ9g#&F(oD9ynPX9|}c_u&&iH*y-C|E%0AGtZ5B{wpkV@oK3&m>FnOa z=!WGHg&oyGu?y=z2PaZ6h8;D6DQ@rFGy6e(`2cXv_sl}s^OS#M9wo|ejucNOc@_4QIb@}!34@PyHM=pK)FRAO`ZWKj} zO@#XtzJD{ZvR-A<8>ds$p=WwEvc?Xht5K~lUrXtzd57N8;AqQX6NVj%l`lgQ#ABX{ z%C-p1iifwWLZy7pkM1pWLAH)Jh5U6V zIi}+1&bbRtGk|0QH+Et(<&wA^##0C<|rDDx?WGtf5MBF-6xWHqVFgo`xy z`$p>Brr5|x^#$^EUw&H;Zxe)9d;f$#7K6sKVHZqh|k?}5Zy@eFdkK?;qMLqRN_|KNZlarEk1(Jv4wOgFOBO@ z89^esQ=AKmND76A)=REuAv=3C*kDeUXP>4{UZ) z%(ArXcU9Wui}DTM&0GGy9dydSa*S<1)EHPe9ceB;VrbBcUcL1cH=huMFaHEab(0tZSVn@nlfh_yskD2p5}(a6h=11ED@+ASRX?et`*8BL7r{pxZ#G(qS+@n=x` z6#C;T*h+1itvuCj~f~MZAS|ES?_RZeI}*2)7MS zDkP-GE(Xkx$vM_cEI09N_9XtL)38*J@8ew@Egq_@(>vg3dA%mtj`8kxt@T}{B(%EL z1kMj_@#I$Q9L(wlvNO*V|4!H0>Ak%roorTsr3Y5zDoMj`P~j$JOQq-aHzVE9d+m7l zK=y~kh1$1()Gm+~Y!t17 zQnMN+J@4#Lwht%Fq#7EO^Ib;FcC3D&@ke{R&S8oJKf=^cu5yRijFekbd$5BzM~aOi zp2s@y;OOYcMYQ0Uc;@IJisR?J@!{4;kF;&j%V8Owugf>1*Y>g;9`Em3{DA*i=}J8l ziIP;8APX17L_0`3U-`;VcsgN8gEH^f1`jSvyQ)j+XuYq~(N?CtK9MQCfIEnJDeNbX zZ)jHjIdbMCprC4<vYblCkN=KxF!-9=`M`!h-GL@WJt>6f4j`dPA9`SPZ@8oO($V8WPyhFp1nqBuTLnWfq zVys*slA(_U6#81`rlG+2{-pFF<7KR}f!EmOPz&`C%;3f|ns=~MglQ&F + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 0000000..83fddbe --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,1298 @@ +# Doxyfile.in +# +# Autoconf file for generation of Doxyfile +# +# Copyright (c) 2005, 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# + + + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for Veil +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file that +# follow. The default is UTF-8 which is also the encoding used for all text before +# the first occurrence of this tag. Doxygen uses libiconv (or the iconv built into +# libc) for the transcoding. See http://www.gnu.org/software/libiconv for the list of +# possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = "Veil" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = docs + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of source +# files, where putting all generated files in the same directory would otherwise +# cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, +# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, +# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, +# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, +# Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# This tag can be used to specify the encoding used in the generated output. +# The encoding is not always determined by the language that is chosen, +# but also whether or not the output is meant for Windows or non-Windows users. +# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES +# forces the Windows encoding (this is the default for the Windows binary), +# whereas setting the tag to NO uses a Unix-style encoding (the default for +# all platforms other than Windows). + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is used +# as the annotated text. Otherwise, the brief description is used as-is. If left +# blank, the following values are used ("$name" is automatically replaced with the +# name of the entity): "The $name class" "The $name widget" "The $name file" +# "is" "provides" "specifies" "contains" "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited +# members of a class in the documentation of that class as if those members were +# ordinary class members. Constructors, destructors and assignment operators of +# the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explicit @brief command for a brief description. + +JAVADOC_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = YES + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources +# only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be extracted +# and appear in the documentation as a namespace called 'anonymous_namespace{file}', +# where file will be replaced with the base name of the file that contains the anonymous +# namespace. By default anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. + +SHOW_DIRECTORIES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from the +# version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ./src + +# This tag can be used to specify the character encoding of the source files that +# doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default +# input encoding. Doxygen uses libiconv (or the iconv built into libc) for the transcoding. +# See http://www.gnu.org/software/libiconv for the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp +# *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm + +FILE_PATTERNS = *.c *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories +# that are symbolic links (a Unix filesystem feature) are excluded from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the output. +# The symbol name can be a fully qualified name, a word, or if the wildcard * is used, +# a substring. Examples: ANamespace, AClass, AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = ./demo/ ./src/ + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = ./demo/ + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentstion. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = YES + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = /usr/include/postgresql/ \ + /usr/include/postgresql/8.4/server/ \ + /usr/include/postgresql/8.4/server//utils + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = PG_FUNCTION_INFO_V1(x)= \ + DOXYGEN_SHOULD_SKIP_THIS \ + PG_MODULE_MAGIC + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse the +# parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = NO + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. Note that this +# option is superseded by the HAVE_DOT option below. This is only a fallback. It is +# recommended to install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see http://www.mcternan.me.uk/mscgen/) to +# produce the chart and insert it in the documentation. The MSCGEN_PATH tag allows you to +# specify the directory where the mscgen tool resides. If left empty the tool is assumed to +# be found in the default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = NO + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = NO + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = NO + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = YES + +# If the CALLER_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES +# then doxygen will generate a caller dependency graph for every global +# function or class method. Note that enabling this option will +# significantly increase the time of a run. So in most cases it will be +# better to enable caller graphs for selected functions only using the +# \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = NO + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = NO + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the number +# of direct children of the root node in a graph is already larger than +# MAX_DOT_GRAPH_NOTES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 100 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes that +# lay further from the root node will be omitted. Note that setting this option to +# 1 or 2 may greatly reduce the computation time needed for large code bases. Also +# note that a graph may be further truncated if the graph's image dimensions are +# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). +# If 0 is used for the depth value (the default), the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, which results in a white background. +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = NO + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/docs/GNUmakefile b/docs/GNUmakefile new file mode 100644 index 0000000..663dc3b --- /dev/null +++ b/docs/GNUmakefile @@ -0,0 +1,14 @@ +# ---------- +# GNUmakefile +# +# Copyright (c) 2005 - 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# +# ---------- +# + +all: + +%:: + cd ..; $(MAKE) MAKEFLAGS="$(MAKEFLAGS)" $@ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..6907c6e --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,36 @@ +# Makefile +# +# Makefile for docs directory of Veil +# +# Copyright (c) 2005 - 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# +# +# Do not attempt to use this makefile explicitly: its targets are available +# and should be built from the main GNUmakefile in the parent directory. +# + +.PHONY: docs_clean docs docs_check + +DOCS_DIR = docs +DOCS_GARBAGE = $(garbage:%=$(DOCS_DIR)/%) + + +docs: docs/html + +docs/html: $(SOURCES) $(HEADERS) demo/veil_demo.png + doxygen $(DOCS_DIR)/Doxyfile || \ + (echo "Doxygen fails: is it installed?"; exit 2) + @rm -f NO # Don't know why this file is generated but it can go. + +# Clean this directory and ensure generated docs are removed +clean: docs_clean + +docs_clean: + @echo Cleaning docs... + @rm -f $(DOCS_GARBAGE) NO + @rm -rf $(DOCS_DIR)/html + +docs_distclean: docs_clean + @rm -f Doxyfile \ No newline at end of file diff --git a/docs/demo.xml b/docs/demo.xml new file mode 100644 index 0000000..daba95c --- /dev/null +++ b/docs/demo.xml @@ -0,0 +1,123 @@ + + + + + + + + + + +
+ + + + + + + + + +
+ + + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + + + + + + + + + +
+ + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + +
+ + +
+ + +
+ + +
+ + + +
+ + + + + +
+
diff --git a/regress/GNUmakefile b/regress/GNUmakefile new file mode 100644 index 0000000..1bb84fc --- /dev/null +++ b/regress/GNUmakefile @@ -0,0 +1,13 @@ +# ---------- +# GNUmakefile +# +# Copyright (c) 2005 - 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# +# + +DEFAULT: + +%:: + cd ..; $(MAKE) MAKEFLAGS="$(MAKEFLAGS)" $@ \ No newline at end of file diff --git a/regress/Makefile b/regress/Makefile new file mode 100644 index 0000000..603e79f --- /dev/null +++ b/regress/Makefile @@ -0,0 +1,27 @@ +# Makefile +# +# Makefile for regress directory of Veil +# +# Copyright (c) 2005 - 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# +# Do not attempt to use this makefile explicitly: its targets are available +# and should be built from the main GNUmakefile in the parent directory. +# + +.PHONY: regress check + +REGRESS_DIR = regress +REGRESS_CLEAN = $(garbage:%=$(REGRESS_DIR)/%) + + +# Run regression test +regress check: all + @cd $(REGRESS_DIR) 2>/dev/null; \ + ./regress.sh + +clean: regress_clean + +regress_clean: + @cd $(REGRESS_DIR) 2>/dev/null; ./regress.sh -d diff --git a/regress/regress.log b/regress/regress.log new file mode 100644 index 0000000..64a84ef --- /dev/null +++ b/regress/regress.log @@ -0,0 +1,1936 @@ +- Creating regression test objects... +- Loading the veil extension... +NOTICE: extension "veil" does not exist, skipping +DROP EXTENSION +CREATE EXTENSION +CREATE FUNCTION +- Creating tables... +- ...privileges... +CREATE TABLE +psql:./regress_tables.sql:20: NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "privilege__pk" for table "privileges" +ALTER TABLE +- ...role... +CREATE TABLE +psql:./regress_tables.sql:28: NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "role__pk" for table "roles" +ALTER TABLE +- ...role_privileges... +CREATE TABLE +psql:./regress_tables.sql:37: NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "role_privilege__pk" for table "role_privileges" +ALTER TABLE +ALTER TABLE +ALTER TABLE +- Populating tables... +BEGIN +INSERT 0 1 +INSERT 0 1 +INSERT 0 1 +INSERT 0 1 +INSERT 0 1 +INSERT 0 1 +INSERT 0 1 +INSERT 0 1 +INSERT 0 1 +COMMIT +Performing regression tests... +- +- ...test set 1: variables... +PREP +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t + +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t + +TEST 1.1 ~ #1 *| *VEIL_SHMEMCTL#Initial variable listing + 1 | VEIL_SHMEMCTL + +- NOTE Test 1.1 will fail (correctly) if this is not a freshly created database +- +TEST 1.2 = #42#create session int4 + 42 + +TEST 1.3 = #42#retrieve session int4 + 42 + +TEST 1.4 ~ #sess_int4 *\| *Int4 *\| *f#list defined variables + VEIL_SHMEMCTL | ShmemCtl | t + sess_int4 | Int4 | f + +TEST 1.5 ~ #shared_int4 *\| *Undefined *\| *t#create shared int4 + f + + VEIL_SHMEMCTL | ShmemCtl | t + shared_int4 | Undefined | t + sess_int4 | Int4 | f + +TEST 1.6 = #99#get shared int4 + 99 + +TEST 1.7 ~ #shared_int4 *\| *Int4 *\| *t#list defined variables + VEIL_SHMEMCTL | ShmemCtl | t + shared_int4 | Int4 | t + sess_int4 | Int4 | f + +TEST 1.8 ~ #ERROR.*mismatch#access non-existant session range +ERROR: type mismatch in sess_range: expected Range, got Undefined +DETAIL: Variable sess_range is not of the expected type. +TEST 1.9 = #14#create session range + 14 + +PREP + f + +TEST 1.10 = #1#create shared ranged + 1 + +TEST 1.11 ~ #5 *| *3#list defined variables + 5 | 3 + +- NOTE Test 1.11 will fail (correctly) if this is not a freshly created database +- +TEST 1.12 ~ #11 *| *24#show session range + 11 | 24 + +TEST 1.13 ~ #17 *| *17#show shared range + 17 | 17 + +TEST 1.14 ~ #ERROR.*mismatch#fetch against undefined int4 array +ERROR: type mismatch in sess_int4array: expected Int4Array, got Undefined +DETAIL: Variable sess_int4array is not of the expected type. +TEST 1.15 ~ #ERROR.*mismatch#set against undefined int4 array +ERROR: type mismatch in sess_int4array: expected Int4Array, got Undefined +DETAIL: Variable sess_int4array is not of the expected type. +TEST 1.16 = #t#create session int4 array + t + +TEST 1.17 ~ #range error#attempt to set arrary element out of range +ERROR: Int4ArraySet range error +DETAIL: Index (12) not in range 17..17. +TEST 1.18 = #14#set int4 array element + 14 + +TEST 1.19 = #14#fetch int4 array element + 14 + +PREP + t + +TEST 1.20 = #0#clear array and fetch element + 0 + +PREP + f + + t + +TEST 1.21 = #14#define and fetch from shared int4 array + 14 + +TEST 1.22 = #14#shared int4 array get + 14 + +PREP + 14 + + t + + t + + 24 + + 11 + +- +- ...test set 1a: variables (second process)... +TEST 1.23 ~ #4 *| *4#list all shared variables + 4 | 4 + +- NOTE Test 1.23 may fail if this is not a freshly created database +- +TEST 1.24 = #99#get shared int4 + 99 + +TEST 1.25 = #0#Before de-serialising session variables + 0 + +PREP + 1 + 1 + 1 + 1 + 1 + +TEST 1.25 = #5#Checking session variables are de-serialised + 5 + +TEST 1.26 ~ #17 *| *17#Checking Range of range variable + 17 | 17 + +TEST 1.27 ~ #11 *| *24#Checking Range of range2 variable +Performing regression tests... +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t + +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t + +TEST 1.1 ~ #1 *| *VEIL_SHMEMCTL#Initial variable listing + 1 | VEIL_SHMEMCTL +MATCH ~ "1 | VEIL_SHMEMCTL" + +PASS +END TEST 1.1 + +TEST 1.2 = #42#create session int4 + 42 +MATCH = "42" + +PASS +END TEST 1.2 + +TEST 1.3 = #42#retrieve session int4 + 42 +MATCH = "42" + +PASS +END TEST 1.3 + +TEST 1.4 ~ #sess_int4 *\| *Int4 *\| *f#list defined variables + VEIL_SHMEMCTL | ShmemCtl | t + sess_int4 | Int4 | f +MATCH ~ "sess_int4 | Int4 | f" + +PASS +END TEST 1.4 + +TEST 1.5 ~ #shared_int4 *\| *Undefined *\| *t#create shared int4 + f + + VEIL_SHMEMCTL | ShmemCtl | t + shared_int4 | Undefined | t +MATCH ~ "shared_int4 | Undefined | t" + sess_int4 | Int4 | f + +PASS +END TEST 1.5 + +TEST 1.6 = #99#get shared int4 + 99 +MATCH = "99" + +PASS +END TEST 1.6 + +TEST 1.7 ~ #shared_int4 *\| *Int4 *\| *t#list defined variables + VEIL_SHMEMCTL | ShmemCtl | t + shared_int4 | Int4 | t +MATCH ~ "shared_int4 | Int4 | t" + sess_int4 | Int4 | f + +PASS +END TEST 1.7 + +TEST 1.8 ~ #ERROR.*mismatch#access non-existant session range +ERROR: type mismatch in sess_range: expected Range, got Undefined +MATCH ~ "ERROR: type mismatch in sess_range: expected Range, got Undefined" +DETAIL: Variable sess_range is not of the expected type. +PASS +END TEST 1.8 + +TEST 1.9 = #14#create session range + 14 +MATCH = "14" + +PASS +END TEST 1.9 + + f + +TEST 1.10 = #1#create shared ranged + 1 +MATCH = "1" + +PASS +END TEST 1.10 + +TEST 1.11 ~ #5 *| *3#list defined variables + 5 | 3 +MATCH ~ "5 | 3" + +PASS +END TEST 1.11 + +TEST 1.12 ~ #11 *| *24#show session range + 11 | 24 +MATCH ~ "11 | 24" + +PASS +END TEST 1.12 + +TEST 1.13 ~ #17 *| *17#show shared range + 17 | 17 +MATCH ~ "17 | 17" + +PASS +END TEST 1.13 + +TEST 1.14 ~ #ERROR.*mismatch#fetch against undefined int4 array +ERROR: type mismatch in sess_int4array: expected Int4Array, got Undefined +MATCH ~ "ERROR: type mismatch in sess_int4array: expected Int4Array, got Undefined" +DETAIL: Variable sess_int4array is not of the expected type. +PASS +END TEST 1.14 + +TEST 1.15 ~ #ERROR.*mismatch#set against undefined int4 array +ERROR: type mismatch in sess_int4array: expected Int4Array, got Undefined +MATCH ~ "ERROR: type mismatch in sess_int4array: expected Int4Array, got Undefined" +DETAIL: Variable sess_int4array is not of the expected type. +PASS +END TEST 1.15 + +TEST 1.16 = #t#create session int4 array + t +MATCH = "t" + +PASS +END TEST 1.16 + +TEST 1.17 ~ #range error#attempt to set arrary element out of range +ERROR: Int4ArraySet range error +MATCH ~ "ERROR: Int4ArraySet range error" +DETAIL: Index (12) not in range 17..17. +PASS +END TEST 1.17 + +TEST 1.18 = #14#set int4 array element + 14 +MATCH = "14" + +PASS +END TEST 1.18 + +TEST 1.19 = #14#fetch int4 array element + 14 +MATCH = "14" + +PASS +END TEST 1.19 + + t + +TEST 1.20 = #0#clear array and fetch element + 0 +MATCH = "0" + +PASS +END TEST 1.20 + + f + + t + +TEST 1.21 = #14#define and fetch from shared int4 array + 14 +MATCH = "14" + +PASS +END TEST 1.21 + +TEST 1.22 = #14#shared int4 array get + 14 +MATCH = "14" + +PASS +END TEST 1.22 + + 14 + + t + + t + + 24 + + 11 + +TEST 1.23 ~ #4 *| *4#list all shared variables + 4 | 4 +MATCH ~ "4 | 4" + +PASS +END TEST 1.23 + +TEST 1.24 = #99#get shared int4 + 99 +MATCH = "99" + +PASS +END TEST 1.24 + +TEST 1.25 = #0#Before de-serialising session variables + 0 +MATCH = "0" + +PASS +END TEST 1.25 + + 1 + 1 + 1 + 1 + 1 + +TEST 1.25 = #5#Checking session variables are de-serialised + 5 +MATCH = "5" + +PASS +END TEST 1.25 + +TEST 1.26 ~ #17 *| *17#Checking Range of range variable + 17 | 17 +MATCH ~ "17 | 17" + +PASS +END TEST 1.26 + +TEST 1.27 ~ #11 *| *24#Checking Ran 11 | 24 + +TEST 1.28 = #24#Checking array2 variable(1) + 24 + +TEST 1.29 = #11#Checking array2 variable(2) + 11 + +TEST 1.30 = #0#Checking array2 variable(3) + 0 + +- ...test set 2: bitmaps... +PREP + 70 + + 2 + + f + +TEST 2.1 ~ #OK#Initialise shared and session bitmaps + OK | t | t + +TEST 2.2 = #3#Populate shared bitmap + 3 + +TEST 2.3 = #2#Populate session bitmap + 2 + +PREP +TEST 2.4 = #t#Test for known true values in session and shared bitmaps + t + +TEST 2.5 = #f#Test for known false value + f + +TEST 2.6 = #t#Clear sessionbitmap + t + +TEST 2.7 = #f#Test for absence of previous true value in shared bitmap + f + +TEST 2.8 ~ #20001.*|.*20070#Test bitmap range + 20001 | 20070 + +TEST 2.9 ~ #3.*|.*20070#Test bitmap bits + 3 | 20070 + +TEST 2.10 = #20070#Further test bitmap bits + 20001 + 20002 + 20070 + +- +- ...test set 2a: bitmaps (second process)... +PREP + t + +TEST 2.11 != #20070#Check that shared bitmap has bit cleared + 20001 + 20002 + +TEST 2.12 = #2#Check that shared bitmap has other bits still set + 2 + +PREP + 1 + +TEST 2.13 = #t#Test for known true values in serialised session bitmap + t + +PREP + t | t | t + + t | t | t | t + + t | t + +TEST 2.13 ~ #20001 *| *20003 *| *3#Check union of bitmaps + 20001 | 20003 | 3 + +PREP + t | t + +TEST 2.14 ~ #20002 *| *20002 *| *1#Check bitmap intersection + 20002 | 20002 | 1 + +- +- ...test set 3: bitmap arrays... +PREP + 2 + + 70 + +TEST 3.1 = #t#Create bitmap array + t + +TEST 3.2 = #f#Test false bit + f + +TEST 3.3 = #t#Set bit + t + +TEST 3.4 = #t#Test newly true bit + t + +TEST 3.5 = #t#Clear the aray + t + +TEST 3.6 = #f#Test that bit again + f + +PREP + f + +TEST 3.7 ~ #ERROR.*illegal#Attempt to create shared bitmap ref +ERROR: illegal attempt to define shared BitmapRef shared_bitmap_ref +HINT: BitmapRefs may only be defined as session, not shared, variables. +TEST 3.8 ~ #session_bitmap_refx#Create session bitmap ref + session_bitmap_refx + +TEST 3.9 ~ #ERROR.*not.*defined#Check for bitmap ref not in transaction +ERROR: BitmapRef session_bitmap_ref is not defined +HINT: Perhaps the name is mis-spelled, or its definition is missing from veil_init(). +PREP + session_bitmap_ref + + t | t + +TEST 3.10 ~ #2 *| *20001 *| *20003#Add bits thru ref, check bits in array + 2 | 20001 | 20003 + +PREP + session_bitmap_ref + + t + +TEST 3.11 ~ #2 *| *20001 *| *20003#Union through ref, check bits in array + 2 | 20001 | 20003 + +PREP + t + + t + + t + +TEST 3.12 ~ #1 *| *20003 *| *20003#Intersect thru ref, check bits in array + 1 | 20003 | 20003 + +PREP +TEST 3.13 ~ #10001.*10002#Check array range + 10001 | 10002 + +TEST 3.14 ~ #20001.*20070#Check bitmaps range + 20001 | 20070 + +PREP + f + + t + + session_bitmap_ref + +ge of range2 variable + 11 | 24 +MATCH ~ "11 | 24" + +PASS +END TEST 1.27 + +TEST 1.28 = #24#Checking array2 variable(1) + 24 +MATCH = "24" + +PASS +END TEST 1.28 + +TEST 1.29 = #11#Checking array2 variable(2) + 11 +MATCH = "11" + +PASS +END TEST 1.29 + +TEST 1.30 = #0#Checking array2 variable(3) + 0 +MATCH = "0" + +PASS +END TEST 1.30 + + 70 + + 2 + + f + +TEST 2.1 ~ #OK#Initialise shared and session bitmaps + OK | t | t +MATCH ~ "OK | t | t" + +PASS +END TEST 2.1 + +TEST 2.2 = #3#Populate shared bitmap + 3 +MATCH = "3" + +PASS +END TEST 2.2 + +TEST 2.3 = #2#Populate session bitmap + 2 +MATCH = "2" + +PASS +END TEST 2.3 + +TEST 2.4 = #t#Test for known true values in session and shared bitmaps + t +MATCH = "t" + +PASS +END TEST 2.4 + +TEST 2.5 = #f#Test for known false value + f +MATCH = "f" + +PASS +END TEST 2.5 + +TEST 2.6 = #t#Clear sessionbitmap + t +MATCH = "t" + +PASS +END TEST 2.6 + +TEST 2.7 = #f#Test for absence of previous true value in shared bitmap + f +MATCH = "f" + +PASS +END TEST 2.7 + +TEST 2.8 ~ #20001.*|.*20070#Test bitmap range + 20001 | 20070 +MATCH ~ "20001 | 20070" + +PASS +END TEST 2.8 + +TEST 2.9 ~ #3.*|.*20070#Test bitmap bits + 3 | 20070 +MATCH ~ "3 | 20070" + +PASS +END TEST 2.9 + +TEST 2.10 = #20070#Further test bitmap bits + 20001 + 20002 + 20070 +MATCH = "20070" + +PASS +END TEST 2.10 + + t + +TEST 2.11 != #20070#Check that shared bitmap has bit cleared + 20001 + 20002 + +PASS +END TEST 2.11 + +TEST 2.12 = #2#Check that shared bitmap has other bits still set + 2 +MATCH = "2" + +PASS +END TEST 2.12 + + 1 + +TEST 2.13 = #t#Test for known true values in serialised session bitmap + t +MATCH = "t" + +PASS +END TEST 2.13 + + t | t | t + + t | t | t | t + + t | t + +TEST 2.13 ~ #20001 *| *20003 *| *3#Check union of bitmaps + 20001 | 20003 | 3 +MATCH ~ "20001 | 20003 | 3" + +PASS +END TEST 2.13 + + t | t + +TEST 2.14 ~ #20002 *| *20002 *| *1#Check bitmap intersection + 20002 | 20002 | 1 +MATCH ~ "20002 | 20002 | 1" + +PASS +END TEST 2.14 + + 2 + + 70 + +TEST 3.1 = #t#Create bitmap array + t +MATCH = "t" + +PASS +END TEST 3.1 + +TEST 3.2 = #f#Test false bit + f +MATCH = "f" + +PASS +END TEST 3.2 + +TEST 3.3 = #t#Set bit + t +MATCH = "t" + +PASS +END TEST 3.3 + +TEST 3.4 = #t#Test newly true bit + t +MATCH = "t" + +PASS +END TEST 3.4 + +TEST 3.5 = #t#Clear the aray + t +MATCH = "t" + +PASS +END TEST 3.5 + +TEST 3.6 = #f#Test that bit again + f +MATCH = "f" + +PASS +END TEST 3.6 + + f + +TEST 3.7 ~ #ERROR.*illegal#Attempt to create shared bitmap ref +ERROR: illegal attempt to define shared BitmapRef shared_bitmap_ref +MATCH ~ "ERROR: illegal attempt to define shared BitmapRef shared_bitmap_ref" +HINT: BitmapRefs may only be defined as session, not shared, variables. +PASS +END TEST 3.7 + +TEST 3.8 ~ #session_bitmap_refx#Create session bitmap ref + session_bitmap_refx +MATCH ~ "session_bitmap_refx" + +PASS +END TEST 3.8 + +TEST 3.9 ~ #ERROR.*not.*defined#Check for bitmap ref not in transaction +ERROR: BitmapRef session_bitmap_ref is not defined +MATCH ~ "ERROR: BitmapRef session_bitmap_ref is not defined" +HINT: Perhaps the name is mis-spelled, or its definition is missing from veil_init(). +PASS +END TEST 3.9 + + session_bitmap_ref + + t | t + +TEST 3.10 ~ #2 *| *20001 *| *20003#Add bits thru ref, check bits in array + 2 | 20001 | 20003 +MATCH ~ "2 | 20001 | 20003" + +PASS +END TEST 3.10 + + session_bitmap_ref + + t + +TEST 3.11 ~ #2 *| *20001 *| *20003#Union through ref, check bits in array + 2 | 20001 | 20003 +MATCH ~ "2 | 20001 | 20003" + +PASS +END TEST 3.11 + + t + + t + + t + +TEST 3.12 ~ #1 *| *20003 *| *20003#Intersect thru ref, check bits in array + 1 | 20003 | 20003 +MATCH ~ "1 | 20003 | 20003" + +PASS +END TEST 3.12 + +TEST 3.13 ~ #10001.*10002#Check array range + 10001 | 10002 +MATCH ~ "10001 | 10002" + +PASS +END TEST 3.13 + +TEST 3.14 ~ #20001.*20070#Check bitmaps range + 20001 | 20070 +MATCH ~ "20001 | 20070" + +PASS +END TEST 3.14 + + f + + t + + session_bitmap_ t + +- +- ...test set 3a: bitmap arrays (second process)... +TEST 3.15 ~ #1 *| *20003 *| *20003#Check bits in shared bitmap array + 1 | 20003 | 20003 + +PREP + 1 + +TEST 3.16 ~ #10001.*10002#Check array range after de-serialisation + 10001 | 10002 + +TEST 3.17 ~ #20001.*20070#Check bitmaps range after de-serialisation + 20001 | 20070 + +TEST 3.18 ~ #1 *\| *20003 *\| *20003#Check bits in array after de-ser. + 1 | 20003 | 20003 + +- +- ...test set 4: bitmap hashes... +PREP + 70 + +TEST 4.1 = #t#Create session bitmap hash + t + +TEST 4.2 = #f#Check for known false + f + +TEST 4.3 = #t#Set a bit + t + +TEST 4.4 = #t#Check that it is now true + t + +PREP +TEST 4.5 = #t#Clear the hash + t + +TEST 4.6 = #f#Check that bit again + f + +PREP + f + +TEST 4.7 ~ #ERROR.*illegal#Attempt to create shared bitmap hash +ERROR: illegal attempt to define shared BitmapRef shared_bitmap_ref +HINT: BitmapRefs may only be defined as session, not shared, variables. +TEST 4.8 ~ #session_bitmap_refx#Get bitmap ref from bitmap hash + session_bitmap_refx + +TEST 4.9 ~ #ERROR.*not.*defined#Check ref in transaction +ERROR: BitmapRef session_bitmap_ref is not defined +HINT: Perhaps the name is mis-spelled, or its definition is missing from veil_init(). +PREP + session_bitmap_ref + + t | t + +TEST 4.10 ~ #2 *| *20001 *| *20003#Test bits after setting them + 2 | 20001 | 20003 + +PREP + session_bitmap_ref + + t + +TEST 4.11 ~ #2 *| *20001 *| *20003#Union and then test bits +WARNING: there is already a transaction in progress + 2 | 20001 | 20003 + +PREP + t + + t + + t + +TEST 4.12 ~ #1 *| *20003 *| *20003#Intersect and test bits + 1 | 20003 | 20003 + +TEST 4.13 ~ #wibble#Test bitmap hash entry (a) + wibble + rubble + +TEST 4.14 ~ #rubble#Test bitmap hash entry (b) + wibble + rubble + +TEST 4.15 ~ #20001.*20070#Test range of bitmaps in hash + 20001 | 20070 + +PREP + f + +TEST 4.16 ~ #ERROR.*illegal.*BitmapHash#Attempt to create shared bitmap hash +ERROR: illegal attempt to define shared BitmapHash shared_role_privs2 +HINT: BitmapHashes may only be defined as session, not shared, variables. +PREP + t + + t + +TEST 4.17 ~ #3.*20001.*20003#Test union into bitmap hash + 3 | 20001 | 20003 + +TEST 4.18 ~ #truex#Check for defined bitmap in hash + truex + +TEST 4.19 ~ #falsex#Check for undefined bitmap in hash + falsex + +PREP + 1 + +TEST 4.20 = #t#Check a bit in the deserialised bitmap hash + t + +TEST 4.21 ~ #1 *| *20003 *| *20003#Count those bits + 1 | 20001 | 20001 + +- +- ...test set 5: control functions... +PREP IGNORE +- Loading the veil extension... +TEST 5.1 ~ #ERROR.*veil_init#Default init +ERROR: default veil version of veil_init() has been called +HINT: You must define your own version of this function. +CONTEXT: SQL statement "select veil_init(FALSE)" +PREP +TEST 5.2 = #10#Demo init + 10 + +PREP +TEST 5.3 = #t#Reset once +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t + +TEST 5.4 = #1#Check variables after reset + 1 + +TEST 5.5 = #t#Reset again +ref + + t + +TEST 3.15 ~ #1 *| *20003 *| *20003#Check bits in shared bitmap array + 1 | 20003 | 20003 +MATCH ~ "1 | 20003 | 20003" + +PASS +END TEST 3.15 + + 1 + +TEST 3.16 ~ #10001.*10002#Check array range after de-serialisation + 10001 | 10002 +MATCH ~ "10001 | 10002" + +PASS +END TEST 3.16 + +TEST 3.17 ~ #20001.*20070#Check bitmaps range after de-serialisation + 20001 | 20070 +MATCH ~ "20001 | 20070" + +PASS +END TEST 3.17 + +TEST 3.18 ~ #1 *\| *20003 *\| *20003#Check bits in array after de-ser. + 1 | 20003 | 20003 +MATCH ~ "1 | 20003 | 20003" + +PASS +END TEST 3.18 + + 70 + +TEST 4.1 = #t#Create session bitmap hash + t +MATCH = "t" + +PASS +END TEST 4.1 + +TEST 4.2 = #f#Check for known false + f +MATCH = "f" + +PASS +END TEST 4.2 + +TEST 4.3 = #t#Set a bit + t +MATCH = "t" + +PASS +END TEST 4.3 + +TEST 4.4 = #t#Check that it is now true + t +MATCH = "t" + +PASS +END TEST 4.4 + +TEST 4.5 = #t#Clear the hash + t +MATCH = "t" + +PASS +END TEST 4.5 + +TEST 4.6 = #f#Check that bit again + f +MATCH = "f" + +PASS +END TEST 4.6 + + f + +TEST 4.7 ~ #ERROR.*illegal#Attempt to create shared bitmap hash +ERROR: illegal attempt to define shared BitmapRef shared_bitmap_ref +MATCH ~ "ERROR: illegal attempt to define shared BitmapRef shared_bitmap_ref" +HINT: BitmapRefs may only be defined as session, not shared, variables. +PASS +END TEST 4.7 + +TEST 4.8 ~ #session_bitmap_refx#Get bitmap ref from bitmap hash + session_bitmap_refx +MATCH ~ "session_bitmap_refx" + +PASS +END TEST 4.8 + +TEST 4.9 ~ #ERROR.*not.*defined#Check ref in transaction +ERROR: BitmapRef session_bitmap_ref is not defined +MATCH ~ "ERROR: BitmapRef session_bitmap_ref is not defined" +HINT: Perhaps the name is mis-spelled, or its definition is missing from veil_init(). +PASS +END TEST 4.9 + + session_bitmap_ref + + t | t + +TEST 4.10 ~ #2 *| *20001 *| *20003#Test bits after setting them + 2 | 20001 | 20003 +MATCH ~ "2 | 20001 | 20003" + +PASS +END TEST 4.10 + + session_bitmap_ref + + t + +TEST 4.11 ~ #2 *| *20001 *| *20003#Union and then test bits +WARNING: there is already a transaction in progress + 2 | 20001 | 20003 +MATCH ~ "2 | 20001 | 20003" + +PASS +END TEST 4.11 + + t + + t + + t + +TEST 4.12 ~ #1 *| *20003 *| *20003#Intersect and test bits + 1 | 20003 | 20003 +MATCH ~ "1 | 20003 | 20003" + +PASS +END TEST 4.12 + +TEST 4.13 ~ #wibble#Test bitmap hash entry (a) + wibble +MATCH ~ "wibble" + rubble + +PASS +END TEST 4.13 + +TEST 4.14 ~ #rubble#Test bitmap hash entry (b) + wibble + rubble +MATCH ~ "rubble" + +PASS +END TEST 4.14 + +TEST 4.15 ~ #20001.*20070#Test range of bitmaps in hash + 20001 | 20070 +MATCH ~ "20001 | 20070" + +PASS +END TEST 4.15 + + f + +TEST 4.16 ~ #ERROR.*illegal.*BitmapHash#Attempt to create shared bitmap hash +ERROR: illegal attempt to define shared BitmapHash shared_role_privs2 +MATCH ~ "ERROR: illegal attempt to define shared BitmapHash shared_role_privs2" +HINT: BitmapHashes may only be defined as session, not shared, variables. +PASS +END TEST 4.16 + + t + + t + +TEST 4.17 ~ #3.*20001.*20003#Test union into bitmap hash + 3 | 20001 | 20003 +MATCH ~ "3 | 20001 | 20003" + +PASS +END TEST 4.17 + +TEST 4.18 ~ #truex#Check for defined bitmap in hash + truex +MATCH ~ "truex" + +PASS +END TEST 4.18 + +TEST 4.19 ~ #falsex#Check for undefined bitmap in hash + falsex +MATCH ~ "falsex" + +PASS +END TEST 4.19 + + 1 + +TEST 4.20 = #t#Check a bit in the deserialised bitmap hash + t +MATCH = "t" + +PASS +END TEST 4.20 + +TEST 4.21 ~ #1 *| *20003 *| *20003#Count those bits + 1 | 20001 | 20001 +MATCH ~ "1 | 20001 | 20001" + +PASS +END TEST 4.21 + +TEST 5.1 ~ #ERROR.*veil_init#Default init +ERROR: default veil version of veil_init() has been called +MATCH ~ "ERROR: default veil version of veil_init() has been called" +HINT: You must define your own version of this function. +CONTEXT: SQL statement "select veil_init(FALSE)" +PASS +END TEST 5.1 + +TEST 5.2 = #10#Demo init + 10 +MATCH = "10" + +PASS +END TEST 5.2 + +TEST 5.3 = #t#Reset once +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t +MATCH = "t" + +PASS +END TEST 5.3 + +TEST 5.4 = #1#Check variables after reset + 1 +MATCH = "1" + +PNOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t + +TEST 5.6 = #2#Check variables again + 2 + +PREP IGNORE +- Loading the veil extension... +TEST 5.7 ~ #ERROR.*veil_init#Reset with failing veil_init +ERROR: default veil version of veil_init() has been called +HINT: You must define your own version of this function. +CONTEXT: SQL statement "select veil_init(TRUE)" +TEST 5.8 ~ #^ *f *$#Failing reset (context switch was left incomplete) +WARNING: failed to perform reset +DETAIL: Unable to prepare for memory reset. Maybe another process is performing a reset, or maybe there is a long-running transaction that is still using the previous memory context. + f + +TEST 5.9 ~ #WARNING.*reset#Again (looking for WARNING message) +WARNING: failed to perform reset +DETAIL: Unable to prepare for memory reset. Maybe another process is performing a reset, or maybe there is a long-running transaction that is still using the previous memory context. + f + +TEST 5.10 ~ #^ *t *$#Force reset + t + +PREP +TEST 5.11 = #t#Reset again +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t + +PREP + f + + 100 + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + +TEST 5.12 = #25#Original bitmap before reset from other session + 25 + +- +- ...test set 5a: reset from second session (second process)... +TEST 5.13 = #25#Bitmap variable from other session with bits + 25 + +TEST 5.14 = #t#Successful reset + 25 + +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t + +TEST 5.15 ~ #ERROR.*Undefined#No bitmap variable from other session + f + +ERROR: type mismatch in bitmap_x: expected Bitmap, got Undefined +DETAIL: Variable bitmap_x is not of the expected type. +TEST 5.16 = #f#Failed reset as session still in use +WARNING: failed to perform reset +DETAIL: Unable to prepare for memory reset. Maybe another process is performing a reset, or maybe there is a long-running transaction that is still using the previous memory context. + f + +TEST 5.17 ~ #WARNING.*reset#Failed reset as session still in use (again) +WARNING: failed to perform reset +DETAIL: Unable to prepare for memory reset. Maybe another process is performing a reset, or maybe there is a long-running transaction that is still using the previous memory context. + f + +PREP + 100 + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + +- ...(back from 5a)... +TEST 5.19 = #25#Original Bitmap (from original transaction) + 25 + + VEIL_SHMEMCTL | ShmemCtl | t + bitmap_x | Bitmap | t + range_x | Range | f + +TEST 5.20 = #15#New bitmap created by other session + 15 + +- +- ...test set 5b: (like 5a but with opposite contexts)... +TEST 5.21 = #15#Bitmap variable from other session with bits + 15 + +TEST 5.22 = #t#Successful reset + 15 + +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t + +TEST 5.23 ~ #ERROR.*Undefined#No bitmap variable from other session + f + +ERROR: type mismatch in bitmap_x: expected Bitmap, got Undefined +DETAIL: Variable bitmap_x is not of the expected type. +TEST 5.24 = #f#Failed reset as session still in use +WARNING: failed to perform reset +DETAIL: Unable to prepare for memory reset. Maybe another process is performing a reset, or maybe there is a long-running transaction that is still using the previous memory context. +ASS +END TEST 5.4 + +TEST 5.5 = #t#Reset again +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t +MATCH = "t" + +PASS +END TEST 5.5 + +TEST 5.6 = #2#Check variables again + 2 +MATCH = "2" + +PASS +END TEST 5.6 + +TEST 5.7 ~ #ERROR.*veil_init#Reset with failing veil_init +ERROR: default veil version of veil_init() has been called +MATCH ~ "ERROR: default veil version of veil_init() has been called" +HINT: You must define your own version of this function. +CONTEXT: SQL statement "select veil_init(TRUE)" +PASS +END TEST 5.7 + +TEST 5.8 ~ #^ *f *$#Failing reset (context switch was left incomplete) +WARNING: failed to perform reset +DETAIL: Unable to prepare for memory reset. Maybe another process is performing a reset, or maybe there is a long-running transaction that is still using the previous memory context. + f +MATCH ~ "f" + +PASS +END TEST 5.8 + +TEST 5.9 ~ #WARNING.*reset#Again (looking for WARNING message) +WARNING: failed to perform reset +MATCH ~ "WARNING: failed to perform reset" +DETAIL: Unable to prepare for memory reset. Maybe another process is performing a reset, or maybe there is a long-running transaction that is still using the previous memory context. + f + +PASS +END TEST 5.9 + +TEST 5.10 ~ #^ *t *$#Force reset + t +MATCH ~ "t" + +PASS +END TEST 5.10 + +TEST 5.11 = #t#Reset again +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t +MATCH = "t" + +PASS +END TEST 5.11 + + f + + 100 + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + +TEST 5.12 = #25#Original bitmap before reset from other session + 25 +MATCH = "25" + +PASS +END TEST 5.12 + +TEST 5.13 = #25#Bitmap variable from other session with bits + 25 +MATCH = "25" + +PASS +END TEST 5.13 + +TEST 5.14 = #t#Successful reset + 25 + +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t +MATCH = "t" + +PASS +END TEST 5.14 + +TEST 5.15 ~ #ERROR.*Undefined#No bitmap variable from other session + f + +ERROR: type mismatch in bitmap_x: expected Bitmap, got Undefined +MATCH ~ "ERROR: type mismatch in bitmap_x: expected Bitmap, got Undefined" +DETAIL: Variable bitmap_x is not of the expected type. +PASS +END TEST 5.15 + +TEST 5.16 = #f#Failed reset as session still in use +WARNING: failed to perform reset +DETAIL: Unable to prepare for memory reset. Maybe another process is performing a reset, or maybe there is a long-running transaction that is still using the previous memory context. + f +MATCH = "f" + +PASS +END TEST 5.16 + +TEST 5.17 ~ #WARNING.*reset#Failed reset as session still in use (again) +WARNING: failed to perform reset +MATCH ~ "WARNING: failed to perform reset" +DETAIL: Unable to prepare for memory reset. Maybe another process is performing a reset, or maybe there is a long-running transaction that is still using the previous memory context. + f + +PASS +END TEST 5.17 + + 100 + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + +TEST 5.19 = #25#Original Bitmap (from original transaction) + 25 +MATCH = "25" + + VEIL_SHMEMCTL | ShmemCtl | t + bitmap_x | Bitmap | t + range_x | Range | f + +PASS +END TEST 5.19 + +TEST 5.20 = #15#New bitmap created by other session + 15 +MATCH = "15" + +PASS +END TEST 5.20 + +TEST 5.21 = #15#Bitmap variable from other session with bits + 15 +MATCH = "15" + +PASS +END TEST 5.21 + +TEST 5.22 = #t#Successful reset + 15 + +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t +MATCH = "t" + +PASS +END TEST 5.22 + +TEST 5.23 ~ #ERROR.*Undefined#No bitmap variable from other session + f + +ERROR: type mismatch in bitmap_x: expected Bitmap, got Undefined +MATCH ~ "ERROR: type mismatch in bitmap_x: expected Bitmap, got Undefined" +DETAIL: Variable bitmap_x is not of the expected type. +PASS +END TEST 5.23 + +TEST 5.24 = #f#Failed reset as session still in use +WARNING: failed to perform reset +DETAIL: Unable to prepare for memo f + +TEST 5.25 ~ #WARNING.*reset#Failed reset as session still in use (again) +WARNING: failed to perform reset +DETAIL: Unable to prepare for memory reset. Maybe another process is performing a reset, or maybe there is a long-running transaction that is still using the previous memory context. + f + +PREP + 100 + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + +- ...(back from 5b)... +TEST 5.98 = #15#Original Bitmap (from original transaction) + 15 + + bitmap_x | Bitmap | t + range_x | Range | f + +TEST 5.99 = #24#New bitmap created by other session + 24 + +- +- ...test set 6: veil_init() doing something useful... +PREP IGNORE +- Loading the veil extension... +TEST 6.1 ~ #ERROR.*veil_init#Default init +ERROR: default veil version of veil_init() has been called +HINT: You must define your own version of this function. +CONTEXT: SQL statement "select veil_init(FALSE)" +PREP IGNORE + t + +TEST 6.2 ~ #shared2.*Undefined.*t#Undefined shared variable + VEIL_SHMEMCTL | ShmemCtl | t + shared1 | Int4 | t + shared2 | Undefined | t + bitmap_x | Bitmap | t + +TEST 6.3 ~ #shared1.*Int4.*t#Defined shared variable + VEIL_SHMEMCTL | ShmemCtl | t + shared1 | Int4 | t + shared2 | Undefined | t + bitmap_x | Bitmap | t + +PREP IGNORE +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t + +TEST 6.4 ~ #shared2.*Undefined.*t#Undefined shared variable + shared1 | Int4 | t + shared2 | Undefined | t + +TEST 6.5 ~ #shared1.*Int4.*t#Defined shared variable + shared1 | Int4 | t + shared2 | Undefined | t + +- +- ...test set 7: veil_init() triggering SPI bug... +PREP IGNORE +ERROR: database "regressdb2" does not exist +- Loading the veil extension... +NOTICE: extension "veil" does not exist, skipping +ERROR: language "plpgsql" already exists +TEST 7.1 = #9#Test for SPI bug + 9 + +COMPLETE +ry reset. Maybe another process is performing a reset, or maybe there is a long-running transaction that is still using the previous memory context. + f +MATCH = "f" + +PASS +END TEST 5.24 + +TEST 5.25 ~ #WARNING.*reset#Failed reset as session still in use (again) +WARNING: failed to perform reset +MATCH ~ "WARNING: failed to perform reset" +DETAIL: Unable to prepare for memory reset. Maybe another process is performing a reset, or maybe there is a long-running transaction that is still using the previous memory context. + f + +PASS +END TEST 5.25 + + 100 + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + + t + +TEST 5.98 = #15#Original Bitmap (from original transaction) + 15 +MATCH = "15" + + bitmap_x | Bitmap | t + range_x | Range | f + +PASS +END TEST 5.98 + +TEST 5.99 = #24#New bitmap created by other session + 24 +MATCH = "24" + +PASS +END TEST 5.99 + +TEST 6.1 ~ #ERROR.*veil_init#Default init +ERROR: default veil version of veil_init() has been called +MATCH ~ "ERROR: default veil version of veil_init() has been called" +HINT: You must define your own version of this function. +CONTEXT: SQL statement "select veil_init(FALSE)" +PASS +END TEST 6.1 + + t + +TEST 6.2 ~ #shared2.*Undefined.*t#Undefined shared variable + VEIL_SHMEMCTL | ShmemCtl | t + shared1 | Int4 | t + shared2 | Undefined | t +MATCH ~ "shared2 | Undefined | t" + bitmap_x | Bitmap | t + +PASS +END TEST 6.2 + +TEST 6.3 ~ #shared1.*Int4.*t#Defined shared variable + VEIL_SHMEMCTL | ShmemCtl | t + shared1 | Int4 | t +MATCH ~ "shared1 | Int4 | t" + shared2 | Undefined | t + bitmap_x | Bitmap | t + +PASS +END TEST 6.3 + +NOTICE: veil_init returns true to veil_perform_reset +NOTICE: vl_complete_context_switch returns true to veil_perform_reset + t + +TEST 6.4 ~ #shared2.*Undefined.*t#Undefined shared variable + shared1 | Int4 | t + shared2 | Undefined | t +MATCH ~ "shared2 | Undefined | t" + +PASS +END TEST 6.4 + +TEST 6.5 ~ #shared1.*Int4.*t#Defined shared variable + shared1 | Int4 | t +MATCH ~ "shared1 | Int4 | t" + shared2 | Undefined | t + +PASS +END TEST 6.5 + +ERROR: database "regressdb2" does not exist +NOTICE: extension "veil" does not exist, skipping +ERROR: language "plpgsql" already exists +TEST 7.1 = #9#Test for SPI bug + 9 +MATCH = "9" + +COMPLETE +PASS +END TEST 7.1 + + +tests: 117 +passed: 117 +failed: 0 + +- Dropping regressdb... +- Dropping regressdb... +dropdb: database removal failed: ERROR: database "regressdb" does not exist +- Dropping regressdb... +dropdb: database removal failed: ERROR: database "regressdb" does not exist +- Dropping regressdb... +dropdb: database removal failed: ERROR: database "regressdb" does not exist +- Dropping regressdb... +dropdb: database removal failed: ERROR: database "regressdb" does not exist diff --git a/regress/regress.sh b/regress/regress.sh new file mode 100755 index 0000000..124be39 --- /dev/null +++ b/regress/regress.sh @@ -0,0 +1,1195 @@ +#! /bin/sh +# +# regress.sh +# +# Regression test script for Veil +# +# Copyright (c) 2005-2011 Marc Munro +# Author: Marc Munro +# License: BSD +# + +bail() +{ + echo "Failed while $*. Bailing out..." 1>&2 + exit 2 +} + +db_create() +{ + version=`psql --version | grep '[0-9]\.[0-9]'` + version=`echo ${version} | sed -e 's/[^0-9]*\([0-9]*\.[0-9]*\).*/\1/'` + major=`echo ${version} | cut -d. -f1` + minor=`echo ${version} | cut -d. -f2` + + psql -d template1 <&1 <&1 | psql_clean regress.log +} + +do_test() +{ + psql -q -t -d regressdb < ## +# testno is a string identifying the test (eg 1.4) +# condition is one of "=", "!=", "~", "!~", specifying exact string match, +# exact string mismatch, regular expression match and regular +# expression mismatch respectively +# is a string or regexp that must appear +# or not in the test output for the test to pass. +# is a short phrase describing the test +# +regress_1() +{ + echo - + echo "- ...test set 1: variables..." + do_test 1 <&1 | psql_collate regress.log; STATUS=$? + if [ "x$3" = "xdrop" ]; then + db_drop + fi + fi +} + +usage() +{ + prog=`basename $0` + echo " +Usage: ${prog} -h + ${prog} -c + ${prog} -d + ${prog} [-r] -b + ${prog} [-r] -t ... + ${prog} [-r] -T ... + ${prog} + +With no parameters, ${prog} creates a test database, runs all tests, +and finally drops the database. If tests fail, the database will not be +automatically dropped. + +${prog} expects to be able to create a database locally called regressdb +using the psql command. This must be available in \$PATH. + +All tests are run using psql, with the output being parsed and tidied +for human consumption. The raw output is available in regress.log in +case the parser eats something important. + +The -h option prints this usage message. +-b causes the test database to just be created +-c runs the preload library check and reports on status +-d causes the test database to be dropped +-t runs specified test sets against an already created database +-T runs specified test sets against an already created database with +no parsing and tidying of psql output. + +If you cannot create regressdb locally, you are going to have to hack the +script. Sorry. +" + +} + +# Return 0 if eq, 1 if $1 > $2, 2 if $2 > $1 +simple_cmp() +{ + if [ $1 -eq $2 ]; then return 0; fi + if [ $1 -gt $2 ]; then return 1; fi + return 2 +} + +# Return 0 if eq, 1 if $1 > $2, 2 if $2 > $1 +version_cmp() { + version1=$1 + version2=$2 + major1=`echo ${version1} | cut -d. -f1` + minor1=`echo ${version1} | cut -d. -f2` + major2=`echo ${version2} | cut -d. -f1` + minor2=`echo ${version2} | cut -d. -f2` + simple_cmp $major1 $major2 && simple_cmp $minor1 $minor2 + return $? +} + + +# Check whether shared_preload_libraries has been defined appropriately. +# Returns the following result codes: +# 0 - shared_preload_libraries is defined correctly, or we are using +# a version of postgres prior to 8.2 so no warnings are necessary +# and we can use veil_interface.sql +# 1 - shared_preload_libraries has not been defined +# 2 - shared_preload_libraries does not contain veil shared library +# 3 - shared_preload_libraries references old (or missing) version of +# veil shared library +# +check_preload_libraries() +{ + version=`psql --version | grep '[0-9]\.[0-9]'` + version=`echo ${version} | sed -e 's/[^0-9]*\([0-9]*\.[0-9]*\).*/\1/'` + # This is version 8.2 or higher, so we can expect some cooperation + # from postgres wrt shared memory. + # To get this cooperation the veil shared library must be preloaded + # so, test whether this is being done. + mylib=`dirname $0`/../veil.so + setting=`psql -d template1 -q -t </dev/null + if [ $? -ne 0 ]; then + # Definition of shared_preload_libraries does not contain veil + return 2 + else + echo "${libs}" | while read a; do + cmp ${a} ${mylib} 2>/dev/null && echo FOUND + done | grep FOUND >/dev/null; status=$? + if [ ${status} -ne 0 ]; then + # Definition of shared_preload_libraries has different + # version of veil library from that built locally + return 3 + fi + fi + fi +} + +preload_library_message() +{ + if [ "x$1" = "x1" -o "x$1" = "x2" ]; then + echo " +WARNING: The veil shared library is not defined in shared_preload_libraries +(which is defined in postgresql.conf) Without this definition Veil will not run +" + elif [ "x$1" = "x3" ]; then + echo " +WARNING: The version of veil.so in shared_preload_libraries (defined in +postgresql.conf) is not up to date (not the same as the latest build). +" + fi +} + +# Load helper functions +export dir=`dirname $0` +. ${dir}/../tools/psql_funcs.sh + +owner=`whoami` + +export STATUS=0 + +if [ "x$1" = "x-h" ]; then + usage; exit +elif [ "x$1" = "x-c" ]; then + # Check and report on the preload library situation + check_preload_libraries; preload_status=$? + rm -f regress.log + preload_library_message ${preload_status} +elif [ "x$1" = "x-d" ]; then + # Drop the database + db_drop +elif [ "x$1" = "x-b" ]; then + # Just build the database + check_preload_libraries; preload_status=$? + rm -f regress.log + db_test ${owner} ${preload_status} + preload_library_message ${preload_status} +elif [ "x$1" = "x-n" ]; then + # Don't drop the database after running the tests + rm -f regress.log + check_preload_libraries; preload_status=$? + db_test ${owner} test + preload_library_message ${preload_status} +elif [ "x$1" = "x-t" ]; then + # Run specific tests. No build or drop. + ( + while [ "x$2" != "x" ]; do + regress_$2 2>&1 + shift + done + tests_done + ) | psql_collate regress.log + check_preload_libraries; preload_status=$? + preload_library_message ${preload_status} +elif [ "x$1" = "x-T" ]; then + # Run specific tests in raw form (no parser). No build or drop. + while [ "x$2" != "x" ]; do + regress_$2 2>&1 + shift + done +else + # Build, run all tests and drop. + rm -f regress.log + check_preload_libraries; preload_status=$? + if [ ${preload_status} -gt 1 ]; then + preload_library_message ${preload_status} + echo "ERROR: Refusing to run tests as shared_preload_library is defined +but does not reference an up to date veil shared library." 1>&2 + exit 5 + fi + db_test ${owner} test drop + preload_library_message ${preload_status} +fi + +exit ${STATUS} \ No newline at end of file diff --git a/regress/regress_data.sql b/regress/regress_data.sql new file mode 100644 index 0000000..cd0b3b8 --- /dev/null +++ b/regress/regress_data.sql @@ -0,0 +1,51 @@ +------------ +-- regress_data.sql +-- +-- Data creation script for Veil regression tests +-- +-- Copyright (c) 2005 - 2011 Marc Munro +-- Author: Marc Munro +-- License: BSD +-- +-- Data for regression tests + +\echo - Populating tables... +begin; + +insert into roles + (role_id, role_name) +values (10001, 'DBA'); + +insert into roles + (role_id, role_name) +values (10002, 'GRUNT'); + +insert into privileges + (privilege_id, privilege_name) +values (20001, 'select_x'); + +insert into privileges + (privilege_id, privilege_name) +values (20002, 'select_y'); + +insert into privileges + (privilege_id, privilege_name) +values (20070, 'select_z'); + +insert into role_privileges + (role_id, privilege_id) +values (10001, 20001); + +insert into role_privileges + (role_id, privilege_id) +values (10001, 20002); + +insert into role_privileges + (role_id, privilege_id) +values (10001, 20070); + +insert into role_privileges + (role_id, privilege_id) +values (10002, 20001); + +commit; \ No newline at end of file diff --git a/regress/regress_tables.sql b/regress/regress_tables.sql new file mode 100644 index 0000000..7f5ab45 --- /dev/null +++ b/regress/regress_tables.sql @@ -0,0 +1,46 @@ +------------ +-- regress_tables.sql +-- +-- Table creation script for Veil regression tests. +-- +-- Copyright (c) 2005 - 2011 Marc Munro +-- Author: Marc Munro +-- License: BSD +-- +------------ +-- Tables for regression tests + +\echo - Creating tables... +\echo - ...privileges... +create table privileges ( + privilege_id integer not null, + privilege_name varchar(80) not null +); +alter table privileges add constraint privilege__pk + primary key(privilege_id); + +\echo - ...role... +create table roles ( + role_id integer not null, + role_name varchar(80) not null +); +alter table roles add constraint role__pk + primary key(role_id); + +\echo - ...role_privileges... +create table role_privileges ( + role_id integer not null, + privilege_id integer not null +); + +alter table role_privileges add constraint role_privilege__pk + primary key(role_id, privilege_id); + +alter table role_privileges add constraint role_privilege__role_fk + foreign key(role_id) + references roles(role_id); + +alter table role_privileges add constraint role_privilege__priv_fk + foreign key(privilege_id) + references privileges(privilege_id); + diff --git a/src/GNUmakefile b/src/GNUmakefile new file mode 100644 index 0000000..bd5dd5c --- /dev/null +++ b/src/GNUmakefile @@ -0,0 +1,14 @@ +# ---------- +# GNUmakefile +# +# Copyright (c) 2005 - 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# +# ---------- +# + +all: + +%:: + cd ..; $(MAKE) MAKEFLAGS="$(MAKEFLAGS)" $@ \ No newline at end of file diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..6865a95 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,43 @@ +# Makefile +# +# Makefile for src directory of Veil +# +# Copyright (c) 2005 - 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# +# Do not attempt to use this makefile explicitly: its targets are available +# and should be built from the main GNUmakefile in the parent directory. +# The GNUmakefile in this directory will build using the parent GNUmakefile +# so using make in this directory will work as long as you don't +# try to specify this makefile. It even works with emacs compile and +# next-error functions though the number of makefiles involved seems a +# little alarming at first. +# The whole strangeness of this makefile hierarchy derives from a, +# possibly misguided, attempt to avoid recursive make (see the article +# "Recursive make considered harmful" for a rationale). + + +SOURCES = src/veil_bitmap.c src/veil_config.c src/veil_datatypes.c \ + src/veil_interface.c src/veil_mainpage.c src/veil_query.c \ + src/veil_serialise.c src/veil_shmem.c src/veil_utils.c \ + src/veil_variables.c + +ifdef EXTENSION + LIBDIR=$(DESTDIR)$(datadir)/extension +else + LIBDIR=$(DESTDIR)$(pkglibdir) +endif + +INSTALLED_LIB = $(LIBDIR)/$(addsuffix $(DLSUFFIX), veil) +TRIAL_LIB = $(BUILD_DIR)/$(addsuffix $(DLSUFFIX), veil_trial) +VEIL_LIB = $(BUILD_DIR)/$(addsuffix $(DLSUFFIX), veil) +HEADERS = $(wildcard src/*.h) +SRC_CLEAN = $(VEIL_LIB) + +all: $(VEIL_CONTROL) + +$(VEIL_CONTROL): src/veil_interface.sqs + @echo Creating $(VEIL_CONTROL) + @sed -e 's!@LIBPATH@!$$libdir/veil!g' <$< >$(VEIL_CONTROL) + diff --git a/src/veil_bitmap.c b/src/veil_bitmap.c new file mode 100644 index 0000000..483122e --- /dev/null +++ b/src/veil_bitmap.c @@ -0,0 +1,605 @@ +/** + * @file veil_bitmap.c + * \code + * Author: Marc Munro + * Copyright (c) 2005 - 2011 Marc Munro + * License: BSD + * + * \endcode + * @brief + * Functions for manipulating Bitmaps, BitmapHashes and BitmapArrays + * + */ + +#include +#include "postgres.h" +#include "veil_datatypes.h" +#include "veil_funcs.h" + + + +/** + * Array of bit positions for int32, indexed by bitno. + */ +static +uint32 bitmasks[] = {0x00000001, 0x00000002, 0x00000004, 0x00000008, + 0x00000010, 0x00000020, 0x00000040, 0x00000080, + 0x00000100, 0x00000200, 0x00000400, 0x00000800, + 0x00001000, 0x00002000, 0x00004000, 0x00008000, + 0x00010000, 0x00020000, 0x00040000, 0x00080000, + 0x00100000, 0x00200000, 0x00400000, 0x00800000, + 0x01000000, 0x02000000, 0x04000000, 0x08000000, + 0x10000000, 0x20000000, 0x40000000, 0x80000000}; + + +/** + * Clear all bits in a ::Bitmap. + * + * @param bitmap The ::Bitmap in which all bits are to be cleared + */ +void +vl_ClearBitmap(Bitmap *bitmap) +{ + int elems = ARRAYELEMS(bitmap->bitzero, bitmap->bitmax); + int i; + + for (i = 0; i < elems; i++) { + bitmap->bitset[i] = 0; + } +} + +/** + * Return a newly initialised (empty) ::Bitmap. The bitmap may already + * exist in which case it will be re-used if possible. The bitmap may + * be created in either session or shared memory depending on the value + * of shared. + * + * @param p_bitmap Pointer to an existing bitmap if one exists + * @param shared Whether to create the bitmap in shared memory + * @param min The smallest bit to be stored in the bitmap + * @param max The largest bit to be stored in the bitmap + */ +void +vl_NewBitmap(Bitmap **p_bitmap, bool shared, + int32 min, int32 max) +{ + Bitmap *bitmap = *p_bitmap; + int elems = ARRAYELEMS(min, max); + + if (bitmap) { + int cur_elems = ARRAYELEMS(bitmap->bitzero, bitmap->bitmax); + /* OK, there is an old bitmap in place. If it is the same size + * or larger than we need we will re-use it, otherwise we will + * dispose of it and get a new one. */ + + if (elems <= cur_elems) { + vl_ClearBitmap(bitmap); + } + else { + if (shared) { + vl_free(bitmap); + } + else { + pfree(bitmap); + } + bitmap = NULL; + } + } + if (!bitmap) { + /* We need a new bitmap */ + if (shared) { + bitmap = vl_shmalloc(sizeof(Bitmap) + (sizeof(int32) * elems)); + } + else { + bitmap = vl_malloc(sizeof(Bitmap) + (sizeof(int32) * elems)); + } + } + + DBG_SET_CANARY(*bitmap); + DBG_SET_ELEMS(*bitmap, elems); + DBG_SET_TRAILER(*bitmap, bitset); + bitmap->type = OBJ_BITMAP; + bitmap->bitzero = min; + bitmap->bitmax = max; + vl_ClearBitmap(bitmap); + + DBG_TEST_CANARY(*bitmap); + DBG_TEST_TRAILER(*bitmap, bitset); + *p_bitmap = bitmap; +} + +/** + * Set a bit within a ::Bitmap. If the bit is outside of the acceptable + * range, raise an error. + * + * @param bitmap The ::Bitmap within which the bit is to be set. + * @param bit The bit to be set. + */ +void +vl_BitmapSetbit(Bitmap *bitmap, + int32 bit) +{ + int relative_bit = bit - BITZERO(bitmap->bitzero); + int element = BITSET_ELEM(relative_bit); + + if ((bit > bitmap->bitmax) || + (bit < bitmap->bitzero)) + { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Bitmap range error"), + errdetail("Bit (%d) not in range %d..%d. ", bit, + bitmap->bitzero, bitmap->bitmax))); + } + + DBG_CHECK_INDEX(*bitmap, element); + bitmap->bitset[element] |= bitmasks[BITSET_BIT(relative_bit)]; + DBG_TEST_CANARY(*bitmap); + DBG_TEST_TRAILER(*bitmap, bitset); +} + +/** + * Clear a bit within a ::Bitmap. If the bit is outside of the acceptable + * range, raise an error. + * + * @param bitmap The ::Bitmap within which the bit is to be cleared. + * @param bit The bit to be cleared. + */ +void +vl_BitmapClearbit(Bitmap *bitmap, + int32 bit) +{ + int relative_bit = bit - BITZERO(bitmap->bitzero); + int element = BITSET_ELEM(relative_bit); + + if ((bit > bitmap->bitmax) || + (bit < bitmap->bitzero)) + { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Bitmap range error"), + errdetail("Bit (%d) not in range %d..%d. ", bit, + bitmap->bitzero, bitmap->bitmax))); + } + + bitmap->bitset[element] &= ~(bitmasks[BITSET_BIT(relative_bit)]); +} + +/** + * Test a bit within a ::Bitmap. If the bit is outside of the acceptable + * range return false. + * + * @param bitmap The ::Bitmap within which the bit is to be set. + * @param bit The bit to be tested. + * + * @return True if the bit is set, false otherwise. + */ +bool +vl_BitmapTestbit(Bitmap *bitmap, + int32 bit) +{ + int relative_bit = bit - BITZERO(bitmap->bitzero); + int element = BITSET_ELEM(relative_bit); + + if ((bit > bitmap->bitmax) || + (bit < bitmap->bitzero)) + { + return false; + } + + return (bitmap->bitset[element] & bitmasks[BITSET_BIT(relative_bit)]) != 0; +} + +/** + * Create the union of two bitmaps, updating the first with the result. + * + * @param target The ::Bitmap into which the result will be placed. + * @param source The ::Bitmap to be unioned into target. + */ +void +vl_BitmapUnion(Bitmap *target, + Bitmap *source) +{ + int i; + int elems = ARRAYELEMS(target->bitzero, target->bitmax); + + /* TODO: Make this tolerant of range mismatches. */ + if ((target->bitzero != source->bitzero) || + (target->bitmax != source->bitmax)) + { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Incompatible bitmaps"), + errdetail("Target range is %d..%d. Source range %d..%d.", + target->bitzero, target->bitmax, + source->bitzero, source->bitmax))); + } + for (i = 0; i < elems; i++) { + target->bitset[i] |= source->bitset[i]; + } +} + +/** + * Create the intersection of two bitmaps, updating the first with the + * result. + * + * @param target The ::Bitmap into which the result will be placed. + * @param source The ::Bitmap to be intersected into target. + */ +void +vl_BitmapIntersect(Bitmap *target, + Bitmap *source) +{ + int i; + int elems = ARRAYELEMS(target->bitzero, target->bitmax); + + /* TODO: Make this tolerant of range mismatches. */ + if ((target->bitzero != source->bitzero) || + (target->bitmax != source->bitmax)) + { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Incompatible bitmaps"), + errdetail("Target range is %d..%d. Source range %d..%d.", + target->bitzero, target->bitmax, + source->bitzero, source->bitmax))); + } + for (i = 0; i < elems; i++) { + target->bitset[i] &= source->bitset[i]; + } +} + +/** + * Return the next set bit in the ::Bitmap. + * + * @param bitmap The ::Bitmap being scanned. + * @param bit The starting bit from which to scan the bitmap + * @param found Boolean that will be set to true when a set bit has been + * found. + * + * @return The bit id of the found bit, or zero if no set bits were found. + */ +int32 +vl_BitmapNextBit(Bitmap *bitmap, + int32 bit, + bool *found) +{ + while (bit <= bitmap->bitmax) { + if (vl_BitmapTestbit(bitmap, bit)) { + *found = true; + return bit; + } + bit++; + } + *found = false; + return 0; +} + +/** + * Return a specified ::Bitmap from a ::BitmapArray. + * + * @param bmarray The ::BitmapArray from which the result is to be + * returned. + * @param elem The index of the ::Bitmap within the array. + * + * @return The bitmap corresponding to the parameters, or NULL if no + *such entry exists within the array. + */ +Bitmap * +vl_BitmapFromArray(BitmapArray *bmarray, + int32 elem) +{ + DBG_TEST_CANARY(*bmarray); + DBG_TEST_TRAILER(*bmarray, bitmap); + if ((elem < bmarray->arrayzero) || (elem > bmarray->arraymax)) { + return NULL; + } + else { + DBG_CHECK_INDEX(*bmarray, elem - bmarray->arrayzero); + return bmarray->bitmap[elem - bmarray->arrayzero]; + } +} + +/** + * Clear all bitmaps in the given ::BitmapArray + * + * @param bmarray The ::BitmapArray to be cleared + */ +void +vl_ClearBitmapArray(BitmapArray *bmarray) +{ + int bitmaps = bmarray->arraymax + 1 - bmarray->arrayzero; + int i; + + DBG_TEST_CANARY(*bmarray); + DBG_TEST_TRAILER(*bmarray, bitmap); + for (i = 0; i < bitmaps; i++) { + DBG_CHECK_INDEX(*bmarray, i); + vl_ClearBitmap(bmarray->bitmap[i]); + } +} + +/** + * Return a newly initialised (empty) ::BitmapArray. It may already + * exist in which case it will be re-used if possible. It may + * be created in either session or shared memory depending on the value + * of shared. + * + * @param p_bmarray Pointer to an existing bitmap if one exists. + * @param shared Whether to create the bitmap in shared memory + * @param arrayzero The lowest array index + * @param arraymax The highest array index + * @param bitzero The smallest bit to be stored in the bitmap + * @param bitmax The largest bit to be stored in the bitmap + */ +void +vl_NewBitmapArray(BitmapArray **p_bmarray, bool shared, + int32 arrayzero, int32 arraymax, + int32 bitzero, int32 bitmax) +{ + BitmapArray *bmarray = *p_bmarray; + int bitsetelems = ARRAYELEMS(bitzero, bitmax); + int bitmaps = arraymax + 1 - arrayzero; + int i; + + if (bmarray) { + /* We already have a bitmap array. If possible, we re-use it. */ + int cur_elems = ARRAYELEMS(bmarray->bitzero, bmarray->bitmax); + int cur_maps = bmarray->arraymax + 1 - bmarray->arrayzero; + + DBG_TEST_CANARY(*bmarray); + DBG_TEST_TRAILER(*bmarray, bitmap); + if ((cur_elems >= bitsetelems) && (cur_maps >= bitmaps)) { + vl_ClearBitmapArray(bmarray); + } + else { + if (shared) { + for (i = 0; i < cur_maps; i++) { + vl_free(bmarray->bitmap[i]); + } + vl_free(bmarray); + } + else { + for (i = 0; i < cur_maps; i++) { + pfree(bmarray->bitmap[i]); + } + pfree(bmarray); + } + bmarray = NULL; + } + } + + if (!bmarray) { + if (shared) { + bmarray = vl_shmalloc(sizeof(BitmapArray) + + (sizeof(Bitmap *) * bitmaps)); + + } + else { + bmarray = vl_malloc(sizeof(BitmapArray) + + (sizeof(Bitmap *) * bitmaps)); + } + + for (i = 0; i < bitmaps; i++) { + bmarray->bitmap[i] = NULL; + vl_NewBitmap(&(bmarray->bitmap[i]), shared, bitzero, bitmax); + } + + bmarray->type = OBJ_BITMAP_ARRAY; + DBG_SET_CANARY(*bmarray); + DBG_SET_ELEMS(*bmarray, bitmaps); + DBG_SET_TRAILERP(*bmarray, bitmap); + } + bmarray->bitzero = bitzero; + bmarray->bitmax = bitmax; + bmarray->arrayzero = arrayzero; + bmarray->arraymax = arraymax; + + for (i = 0; i < bitmaps; i++) { + bmarray->bitmap[i]->type = OBJ_BITMAP; + bmarray->bitmap[i]->bitzero = bitzero; + bmarray->bitmap[i]->bitmax = bitmax; + } + + *p_bmarray = bmarray; +} + +/** + * Create a new hash table. This is allocated from session memory as + * BitmapHashes may not be declared as shared variables. + * + * @param name The name of the hash to be created. Note that we prefix + * this with "vl_" to prevent name collisions from other subsystems. + * + * @return Pointer to the newly created hash table. + */ +static HTAB * +new_hash(char *name) +{ + char vl_name[HASH_KEYLEN]; + HTAB *hash; + HASHCTL hashctl; + + (void) snprintf(vl_name, HASH_KEYLEN - 1, "vl_%s", name); + + hashctl.keysize = HASH_KEYLEN; + hashctl.entrysize = sizeof(VarEntry); + + hash = hash_create(vl_name, 200, &hashctl, HASH_ELEM); + return hash; +} + +/** + * Utility function for scanning the hash table of a BitmapHash. + * + * @param hash The hash table being scanned + * @param prev The entry from which to scan, starting with NULL. + * + * @return The next element in the hash table (a VarEntry) or NULL when + * the last element has already been scanned. + */ +VarEntry * +vl_NextHashEntry(HTAB *hash, + VarEntry *prev) +{ + VarEntry *next; + static HASH_SEQ_STATUS status; + if (!prev) { + /* Initialise the hash search */ + + hash_seq_init(&status, hash); + } + next = (VarEntry *) hash_seq_search(&status); + return (next); +} + +/** + * Return a newly initialised (empty) ::BitmapHash. It may already + * exist in which case it will be re-used if possible. BitmapHash + * variables may only be created as session (not shared) variables. + * + * @param p_bmhash Pointer to an existing bitmap if one exists. + * @param name The name to be used for the hash table + * @param bitzero The smallest bit to be stored in the bitmap + * @param bitmax The largest bit to be stored in the bitmap + */ +void +vl_NewBitmapHash(BitmapHash **p_bmhash, char *name, + int32 bitzero, int32 bitmax) +{ + BitmapHash *bmhash = *p_bmhash; + + if (bmhash) { + VarEntry *entry = NULL; + HTAB *hash = bmhash->hash; + bool found; + + while ((entry = vl_NextHashEntry(hash, entry))) { + if (entry->obj) { + if (entry->obj->type != OBJ_BITMAP) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Bitmap hash contains invalid object %d", + entry->obj->type), + errdetail("Object type is %d, expected is %d.", + entry->obj->type, OBJ_BITMAP))); + } + pfree(entry->obj); /* Free the bitmap */ + } + /* Now remove the entry from the hash */ + (void) hash_search(hash, entry->key, HASH_REMOVE, &found); + } + } + else { + bmhash = vl_malloc(sizeof(BitmapHash)); + bmhash->type = OBJ_BITMAP_HASH; + bmhash->hash = new_hash(name); + } + bmhash->bitzero = bitzero; + bmhash->bitmax = bitmax; + + *p_bmhash = bmhash; +} + +/** + * Return a specified ::Bitmap from a ::BitmapHash. Raise an error if + * the returned object from the hash search is not a bitmap. + * + * @param bmhash The ::BitmapHash from which the result is to be + * returned. + * @param hashelem The key of the ::Bitmap within the hash. + * + * @return The bitmap corresponding to the parameters, or NULL if no + *such entry exists within the hash. + */ +Bitmap * +vl_BitmapFromHash(BitmapHash *bmhash, + char *hashelem) +{ + VarEntry *var; + Bitmap *bitmap; + bool found; + + var = hash_search(bmhash->hash, hashelem, HASH_FIND, &found); + if (!found) { + return NULL; + } + + if (!var->obj) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("BitmapFromHash - empty VarEntry"))); + } + + if (var->obj->type != OBJ_BITMAP) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Bitmap hash contains invalid object %d", + var->obj->type), + errdetail("Object type is %d, expected is %d.", + var->obj->type, OBJ_BITMAP))); + } + + bitmap = (Bitmap *) var->obj; + return bitmap; +} + +/** + * Create a newly allocated empty ::Bitmap to a ::BitmapHash + * + * @param bmhash The ::BitmapHash to which to add the new ::Bitmap. + * @param hashelem The key for the new entry + * + * @return Pointer to the newly allocated empty ::Bitmap. + */ +Bitmap * +vl_AddBitmapToHash(BitmapHash *bmhash, + char *hashelem) +{ + VarEntry *var; + Bitmap *bitmap = NULL; + bool found; + + var = hash_search(bmhash->hash, hashelem, HASH_ENTER, &found); + if (found) { + if (!var->obj) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("AddBitmapToHash - empty VarEntry"))); + } + if (var->obj->type != OBJ_BITMAP) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("AddBitmapToHash - type mismatch %d", + var->obj->type), + errdetail("Object type is %d, expected is %d.", + var->obj->type, OBJ_BITMAP))); + } + return (Bitmap *) var->obj; + } + + /* We've created a new entry. Now create the bitmap for it. */ + + vl_NewBitmap(&bitmap, FALSE, bmhash->bitzero, bmhash->bitmax); + var->obj = (Object *) bitmap; + return bitmap; +} + +/** + * Determine whether the supplied key exists in the ::BitmapHash. + * + * @param bmhash The ::BitmapHash to be tested + * @param hashelem The key to be tested + * + * @return True if the key already exists in the hash. + */ +bool +vl_BitmapHashHasKey(BitmapHash *bmhash, + char *hashelem) +{ + VarEntry *var; + bool found; + + var = hash_search(bmhash->hash, hashelem, HASH_FIND, &found); + return found; +} diff --git a/src/veil_bitmap.d b/src/veil_bitmap.d new file mode 100644 index 0000000..870b553 --- /dev/null +++ b/src/veil_bitmap.d @@ -0,0 +1,4 @@ +src/veil_bitmap.o src/veil_bitmap.d: \ + src/veil_bitmap.c \ + src/veil_datatypes.h \ + src/veil_funcs.h diff --git a/src/veil_config.c b/src/veil_config.c new file mode 100644 index 0000000..d8ac770 --- /dev/null +++ b/src/veil_config.c @@ -0,0 +1,107 @@ +/** + * @file veil_config.c + * \code + * Author: Marc Munro + * Copyright (c) 2006-2011 Marc Munro + * License: BSD + * + * \endcode + * @brief + * Code for setting and reading configuration data. + * + */ + +#include "postgres.h" +#include "storage/fd.h" +#include "utils/guc.h" +#include "veil_version.h" +#include "veil_funcs.h" + + +/** + * The number of buckets to create in the hash for shared variables. + * This defaults to 32 and may be defined in postgresql.conf using eg: + * "veil.shared_hash_elems = 64" + */ +static int shared_hash_elems = 32; + +/** + * The number of databases within the db cluster that will use veil. + * Every veil-using database within the cluster will get the same + * allocation of shared memory. + */ +static int dbs_in_cluster = 2; + +/** + * The size in KBytes, of each of Veil's shared memory contexts. Veil + * will pre-allocate (from Postgres 8.2 onwards) twice this amount of + * shared memory, one for each context area. + * The default is 16384 Bytes and may be defined in postgresql.conf + * using eg: "veil.shmem_context_size = 8192" + */ +static int shmem_context_size = 16384; + +/** + * Return the number of databases, within the database cluster, that + * will use Veil. Each such database will be allocated 2 chunks of + * shared memory (of shmem_context_size), and a single LWLock. + * It defaults to 1 and may be defined in postgresql.conf using eg: + * "veil.dbs_in_cluster = 2" + */ +int +veil_dbs_in_cluster() +{ + veil_load_config(); + return dbs_in_cluster; +} + +/** + * Return the number of entries that should be allocated for shared + * variables in our shared hashes. This defaults to 32 and may be + * defined in postgresql.conf using eg: + * "veil.shared_hash_elems = 64" + */ +int +veil_shared_hash_elems() +{ + veil_load_config(); + return shared_hash_elems; +} + +/** + * Return the amount of shared memory to be requested for each of the + * two shared memory contexts. This variable has no effect unless + * shared_preload_libraries has been defined in postgresql.conf to load + * the Veil shared library + * Note that this must be large enough to allocate at least one chunk of + * memory for each veil-using database in the Postgres cluster. It + * defaults to 16K and may be defined in postgresql.conf using eg: + * "veil.shmem_context_size = 16384" + */ +int +veil_shmem_context_size() +{ + veil_load_config(); + return shmem_context_size; +} + +/** + * Retrieve Veil's GUC variables for this session. + */ +void +veil_load_config() +{ + static bool first_time = true; + if (!first_time) { + return; + } + + dbs_in_cluster = atoi(GetConfigOption("veil.dbs_in_cluster", FALSE)); + shared_hash_elems = atoi(GetConfigOption("veil.shared_hash_elems", + FALSE)); + shmem_context_size = atoi(GetConfigOption("veil.shmem_context_size", + FALSE)); + first_time = false; +} + + diff --git a/src/veil_config.d b/src/veil_config.d new file mode 100644 index 0000000..ce47906 --- /dev/null +++ b/src/veil_config.d @@ -0,0 +1,5 @@ +src/veil_config.o src/veil_config.d: \ + src/veil_config.c \ + src/veil_version.h \ + src/veil_funcs.h \ + src/veil_datatypes.h diff --git a/src/veil_datatypes.c b/src/veil_datatypes.c new file mode 100644 index 0000000..e37d94a --- /dev/null +++ b/src/veil_datatypes.c @@ -0,0 +1,66 @@ +/** + * @file veil_datatypes.c + * \code + * Author: Marc Munro + * Copyright (c) 2005 - 2011 Marc Munro + * License: BSD + * + * \endcode + * @brief + * Code for non-bitmap datatypes. + * + */ + +#include "postgres.h" +#include "veil_datatypes.h" +#include "veil_funcs.h" + + +/** + * Create a new session or shared ::Range object. + * + * @param shared Whether the object is to be created in shared (true) or + * session (false) memory. + * + * @return Pointer to newly created object. + */ +Range * +vl_NewRange(bool shared) +{ + Range *range; + + if (shared) { + range = vl_shmalloc(sizeof(Range)); + } + else { + range = vl_malloc(sizeof(Range)); + } + + range->type = OBJ_RANGE; + return range; +} + +/** + * Create a new session or shared ::Int4Var object. + * + * @param shared Whether the object is to be created in shared (true) or + * session (false) memory. + * + * @return Pointer to newly created object. + */ +Int4Var * +vl_NewInt4(bool shared) +{ + Int4Var *i4v; + + if (shared) { + i4v = vl_shmalloc(sizeof(Int4Var)); + } + else { + i4v = vl_malloc(sizeof(Int4Var)); + } + i4v->type = OBJ_INT4; + i4v->isnull = true; + return i4v; +} + diff --git a/src/veil_datatypes.d b/src/veil_datatypes.d new file mode 100644 index 0000000..2090d11 --- /dev/null +++ b/src/veil_datatypes.d @@ -0,0 +1,4 @@ +src/veil_datatypes.o src/veil_datatypes.d: \ + src/veil_datatypes.c \ + src/veil_datatypes.h \ + src/veil_funcs.h diff --git a/src/veil_datatypes.h b/src/veil_datatypes.h new file mode 100644 index 0000000..a37031e --- /dev/null +++ b/src/veil_datatypes.h @@ -0,0 +1,387 @@ +/** + * @file veil_datatypes.h + * \code + * Author: Marc Munro + * Copyright (c) 2005 - 2011 Marc Munro + * License: BSD + * + * \endcode + * @brief + * Define all Veil public datatypes + * + */ + +#ifndef VEIL_DATATYPES +/** + * Prevent this header from being included multiple times. + */ +#define VEIL_DATATYPES 1 + +#ifndef VEIL_DEBUG +/** + * Enables various debugging constructs, such as canaries, in code and + * data structures. If such debugging is required, define VEIL_DEBUG in + * the make invocation, eg: "make VEIL_DEBUG=1". + */ +// TODO: REMOVE THE FOLLOWING DEFINITION +#define VEIL_DEBUG 1 +#endif + +#if VEIL_DEBUG == 1 +/** + * Value to be set in sacrificial "canary" fields. If this value is not + * as expected, the canary has been killed by something inappropriately + * stomping on memory. + */ +#define DBG_CANARY 0xca96ca96 + +/** + * Defines a canary element in a data structure. + */ +#define DBG_CANARY_ENTRY int32 canary; + +/** + * Field to record the size of an array so that its canary element can + * be found. + */ +#define DBG_ELEMS_ENTRY int32 dbgelems; + +/** + * Code to records the size of an array so that its canary element can + * be found. + */ +#define DBG_SET_ELEMS(x,y) (x).dbgelems = y + +/** + * Code to initialise a canary. + */ +#define DBG_SET_CANARY(x) (x).canary = DBG_CANARY + +/** + * Code to test for a canary having been overwritten. + */ +#define DBG_TEST_CANARY(x) if ((x).canary != DBG_CANARY) {\ + elog(ERROR, "canary fault"); } + +/** + * Base size for an array containing a canary. This is zero if + * VEIL_DEBUG is not defined, when this is defined arrays will be one + * element longer to allow a canary to be placed at the end of the array. + */ +#define EMPTY 1 + +/** + * Set a trailing canary in an array. + */ +#define DBG_SET_TRAILER(x, y) (x).y[(x).dbgelems] = DBG_CANARY; +/** + * Set a trailing canary in an array (using a void pointer). + */ +#define DBG_SET_TRAILERP(x, y) (x).y[(x).dbgelems] = (void *) DBG_CANARY; + +/** + * Test the trailing canary in an array. + */ +#define DBG_TEST_TRAILER(x, y) \ + if ((int) (x).y[(x).dbgelems] != DBG_CANARY) { \ + elog(ERROR, "trailing canary fault"); } + +/** + * Check whether an array index is in bounds. + */ +#define DBG_CHECK_INDEX(x,i) if (i >= (x).dbgelems) {\ + elog(ERROR, "Element index out of range %d", i); } + +#else +#define DBG_CANARY_ENTRY +#define DBG_ELEMS_ENTRY +#define DBG_SET_ELEMS(x,y) +#define DBG_SET_CANARY(x) +#define DBG_TEST_CANARY(x) +#define EMPTY 0 +#define DBG_SET_TRAILER(x,Y) +#define DBG_TEST_TRAILER(x,Y) +#define DBG_CHECK_INDEX(x,i) +#endif + +#include "utils/hsearch.h" +#include "storage/lwlock.h" + +/** + * Chunks provide a linked list of dynamically allocated shared memory + * segments, with the most recently allocated chunk at the tail. + * Shmalloc allocates space from this list of chunks, creating new + * chunks as needed up to MAX_ALLOWED_SHMEM. + */ +typedef struct MemChunk { + struct MemChunk *next_chunk; /**< Pointer to next allocated chunk */ + size_t next; /**< Offset, within this chunk, of 1st + * free byte */ + size_t limit; /**< Offset, of 1st byte beyond chunk */ + void *memory[0]; /**< The rest of the chunk, from which + * memory is allocated */ +} MemChunk; + + + +/** + * The key length for veil hash types. + */ +#define HASH_KEYLEN 60 + + +/** + * Describes the type of an Object record or one of its subtypes. + */ +typedef enum { + OBJ_UNDEFINED = 0, + OBJ_SHMEMCTL, + OBJ_INT4, + OBJ_RANGE, + OBJ_BITMAP, + OBJ_BITMAP_ARRAY, + OBJ_BITMAP_HASH, + OBJ_BITMAP_REF, + OBJ_INT4_ARRAY +} ObjType; + +/** + * General purpose object-type. All veil variables are effectively + * sub-types of this. + */ +typedef struct Object { + ObjType type; /**< Identifies the type of the object. */ +} Object; + +/** + * The ShmemCtl structure is the first object allocated from the first + * chunk of shared memory in context 0. This object describes and + * manages shared memory allocated by shmalloc() + */ +typedef struct ShmemCtl { + ObjType type; /**< This must have the value OBJ_SHMEMCTL */ + bool initialised; /**< Set to true once struct is setup */ + LWLockId veil_lwlock; /**< dynamically allocated LWLock */ + int current_context; /**< Index of the current context (0 + * or 1) */ + size_t total_allocated[2]; /**< Total shared memory allocated in + * chunks in each context */ + bool switching; /**< Whether a context-switch is in + * progress */ + MemChunk *context[2]; /**< The first chunks of each context */ + TransactionId xid[2]; /**< The transaction id of the + * transaction that initialised each + * context: this is used to determine + * whether there are transactions + * still runnning that may be using an + * earlier context. */ +} ShmemCtl; + +/** + * Subtype of Object for storing simple int4 values. These values are + * allowed to be null. + */ +typedef struct Int4Var { + ObjType type; /**< This must have the value OBJ_INT4 */ + bool isnull; /**< if true, the value is null */ + int32 value; /**< the integer value of the variable */ +} Int4Var; + +/** + * Subtype of Object for storing range values. A range has an upper and + * lower bound, both stored as int4s. Nulls are not allowed. + */ +typedef struct Range { + ObjType type; /**< This must have the value OBJ_RANGE */ + int32 min; /**< Lower limit for range. */ + int32 max; /**< Upper limit for range. */ +} Range; + + + +/** + * Gives the bitmask index for the bitzero value of a Bitmap. This is + * part of the "normalisation" process for bitmap ranges. This process + * allows unlike bitmaps to be more easily compared by forcing bitmap + * indexes to be normalised around 32-bit word boundaries. Eg, 2 bitmaps + * with domains 1 to 50 and 3 to 55, will have identical bit patterns + * for bits 3 to 50. + * + * @param x The bitzero value of a bitmap + * + * @return The bitmask index representing x. + */ +#define BITZERO(x) (x & 0xffffffe0) + +/** + * Gives the bitmask index for the bitmax value of a bitmap. See + * BITZERO() for more information. + * + * @param x The bitmax value of a bitmap + * + * @return The bitmask index representing x. + */ +#define BITMAX(x) (x | 0x1f) + +/** + * Gives the index of a bit within the array of 32-bit words that + * comprise the bitmap. + * + * @param x The bit in question + * + * @return The array index of the bit. + */ +#define BITSET_ELEM(x) (x >> 5) + +/** + * Gives the index into ::bitmasks for the bit specified in x. + * + * @param x The bit in question + * + * @return The bitmask index + */ +#define BITSET_BIT(x) (x & 0x1f) + +/** + * Gives the number of array elements in a ::Bitmap that runs from + * element min to element max. + * + * @param min + * @param max + * + * @return The number of elements in the bitmap. + */ +#define ARRAYELEMS(min,max) (((max - BITZERO(min)) >> 5) + 1) + + +/** + * Return the smaller of a or b. Note that expressions a and b may be + * evaluated more than once. + * + * @param a + * @param b + * + * @return The smaller value of a or b. + */ +#define MIN(a,b) ((a < b)? a: b) + + +/** + * Subtype of Object for storing bitmaps. A bitmap is stored as an + * array of int4 values. See veil_bitmap.c for more information. Note + * that the size of a Bitmap structure is determined dynamically at run + * time as the size of the array is only known then. + */ +typedef struct Bitmap { + ObjType type; /**< This must have the value OBJ_BITMAP */ + DBG_CANARY_ENTRY /**< Debugging entry */ + DBG_ELEMS_ENTRY /**< Debugging entry */ + int32 bitzero; /**< The index of the lowest bit the bitmap can + * store */ + int32 bitmax; /**< The index of the highest bit the bitmap can + * store */ + uint32 bitset[EMPTY]; /**< Element zero of the array of int4 values + * comprising the bitmap. */ +} Bitmap; + +/** + * Subtype of Object for storing bitmap refs. A bitmapref is like a + * bitmap but instead of containing a bitmap it contains a reference to + * one. This reference may be set during a transaction and then + * referenced only from within the setting transaction. + */ +typedef struct BitmapRef { + ObjType type; /**< This must have the value OBJ_BITMAP_REF */ + TransactionId xid; /**< The xid for which this variable is + * valid */ + Bitmap *bitmap; /**< Pointer to the referenced bitmap */ +} BitmapRef; + +/** + * Subtype of Object for storing bitmap arrays. A bitmap array is + * simply an array of pointers to dynamically allocated Bitmaps. Note + * that the size of a Bitmap structure is determined dynamically at run + * time as the size of the array is only known then. + */ +typedef struct BitmapArray { // subtype of Object + ObjType type; /**< This must have the value OBJ_BITMAP_ARRAY */ + DBG_CANARY_ENTRY /**< Debugging entry */ + DBG_ELEMS_ENTRY /**< Debugging entry */ + int32 bitzero; /**< The index of the lowest bit each bitmap can + * store */ + int32 bitmax; /**< The index of the highest bit each bitmap can + * store */ + int32 arrayzero; /**< The index of array element zero: the + * index of the lowest numbered bitmap in the + * array */ + int32 arraymax; /**< The index of the lowest numbered bitmap in + * the array */ + Bitmap *bitmap[EMPTY]; /**< Element zero of the array of Bitmap pointers + * comprising the array. */ +} BitmapArray; + +/** + * Subtype of Object for storing bitmap hashes. A bitmap hash is a hash + * of dynamically allocated bitmaps, keyed by strings. Note that these + * cannot be created as shared variables. + */ +typedef struct BitmapHash { + ObjType type; /**< This must have the value OBJ_BITMAP_HASH */ + int32 bitzero; /**< The index of the lowest bit each bitmap can + * store */ + int32 bitmax; /**< The index of the highest bit each bitmap can + * store */ + HTAB *hash; /**< Pointer to the (Postgresql dynahash) hash + * table */ +} BitmapHash; + + +/** + * Subtype of Object for storing arrays of integers. + */ +typedef struct Int4Array { + ObjType type; /**< This must have the value OBJ_INT4_ARRAY */ + int32 arrayzero; /**< The index of array element zero: the + * index of the lowest numbered bitmap in the + * array */ + int32 arraymax; /**< The index of the lowest numbered bitmap in + * the array */ + int32 array[0]; /**< Element zero of the array of integers */ +} Int4Array; + + +/** + * A Veil variable. These may be session or shared variables, and may + * contain any Veil variable type. They are created and accessed by + * vl_lookup_shared_variable() and vl_lookup_variable(), and are stored + * in either the shared hash or one of the session hashes. See + * veil_shmem.c and veil_variables.c for more details. + */ +typedef struct VarEntry { + char key[HASH_KEYLEN]; /**< String containing variable name */ + bool shared; /**< Whether this is a shared variable */ + Object *obj; /**< Pointer to the contents of the variable */ +} VarEntry; + + +/** + * Describes a veil shared or session variable. This matches the SQL + * veil_variable_t which is defined as: +\verbatim +create type veil_variable_t as ( + name text, + type text, + shared bool, +); +\endverbatim + */ +typedef struct veil_variable_t { + char *name; /**< The name of the variable */ + char *type; /**< The type of the variable (eg "Bitmap") */ + bool shared; /**< Whether this is a shared variable (as + opposed to a session variable) */ +} veil_variable_t; + + +#endif + diff --git a/src/veil_demo.sql b/src/veil_demo.sql new file mode 100644 index 0000000..313f076 --- /dev/null +++ b/src/veil_demo.sql @@ -0,0 +1,423 @@ + +\echo Creating tables... + +\echo ...privileges... +create table privileges ( + privilege_id integer not null, + privilege_name varchar(80) not null +); +alter table privileges add constraint privilege__pk + primary key(privilege_id); + + +\echo ...role... +create table roles ( + role_id integer not null, + role_name varchar(80) not null +); +alter table roles add constraint role__pk + primary key(role_id); + + +\echo ...role_privileges... +create table role_privileges ( + role_id integer not null, + privilege_id integer not null +); + +alter table role_privileges add constraint role_privilege__pk + primary key(role_id, privilege_id); + +alter table role_privileges add constraint role_privilege__role_fk + foreign key(role_id) + references roles(role_id); + +alter table role_privileges add constraint role_privilege__priv_fk + foreign key(privilege_id) + references privileges(privilege_id); + +\echo ...role_roles... +create table role_roles ( + role_id integer not null, + has_role_id integer not null +); + +alter table role_roles add constraint role_role__pk + primary key(role_id, has_role_id); + +alter table role_roles add constraint role_role__role_fk + foreign key(role_id) + references roles(role_id); + +alter table role_roles add constraint role_role__has_role_fk + foreign key(has_role_id) + references roles(role_id); + + +\echo ...persons... +create sequence person_id_seq; +create table persons ( + person_id integer not null, + person_name varchar(80) not null +); +alter table persons add constraint person__pk + primary key(person_id); + + +\echo ...projects... +create sequence project_id_seq; +create table projects ( + project_id integer not null, + project_name varchar(80) not null +); +alter table projects add constraint project__pk + primary key(project_id); + + +\echo ...detail_types... +create table detail_types ( + detail_type_id integer not null, + required_privilege_id integer not null, + detail_type_name varchar(80) not null +); +alter table detail_types add constraint detail_type__pk + primary key(detail_type_id); + +alter table detail_types add constraint detail_type__priv_fk + foreign key(required_privilege_id) + references privileges(privilege_id); + + +\echo ...assignments... +create table assignments ( + project_id integer not null, + person_id integer not null, + role_id integer not null +); +alter table assignments add constraint assignment__pk + primary key(project_id, person_id); + +alter table assignments add constraint assignment__project_fk + foreign key(project_id) + references projects(project_id); + +alter table assignments add constraint assignment__person_fk + foreign key(person_id) + references persons(person_id); + +alter table assignments add constraint assignment__role_fk + foreign key(role_id) + references roles(role_id); + + +\echo ...person_roles... +create table person_roles ( + person_id integer not null, + role_id integer not null +); +alter table person_roles add constraint person_role__pk + primary key(person_id, role_id); + +alter table person_roles add constraint person_role__person_fk + foreign key(person_id) + references persons(person_id); + +alter table person_roles add constraint person_role__role_fk + foreign key(role_id) + references roles(role_id); + + +\echo ...project_details... +create table project_details ( + project_id integer not null, + detail_type_id integer not null, + value text not null +); +alter table project_details add constraint project_detail__pk + primary key(project_id, detail_type_id); + +alter table project_details add constraint project_detail__project_fk + foreign key(project_id) + references projects(project_id); + +alter table project_details add constraint project_detail__detail_fk + foreign key(detail_type_id) + references detail_types(detail_type_id); + + +\echo ...person_details... +create table person_details ( + person_id integer not null, + detail_type_id integer not null, + value text not null +); +alter table person_details add constraint person_detail__pk + primary key(person_id, detail_type_id); + +alter table person_details add constraint person_detail__person_fk + foreign key(person_id) + references persons(person_id); + +alter table person_details add constraint person_detail__detail_fk + foreign key(detail_type_id) + references detail_types(detail_type_id); + +\echo Setting up base data... + +\echo ...privileges... +copy privileges (privilege_id, privilege_name) from stdin; +10001 select_privileges +10002 insert_privileges +10003 update_privileges +10004 delete_privileges +10005 select_roles +10006 insert_roles +10007 update_roles +10008 delete_roles +10009 select_role_privileges +10010 insert_role_privileges +10011 update_role_privileges +10012 delete_role_privileges +10013 select_persons +10014 insert_persons +10015 update_persons +10016 delete_persons +10017 select_projects +10018 insert_projects +10019 update_projects +10020 delete_projects +10021 select_detail_types +10022 insert_detail_types +10023 update_detail_types +10024 delete_detail_types +10025 select_assignments +10026 insert_assignments +10027 update_assignments +10028 delete_assignments +10029 select_person_roles +10030 insert_person_roles +10031 update_person_roles +10032 delete_person_roles +10033 select_project_details +10034 insert_project_details +10035 update_project_details +10036 delete_project_details +10037 select_person_details +10038 insert_person_details +10039 update_person_details +10040 delete_person_details +10041 select_role_roles +10042 insert_role_roles +10043 update_role_roles +10044 delete_role_roles +10100 can_connect +10150 view_basic +10151 view_personal +10152 view_personal_secure +10153 view_project_confidential +\. + +\echo ...roles... +copy roles (role_id, role_name) from stdin; +11001 DBA +11002 Personal Context +11003 Employee +11004 Worker +11005 Project Manager +11006 Director +11007 Manager +\. + +\echo ...role_privileges... +-- DBA can do anything (but is not automatically an employee) +insert into role_privileges (role_id, privilege_id) +select 11001, privilege_id +from privileges +where privilege_id != 10100; + +-- Personal Context allows update of personal details +copy role_privileges (role_id, privilege_id) from stdin; +11002 10013 +11002 10015 +11002 10025 +11002 10029 +11002 10037 +11002 10038 +11002 10039 +11002 10040 +11002 10150 +11002 10151 +11002 10152 +\. + +-- Basic Access can see lookup data +insert into role_privileges (role_id, privilege_id) +select 11003, privilege_id +from privileges +where privilege_name in ('select_privileges', 'select_roles', + 'select_role_privileges', 'select_detail_types'); + +insert into role_privileges (role_id, privilege_id) +select 11003, 10100; + +-- Workers can modify project info +insert into role_privileges (role_id, privilege_id) +select 11004, privilege_id +from privileges +where privilege_name like '%project%' +and privilege_name not like 'delete%' +and privilege_name not like '%confidential'; + +insert into role_privileges (role_id, privilege_id) +select 11004, 10025; +insert into role_privileges (role_id, privilege_id) +select 11004, 10150; + +-- Project Manager can do anything to project info and can see personal info +insert into role_privileges (role_id, privilege_id) +select 11005, privilege_id +from privileges +where privilege_name like '%project%' +or privilege_name like '%assignment%'; + +insert into role_privileges (role_id, privilege_id) +select 11005, privilege_id +from privileges +where privilege_name like 'select_person%'; + +insert into role_privileges (role_id, privilege_id) +select 11005, 10150; +insert into role_privileges (role_id, privilege_id) +select 11005, 10151; + +-- Director can do anything except modify personal details +insert into role_privileges (role_id, privilege_id) +select 11006, privilege_id +from privileges +where privilege_name not like '%person%'; + +insert into role_privileges (role_id, privilege_id) +select 11006, privilege_id +from privileges +where privilege_name like 'select_person%'; + +insert into role_privileges (role_id, privilege_id) +select 11006, 10014; + +insert into role_privileges (role_id, privilege_id) +select 11006, 10151; + +insert into role_privileges (role_id, privilege_id) +select 11006, 10152; + +-- Manager can see personal info +insert into role_privileges (role_id, privilege_id) +select 11007, privilege_id +from privileges +where privilege_name like 'select_person%'; + +insert into role_privileges (role_id, privilege_id) +select 11007, 10150; +insert into role_privileges (role_id, privilege_id) +select 11007, 10151; + + +\echo ...persons... +copy persons (person_id, person_name) from stdin; +1 Deb (the DBA) +2 Pat (the PM) +3 Derick (the director) +4 Will (the worker) +5 Wilma (the worker) +6 Fred (the fired DBA) +\. + +\echo ...person_roles... +copy person_roles (person_id, role_id) from stdin; +1 11001 +1 11003 +2 11003 +2 11007 +3 11003 +3 11006 +4 11003 +5 11003 +6 11001 +\. + +\echo ...projects... +copy projects (project_id, project_name) from stdin; +101 Secret Project +102 Public project +\. + +\echo ...assignments... +copy assignments (project_id, person_id, role_id) from stdin; +101 3 11005 +101 5 11004 +102 2 11005 +102 4 11004 +102 5 11004 +\. + +\echo ...detail_types... +copy detail_types (detail_type_id, required_privilege_id, + detail_type_name) from stdin; +1001 10150 start_date +1002 10150 status +1003 10150 join_date +1004 10152 salary +1005 10151 date of birth +1006 10152 sin +1007 10150 skills +1008 10153 contract value +\. + +\echo ...person_details... +copy person_details (person_id, detail_type_id, value) from stdin; +1 1003 20050102 +2 1003 20050103 +3 1003 20050104 +4 1003 20050105 +5 1003 20050106 +6 1003 20050107 +1 1002 Employee +2 1002 Employee +3 1002 Employee +4 1002 Employee +5 1002 Employee +6 1002 Terminated +1 1004 50,000 +2 1004 50,000 +3 1004 80,000 +4 1004 30,000 +5 1004 30,000 +6 1004 40,000 +1 1005 19610102 +2 1005 19600102 +3 1005 19650102 +4 1005 19660102 +5 1005 19670102 +1 1006 123456789 +2 1006 123456789 +3 1006 123456789 +4 1006 123456789 +5 1006 123456789 +1 1007 Oracle, C, SQL +2 1007 Soft peoply-stuff +3 1007 None at all +4 1007 Subservience +5 1007 Subservience +\. + + +\echo ...project_details +copy project_details (project_id, detail_type_id, value) from stdin; +101 1001 20050101 +101 1002 Secretly ongoing +101 1008 $800,000 +102 1001 20050101 +102 1002 Ongoing +102 1008 $100,000 +\. + diff --git a/src/veil_funcs.h b/src/veil_funcs.h new file mode 100644 index 0000000..a0dd76e --- /dev/null +++ b/src/veil_funcs.h @@ -0,0 +1,137 @@ +/** + * @file veil_funcs.h + * \code + * Author: Marc Munro + * Copyright (c) 2005 - 2011 Marc Munro + * License: BSD + * \endcode + * @brief + * Provide definitions for all non-local C-callable Veil functions. + * + */ + +#include "veil_datatypes.h" +#include "fmgr.h" + +/* veil_utils */ +extern void *vl_malloc(size_t size); +char *vl_ObjTypeName(ObjType obj); + + +/* veil_variables */ +extern VarEntry *vl_lookup_shared_variable(char *name); +extern VarEntry *vl_lookup_variable(char *name); +extern veil_variable_t *vl_next_variable(veil_variable_t *prev); +extern void vl_ClearInt4Array(Int4Array *array); +extern Int4Array *vl_NewInt4Array(Int4Array *current, bool shared, + int32 min, int32 max); +extern void vl_Int4ArraySet(Int4Array *array, int32 idx, int32 value); +extern int32 vl_Int4ArrayGet(Int4Array *array, int32 idx); + + +/* veil_datatypes */ +extern Range *vl_NewRange(bool shared); +extern Int4Var *vl_NewInt4(bool shared); + +/* veil_bitmap */ +extern void vl_ClearBitmap(Bitmap *bitmap); +extern void vl_NewBitmap(Bitmap **p_bitmap, bool shared, int32 min, int32 max); +extern void vl_BitmapSetbit(Bitmap *bitmap, int32 bit); +extern void vl_BitmapClearbit(Bitmap *bitmap, int32 bit); +extern bool vl_BitmapTestbit(Bitmap *bitmap, int32 bit); +extern void vl_BitmapUnion(Bitmap *target, Bitmap *source); +extern void vl_BitmapIntersect(Bitmap *target, Bitmap *source); +extern int32 vl_BitmapNextBit(Bitmap *bitmap, int32 bit, bool *found); +extern Bitmap *vl_BitmapFromArray(BitmapArray *bmarray, int32 elem); +extern void vl_ClearBitmapArray(BitmapArray *bmarray); +extern void vl_NewBitmapArray(BitmapArray **p_bmarray, bool shared, + int32 arrayzero, int32 arraymax, + int32 bitzero, int32 bitmax); +extern VarEntry *vl_NextHashEntry(HTAB *hash, VarEntry *prev); +extern void vl_NewBitmapHash(BitmapHash **p_bmhash, char *name, + int32 bitzero, int32 bitmax); +extern Bitmap *vl_BitmapFromHash(BitmapHash *bmhash, char *hashelem); +extern Bitmap *vl_AddBitmapToHash(BitmapHash *bmhash, char *hashelem); +extern bool vl_BitmapHashHasKey(BitmapHash *bmhash, char *hashelem); + +/* veil_shmem */ +extern HTAB *vl_get_shared_hash(void); +extern bool vl_prepare_context_switch(void); +extern bool vl_complete_context_switch(void); +extern void vl_force_context_switch(void); +extern void *vl_shmalloc(size_t size); +extern void vl_free(void *mem); +extern void _PG_init(void); + +/* veil_query */ +extern int vl_spi_connect(void); +extern int vl_spi_finish(void); +extern bool vl_bool_from_query(const char *qry, bool *result); +extern bool vl_db_exists(Oid db_id); + +/* veil_config */ +extern void veil_config_init(void); +extern void veil_load_config(void); +extern int veil_shared_hash_elems(void); +extern int veil_dbs_in_cluster(void); +extern int veil_shmem_context_size(void); + + +/* veil_interface */ +extern void vl_type_mismatch(char *name, ObjType expected, ObjType got); +extern Datum veil_variables(PG_FUNCTION_ARGS); +extern Datum veil_share(PG_FUNCTION_ARGS); +extern Datum veil_init_range(PG_FUNCTION_ARGS); +extern Datum veil_range(PG_FUNCTION_ARGS); +extern Datum veil_init_bitmap(PG_FUNCTION_ARGS); +extern Datum veil_clear_bitmap(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_setbit(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_clearbit(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_testbit(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_union(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_intersect(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_bits(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_range(PG_FUNCTION_ARGS); +extern Datum veil_init_bitmap_array(PG_FUNCTION_ARGS); +extern Datum veil_clear_bitmap_array(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_from_array(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_array_testbit(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_array_setbit(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_array_clearbit(PG_FUNCTION_ARGS); +extern Datum veil_union_from_bitmap_array(PG_FUNCTION_ARGS); +extern Datum veil_intersect_from_bitmap_array(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_array_bits(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_array_arange(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_array_brange(PG_FUNCTION_ARGS); +extern Datum veil_init_bitmap_hash(PG_FUNCTION_ARGS); +extern Datum veil_clear_bitmap_hash(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_hash_key_exists(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_from_hash(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_hash_testbit(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_hash_setbit(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_hash_clearbit(PG_FUNCTION_ARGS); +extern Datum veil_union_into_bitmap_hash(PG_FUNCTION_ARGS); +extern Datum veil_union_from_bitmap_hash(PG_FUNCTION_ARGS); +extern Datum veil_intersect_from_bitmap_hash(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_hash_bits(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_hash_entries(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_from_hash(PG_FUNCTION_ARGS); +extern Datum veil_bitmap_hash_range(PG_FUNCTION_ARGS); +extern Datum veil_int4_set(PG_FUNCTION_ARGS); +extern Datum veil_int4_get(PG_FUNCTION_ARGS); +extern Datum veil_init_int4array(PG_FUNCTION_ARGS); +extern Datum veil_clear_int4array(PG_FUNCTION_ARGS); +extern Datum veil_int4array_set(PG_FUNCTION_ARGS); +extern Datum veil_int4array_get(PG_FUNCTION_ARGS); +extern Datum veil_init(PG_FUNCTION_ARGS); +extern Datum veil_perform_reset(PG_FUNCTION_ARGS); +extern Datum veil_force_reset(PG_FUNCTION_ARGS); +extern Datum veil_version(PG_FUNCTION_ARGS); +extern Datum veil_serialise(PG_FUNCTION_ARGS); +extern Datum veil_deserialise(PG_FUNCTION_ARGS); + + +/* veil_serialise */ +extern char *vl_serialise_var(char *name); +extern int4 vl_deserialise(char **p_stream); +extern VarEntry *vl_deserialise_next(char **p_stream); diff --git a/src/veil_interface.c b/src/veil_interface.c new file mode 100644 index 0000000..f5cc6bf --- /dev/null +++ b/src/veil_interface.c @@ -0,0 +1,2506 @@ +/** + * @file veil_interface.c + * \code + * Author: Marc Munro + * Copyright (c) 2005 - 2011 Marc Munro + * License: BSD + * + * \endcode + * @brief + * Functions providing the SQL interface to veil, and utility functions + * to support them. + */ + +#include "postgres.h" +#include "access/xact.h" +#include "executor/spi.h" +#include "funcapi.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" + +#include "veil_version.h" +#include "veil_funcs.h" +#include "veil_datatypes.h" + + +PG_MODULE_MAGIC; + + +/** + * Create a dynamically allocated C string as a copy of a text value. + * + * @param in text value from which the copy is made. + * @return Dynamically allocated (by palloc()) copy of in. + */ +static char * +strfromtext(text *in) +{ + char *out = palloc(VARSIZE(in) - VARHDRSZ + 1); + memcpy(out, VARDATA(in), VARSIZE(in) - VARHDRSZ); + out[VARSIZE(in) - VARHDRSZ] = '\0'; + + return out; +} + +/** + * Create a dynamically allocated text value as a copy of a C string. + * + * @param in String to be copied + * @return Dynamically allocated (by palloc()) copy of in. + */ +static text * +textfromstr(char *in) +{ + int len = strlen(in); + text *out = palloc(len + VARHDRSZ); + memcpy(VARDATA(out), in, len); + SET_VARSIZE(out, (len + VARHDRSZ)); + + return out; +} + +/** + * Create a dynamically allocated text value as a copy of a C string, + * applying a limit to the length. + * + * @param in String to be copied + * @param limit Maximum length of string to be copied. + * @return Dynamically allocated (by palloc()) copy of in. + */ +static text * +textfromstrn(char *in, int limit) +{ + int len = strlen(in); + text *out; + + if (limit < len) { + len = limit; + } + + out = palloc(len + VARHDRSZ); + memcpy(VARDATA(out), in, len); + SET_VARSIZE(out, (len + VARHDRSZ)); + + return out; +} + +/** + * Create a dynamically allocated text value as a copy of a C string, + * applying a limit to the length. + * + * @param str String to be copied + * @return Dynamically allocated (by palloc()) copy of str. + */ +static char * +copystr(char *str) +{ + char *new = palloc((sizeof(char) * strlen(str) + 1)); + strcpy(new, str); + return new; +} + +/** + * Create a dynamically allocated C string as a copy of an integer value. + * + * @param val value to be stringified + * @return Dynamically allocated string. + */ +static char * +strfromint(int4 val) +{ + char *new = palloc((sizeof(char) * 17)); /* Large enough for any 32 + * bit number */ + sprintf(new, "%d", val); + return new; +} + +/** + * Create a dynamically allocated C string as a copy of a boolean value. + * + * @param val value to be stringified + * @return Dynamically allocated string. + */ +static char * +strfrombool(bool val) +{ + char *new = palloc((sizeof(char) * 2)); + if (val) { + strcpy(new, "t"); + } + else { + strcpy(new, "f"); + } + return new; +} + +/** + * Perform session initialisation once for the session. This calls the + * user-defined function veil_init which should create and possibly + * initialise all session and, maybe, shared variables. This function + * may be safely called any number of times - it will only perform the + * initialisation on the first call. + * + */ +static void +ensure_init() +{ + bool success = false; + TransactionId this_xid; + int ok; + HTAB *hash; + static bool done = false; + static TransactionId xid = 0; + + if (!done) { + this_xid = GetCurrentTransactionId(); + if (xid == this_xid) { + /* We must have been called recursively, so just return */ + return; + } + xid = this_xid; /* Record our xid in case we recurse */ + ok = vl_spi_connect(); + if (ok != SPI_OK_CONNECT) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to initialise session (1)"), + errdetail("SPI_connect() failed, returning %d.", ok))); + } + + hash = vl_get_shared_hash(); /* Init all shared memory + constructs */ + (void) vl_bool_from_query("select veil_init(FALSE)", &success); + + if (!success) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to initialise session (2)"), + errdetail("veil_init() did not return true."))); + } + + ok = vl_spi_finish(); + if (ok != SPI_OK_FINISH) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to initialise session (3)"), + errdetail("SPI_finish() failed, returning %d.", ok))); + } + done = true; /* init is done, we don't need to do it again. */ + } +} + +/** + * Report, by raising an error, a type mismatch between the expected and + * actual type of a VarEntry variable. + * + * @param name The name of the variable + * @param expected The expected type. + * @param got The actual type + */ +extern void +vl_type_mismatch(char *name, + ObjType expected, + ObjType got) +{ + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("type mismatch in %s: expected %s, got %s", + name, vl_ObjTypeName(expected), vl_ObjTypeName(got)), + errdetail("Variable %s is not of the expected type.", name))); +} + +/** + * Return the Int4Var variable matching the name parameter, possibly + * creating the variable. Raise an error if the named variable already + * exists and is of the wrong type. + * + * @param name The name of the variable. + * @param create Whether to create the variable if it does not exist. + * @return Pointer to the variable or null if the variable does not + * exist and create was false. + */ +static Int4Var * +GetInt4Var(char *name, + bool create) +{ + VarEntry *var; + Int4Var *i4v; + + var = vl_lookup_variable(name); + i4v = (Int4Var *) var->obj; + + if (i4v) { + if (i4v->type != OBJ_INT4) { + vl_type_mismatch(name, OBJ_INT4, i4v->type); + } + } + else { + if (create) { + var->obj = (Object *) vl_NewInt4(var->shared); + i4v = (Int4Var *) var->obj; + } + else { + vl_type_mismatch(name, OBJ_INT4, OBJ_UNDEFINED); + } + } + return i4v; +} + +/** + * Return the Range variable matching the name parameter, possibly + * creating the variable. Raise an error if the named variable already + * exists and is of the wrong type. + * + * @param name The name of the variable. + * @param create Whether to create the variable if it does not exist. + * @return Pointer to the variable or null if the variable does not + * exist and create was false. + */ +static Range * +GetRange(char *name, + bool create) +{ + VarEntry *var; + Range *range; + + var = vl_lookup_variable(name); + range = (Range *) var->obj; + + if (range) { + if (range->type != OBJ_RANGE) { + vl_type_mismatch(name, OBJ_RANGE, range->type); + } + } + else { + if (create) { + var->obj = (Object *) vl_NewRange(var->shared); + range = (Range *) var->obj; + } + else { + vl_type_mismatch(name, OBJ_RANGE, OBJ_UNDEFINED); + } + } + return range; +} + +/** + * Return the Bitmap from a bitmap variable. This function exists + * primarily to perform type checking, and to raise an error if the + * variable is not a bitmap. + * + * @param var The VarEntry that should contain a bitmap. + * @param allow_empty Whether to raise an error if the variable has not + * yet been initialised. + * @param allow_ref Whether to (not) raise an error if the variable is a + * bitmap_ref rather than a bitmap. + * @return Pointer to the variable or null if the variable is undefined + * and allow_empty was true. + */ +static Bitmap * +GetBitmapFromVar(VarEntry *var, + bool allow_empty, + bool allow_ref) +{ + Bitmap *bitmap = (Bitmap *) var->obj; + + if (bitmap) { + if (bitmap->type != OBJ_BITMAP) { + if (allow_ref && (bitmap->type == OBJ_BITMAP_REF)) { + BitmapRef *bmref = (BitmapRef *) bitmap; + if (bmref->xid == GetCurrentTransactionId()) { + bitmap = bmref->bitmap; + } + else { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("BitmapRef %s is not defined", + var->key), + errhint("Perhaps the name is mis-spelled, or its " + "definition is missing from " + "veil_init()."))); + } + } + else { + vl_type_mismatch(var->key, OBJ_BITMAP, bitmap->type); + } + } + } + if (!bitmap) { + if (!allow_empty) { + vl_type_mismatch(var->key, OBJ_BITMAP, OBJ_UNDEFINED); + } + } + return bitmap; +} + +/** + * Return the Bitmap matching the name parameter, possibly creating the + * VarEntry (variable) for it. Raise an error if the named variable + * already exists and is of the wrong type. + * + * @param name The name of the variable. + * @param allow_empty Whether to raise an error if the variable has not + * been defined. + * @param allow_ref Whether to (not) raise an error if the variable is a + * bitmap_ref rather than a bitmap. + * @return Pointer to the variable or null if the variable does not + * exist and allow_empty was false. + */ +static Bitmap * +GetBitmap(char *name, + bool allow_empty, + bool allow_ref) +{ + VarEntry *var; + Bitmap *bitmap; + + var = vl_lookup_variable(name); + bitmap = GetBitmapFromVar(var, allow_empty, allow_ref); + + return bitmap; +} + +/** + * Return the BitmapRef from a bitmap ref variable. This function exists + * primarily to perform type checking, and to raise an error if the + * variable is not a bitmap ref. Note that BitmapRef variables may not + * be shared as they can contain references to non-shared objects. + * + * @param var The VarEntry that should contain a bitmap ref. + * @return Pointer to the variable. + */ +static BitmapRef * +GetBitmapRefFromVar(VarEntry *var) +{ + BitmapRef *bmref = (BitmapRef *) var->obj; + + if (bmref) { + if (bmref->type != OBJ_BITMAP_REF) { + vl_type_mismatch(var->key, OBJ_BITMAP_REF, bmref->type); + } + } + else { + if (var->shared) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("illegal attempt to define shared BitmapRef %s", + var->key), + errhint("BitmapRefs may only be defined as session, " + "not shared, variables."))); + } + /* Create a new bmref (these are always session variables. */ + bmref = vl_malloc(sizeof(BitmapRef)); + bmref->type = OBJ_BITMAP_REF; + bmref->bitmap = NULL; + var->obj = (Object *) bmref; + } + + return bmref; +} + +/** + * Return the BitmapRef matching the name parameter, possibly creating the + * VarEntry (variable) for it. Raise an error if the named variable + * already exists and is of the wrong type. + * + * @param name The name of the variable. + * @return Pointer to the variable + */ +static BitmapRef * +GetBitmapRef(char *name) +{ + VarEntry *var; + BitmapRef *bmref; + + var = vl_lookup_variable(name); + bmref = GetBitmapRefFromVar(var); + + return bmref; +} + +/** + * Return the BitmapArray from a bitmap array variable. This function + * exists primarily to perform type checking, and to raise an error if + * the variable is not a bitmap array. + * + * @param var The VarEntry that should contain a bitmap array. + * @param allow_empty Whether to raise an error if the variable has not + * yet been initialised. + * @return Pointer to the variable or null if the variable is undefined + * and allow_empty was true. + */ +static BitmapArray * +GetBitmapArrayFromVar(VarEntry *var, + bool allow_empty) +{ + BitmapArray *bmarray; + bmarray = (BitmapArray *) var->obj; + + if (bmarray) { + if (bmarray->type != OBJ_BITMAP_ARRAY) { + vl_type_mismatch(var->key, OBJ_BITMAP_ARRAY, bmarray->type); + } + } + else { + if (!allow_empty) { + vl_type_mismatch(var->key, OBJ_BITMAP_ARRAY, OBJ_UNDEFINED); + } + } + + return bmarray; +} + +/** + * Return the BitmapArray matching the name parameter, possibly creating + * the (VarEntry) variable. Raise an error if the named variable + * already exists and is of the wrong type. + * + * @param name The name of the variable. + * @param allow_empty Whether to raise an error if the variable has not + * been defined + * @return Pointer to the variable or null if the variable does not + * exist and create was false. + */ +static BitmapArray * +GetBitmapArray(char *name, + bool allow_empty) +{ + VarEntry *var; + BitmapArray *bmarray; + + var = vl_lookup_variable(name); + bmarray = GetBitmapArrayFromVar(var, allow_empty); + + return bmarray; +} + +/** + * Return the BitmapHash from a bitmap hash variable. This function + * exists primarily to perform type checking, and to raise an error if + * the variable is not a bitmap hash. + * + * @param var The VarEntry that should contain a bitmap hash. + * @param allow_empty Whether to raise an error if the variable has not + * yet been initialised. + * @return Pointer to the variable or null if the variable is undefined + * and allow_empty was true. + */ +static BitmapHash * +GetBitmapHashFromVar(VarEntry *var, + bool allow_empty) +{ + BitmapHash *bmhash; + bmhash = (BitmapHash *) var->obj; + + if (bmhash) { + if (bmhash->type != OBJ_BITMAP_HASH) { + vl_type_mismatch(var->key, OBJ_BITMAP_HASH, bmhash->type); + } + } + else { + if (!allow_empty) { + vl_type_mismatch(var->key, OBJ_BITMAP_HASH, OBJ_UNDEFINED); + } + } + + return bmhash; +} + +/** + * Return the BitmapHash matching the name parameter, possibly creating + * the VarEntry (variable) for it. Raise an error if the named variable + * already exists and is of the wrong type. + * + * @param name The name of the variable. + * @param allow_empty Whether to raise an error if the variable has not + * been defined. + * @return Pointer to the variable or null if the variable does not + * exist and create was false. + */ +static BitmapHash * +GetBitmapHash(char *name, + bool allow_empty) +{ + VarEntry *var; + BitmapHash *bmhash; + + var = vl_lookup_variable(name); + bmhash = GetBitmapHashFromVar(var, allow_empty); + + return bmhash; +} + +/** + * Return the Int4Array from an Int4Array variable. This function + * exists primarily to perform type checking, and to raise an error if + * the variable is not an Int4Array. + * + * @param var The VarEntry that should contain an Int4Array. + * @param allow_empty Whether to raise an error if the variable has not + * yet been initialised. + * @return Pointer to the variable or null if the variable is undefined + * and allow_empty was true. + */ +static Int4Array * +GetInt4ArrayFromVar(VarEntry *var, + bool allow_empty) +{ + Int4Array *array; + array = (Int4Array *) var->obj; + + if (array) { + if (array->type != OBJ_INT4_ARRAY) { + vl_type_mismatch(var->key, OBJ_INT4_ARRAY, array->type); + } + } + else { + if (!allow_empty) { + vl_type_mismatch(var->key, OBJ_INT4_ARRAY, OBJ_UNDEFINED); + } + } + + return array; +} + +/** + * Return the Int4Array matching the name parameter, possibly creating + * the VarEntry (variable) for it. Raise an error if the named variable + * already exists and is of the wrong type. + * + * @param name The name of the variable. + * @param allow_empty Whether to raise an error if the variable has not + * been defined. + * @return Pointer to the variable or null if the variable does not + * exist and create was false. + */ +static Int4Array * +GetInt4Array(char *name, + bool allow_empty) +{ + VarEntry *var; + Int4Array *array; + + var = vl_lookup_variable(name); + array = GetInt4ArrayFromVar(var, allow_empty); + + return array; +} + +PG_FUNCTION_INFO_V1(veil_variables); +/** + * veil_variables() returns setof veil_variable_t + * Return a veil_variable_t record for each defined + * variable. This includes both session and shared variables. + * + * @param fcinfo None + * @return setof veil_variable_t + */ +Datum +veil_variables(PG_FUNCTION_ARGS) +{ + TupleDesc tupdesc; + TupleTableSlot *slot; + AttInMetadata *attinmeta; + FuncCallContext *funcctx; + + veil_variable_t *var; + char **values; + HeapTuple tuple; + Datum datum; + + if (SRF_IS_FIRSTCALL()) + { + /* Only do this on first call for this result set */ + MemoryContext oldcontext; + + ensure_init(); + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + tupdesc = RelationNameGetTupleDesc("veil_variable_t"); + slot = TupleDescGetSlot(tupdesc); + funcctx->slot = slot; + attinmeta = TupleDescGetAttInMetadata(tupdesc); + funcctx->attinmeta = attinmeta; + + MemoryContextSwitchTo(oldcontext); + funcctx->user_fctx = NULL; + } + + funcctx = SRF_PERCALL_SETUP(); + var = vl_next_variable(funcctx->user_fctx); + funcctx->user_fctx = var; + + if (var) { + values = (char **) palloc(3 * sizeof(char *)); + values[0] = copystr(var->name); + values[1] = copystr(var->type); + values[2] = strfrombool(var->shared); + + slot = funcctx->slot; + attinmeta = funcctx->attinmeta; + + tuple = BuildTupleFromCStrings(attinmeta, values); + datum = TupleGetDatum(slot, tuple); + SRF_RETURN_NEXT(funcctx, datum); + + } + else { + SRF_RETURN_DONE(funcctx); + } +} + +PG_FUNCTION_INFO_V1(veil_share); +/** + * veil_share(name text) returns bool + * Define a shared variable called NAME, returning true. If the + * variable is already defined as a session variable an ERROR will be + * raised. + * + * Session variables are simply defined by their first usage. Shared + * variables must be defined using this function. They may then be used + * in exactly the same way as session variables. Shared variables are + * shared by all backends and so need only be initialised once. The + * result of this function tells the caller whether the variable needs + * to be initialised. The caller that first defines a shared variable + * will get a false result and from this will know that the variable + * must be initialised. All subsequent callers will get a true result + * and so will know that the variable is already initialised. + * + * @param fcinfo name text name of variable. + * @return bool true if the variable already exists + */ +Datum +veil_share(PG_FUNCTION_ARGS) +{ + char *name; + VarEntry *var; + + ensure_init(); + name = strfromtext(PG_GETARG_TEXT_P(0)); + + var = vl_lookup_shared_variable(name); + + PG_RETURN_BOOL((var->obj != NULL)); +} + + +PG_FUNCTION_INFO_V1(veil_init_range); +/** + * veil_init_range(name text, min int4, max int4) returns int4 + * Initialise a Range variable called NAME constrained by MIN and MAX, + * returning the number of elements in the range. Ranges may be + * examined using the veil_range() function. + * + * @param fcinfo name text The name of the variable to + * initialise. + *
min int4 The min value of the range. + *
max int4 The max value of the range. + * @return int4 The size of the range ((max - min) + 1). + */ +Datum +veil_init_range(PG_FUNCTION_ARGS) +{ + int32 min; + int32 max; + Range *range; + char *name; + + ensure_init(); + name = strfromtext(PG_GETARG_TEXT_P(0)); + min = PG_GETARG_INT32(1); + max = PG_GETARG_INT32(2); + + range = GetRange(name, true); + + range->min = min; + range->max = max; + PG_RETURN_INT32(max + 1 - min); +} + + +/** + * Create a datum containing the values of a veil_range_t composite + * type. + * + * @param min Min value of range + * @param max Max value of range + * @return Composite (row) type datum containing the range elements. + */ +static Datum +datum_from_range(int32 min, int32 max) +{ + static bool init_done = false; + static TupleDesc tupdesc; + static AttInMetadata *attinmeta; + TupleTableSlot *slot; + HeapTuple tuple; + char **values; + + if (!init_done) { + /* Keep all static data in top memory context where it will + * safely remain during the session. */ + + MemoryContext oldcontext = MemoryContextSwitchTo(TopMemoryContext); + + init_done = true; + tupdesc = RelationNameGetTupleDesc("veil_range_t"); + slot = TupleDescGetSlot(tupdesc); + attinmeta = TupleDescGetAttInMetadata(tupdesc); + + MemoryContextSwitchTo(oldcontext); + } + + /* Create value strings to be returned to caller. */ + values = (char **) palloc(2 * sizeof(char *)); + values[0] = strfromint(min); + values[1] = strfromint(max); + + tuple = BuildTupleFromCStrings(attinmeta, values); + slot = TupleDescGetSlot(tupdesc); + + /* make the tuple into a datum */ + return TupleGetDatum(slot, tuple); +} + + +PG_FUNCTION_INFO_V1(veil_range); +/** + * veil_range(name text) returns veil_range_t + * Return the range (as a SQL veil_range_t composite type) from the + * named variable. + * An Error will be raised if the variable is not defined or is of the + * wrong type. + * + * @param fcinfo name text The name of the range variable. + * @return veil_range_t Composite type containing the min + * and max values from the named variable. + */ +Datum +veil_range(PG_FUNCTION_ARGS) +{ + char *name; + Range *range; + Datum datum; + + ensure_init(); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + range = GetRange(name, false); + + datum = (datum_from_range(range->min, range->max)); + + PG_RETURN_DATUM(datum); +} + +PG_FUNCTION_INFO_V1(veil_init_bitmap); +/** + * veil_init_bitmap(bitmap_name text, range_nametext) returns bool + * Create or re-initialise a Bitmap, for dealing with a named range of + * values. + * An error will be raised if the variable already exists and is not a + * Bitmap. + * + * @param fcinfo bitmap_name text The name of the bitmap to + * create or reset + *
range_name text The name of a Range variable that + * defines the range of the new bitmap. + * @return bool true + */ +Datum +veil_init_bitmap(PG_FUNCTION_ARGS) +{ + char *bitmap_name; + char *range_name; + Bitmap *bitmap; + VarEntry *bitmap_var; + Range *range; + + ensure_init(); + + bitmap_name = strfromtext(PG_GETARG_TEXT_P(0)); + bitmap_var = vl_lookup_variable(bitmap_name); + bitmap = GetBitmapFromVar(bitmap_var, true, false); + range_name = strfromtext(PG_GETARG_TEXT_P(1)); + range = GetRange(range_name, false); + + vl_NewBitmap(&bitmap, bitmap_var->shared, range->min, range->max); + + bitmap_var->obj = (Object *) bitmap; + + PG_RETURN_BOOL(true); +} + +PG_FUNCTION_INFO_V1(veil_clear_bitmap); +/** + * veil_clear_bitmap(name text) returns bool + * Clear all bits in the specified Bitmap. + * An error will be raised if the variable is not a Bitmap or BitmapRef. + * + * @param fcinfo name text The name of the bitmap to + * be cleared. + * @return bool true + */ +Datum +veil_clear_bitmap(PG_FUNCTION_ARGS) +{ + char *bitmap_name; + VarEntry *bitmap_var; + Bitmap *bitmap; + + ensure_init(); + + bitmap_name = strfromtext(PG_GETARG_TEXT_P(0)); + bitmap_var = vl_lookup_variable(bitmap_name); + bitmap = GetBitmapFromVar(bitmap_var, false, true); + + vl_ClearBitmap(bitmap); + + PG_RETURN_BOOL(true); +} + +PG_FUNCTION_INFO_V1(veil_bitmap_setbit); +/** + * veil_bitmap_setbit(name text, bit_number int4) returns bool + * Set the specified bit in the specified Bitmap. + * + * An error will be raised if the variable is not a Bitmap or BitmapRef. + * + * @param fcinfo name text The name of the bitmap variable. + *
bit_number int4 The bit to be set. + * @return bool true + */ +Datum +veil_bitmap_setbit(PG_FUNCTION_ARGS) +{ + char *name; + Bitmap *bitmap; + int32 bit; + + ensure_init(); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + bit = PG_GETARG_INT32(1); + bitmap = GetBitmap(name, false, true); + vl_BitmapSetbit(bitmap, bit); + + PG_RETURN_BOOL(true); +} + +PG_FUNCTION_INFO_V1(veil_bitmap_clearbit); +/** + * veil_bitmap_clearbit(name int4, bit_number text) returns bool + * Clear the specified bit in the specified Bitmap. + * + * An error will be raised if the variable is not a Bitmap or BitmapRef. + * + * @param fcinfo name text The name of the bitmap variable. + *
bit_number int4 The bit to be cleared. + * @return bool true + */ +Datum +veil_bitmap_clearbit(PG_FUNCTION_ARGS) +{ + char *name; + Bitmap *bitmap; + int32 bit; + + ensure_init(); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + bit = PG_GETARG_INT32(1); + bitmap = GetBitmap(name, false, true); + vl_BitmapClearbit(bitmap, bit); + + PG_RETURN_BOOL(true); +} + +PG_FUNCTION_INFO_V1(veil_bitmap_testbit); +/** + * veil_bitmap_testbit(name text, bit_number int4) returns bool + * Test the specified bit in the specified Bitmap, returning true if it + * is set. + * + * An error will be raised if the variable is not a Bitmap or BitmapRef. + * + * @param fcinfo name text The name of the bitmap variable. + *
bit_number int4 The bit to be tested. + * @return bool true if the bit was set + */ +Datum +veil_bitmap_testbit(PG_FUNCTION_ARGS) +{ + char *name; + Bitmap *bitmap; + int32 bit; + bool result; + + ensure_init(); + + bit = PG_GETARG_INT32(1); + name = strfromtext(PG_GETARG_TEXT_P(0)); + bitmap = GetBitmap(name, false, true); + + result = vl_BitmapTestbit(bitmap, bit); + PG_RETURN_BOOL(result); +} + + +PG_FUNCTION_INFO_V1(veil_bitmap_union); +/** + * veil_bitmap_union(result_name text, name2 text) returns bool + * Union the bitmap specified in parameter 1 with that in parameter 2, + * with the result in parameter 1. + * + * An error will be raised if the variables are not of type Bitmap or + * BitmapRef. + * + * @param fcinfo result_name text The target bitmap + *
name2 text The bitmap with which to union the target + * @return bool true + */ +Datum +veil_bitmap_union(PG_FUNCTION_ARGS) +{ + char *bitmap1_name; + char *bitmap2_name; + Bitmap *target; + Bitmap *source; + + ensure_init(); + + bitmap1_name = strfromtext(PG_GETARG_TEXT_P(0)); + bitmap2_name = strfromtext(PG_GETARG_TEXT_P(1)); + target = GetBitmap(bitmap1_name, false, true); + source = GetBitmap(bitmap2_name, false, true); + + if (target && source) { + vl_BitmapUnion(target, source); + } + + PG_RETURN_BOOL(true); +} + + +PG_FUNCTION_INFO_V1(veil_bitmap_intersect); +/** + * veil_bitmap_intersect(result_name text, name2 text) returns bool + * Intersect the bitmap specified in parameter 1 with that in parameter 2, + * with the result in parameter 1. + * + * An error will be raised if the variables are not of type Bitmap or + * BitmapRef. + * + * @param fcinfo result_name text The target bitmap + *
name2 text The bitmap with which to intersect the target + * @return bool true + */ +Datum +veil_bitmap_intersect(PG_FUNCTION_ARGS) +{ + char *bitmap1_name; + char *bitmap2_name; + Bitmap *target; + Bitmap *source; + + ensure_init(); + + bitmap1_name = strfromtext(PG_GETARG_TEXT_P(0)); + bitmap2_name = strfromtext(PG_GETARG_TEXT_P(1)); + target = GetBitmap(bitmap1_name, false, true); + source = GetBitmap(bitmap2_name, false, true); + + vl_BitmapIntersect(target, source); + PG_RETURN_BOOL(true); +} + + +PG_FUNCTION_INFO_V1(veil_bitmap_bits); +/** + * veil_bitmap_bits(name text) returns setof int4 + * Return the set of all bits set in the specified Bitmap or BitmapRef. + * + * @param fcinfo name text The name of the bitmap. + * @return setof int4The set of bits that are set in the + * bitmap. + */ +Datum +veil_bitmap_bits(PG_FUNCTION_ARGS) +{ + struct bitmap_bits_state { + Bitmap *bitmap; + int32 bit; + } *state; + FuncCallContext *funcctx; + MemoryContext oldcontext; + char *name; + bool found; + Datum datum; + + if (SRF_IS_FIRSTCALL()) + { + /* Only do this on first call for this result set */ + ensure_init(); + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + state = palloc(sizeof(struct bitmap_bits_state)); + MemoryContextSwitchTo(oldcontext); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + state->bitmap = GetBitmap(name, false, true); + + if (!state->bitmap) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Bitmap %s is not defined", + name), + errhint("Perhaps the name is mis-spelled, or its " + "definition is missing from veil_init()."))); + } + + state->bit = state->bitmap->bitzero; + funcctx->user_fctx = state; + } + + funcctx = SRF_PERCALL_SETUP(); + state = funcctx->user_fctx; + + state->bit = vl_BitmapNextBit(state->bitmap, state->bit, &found); + + if (found) { + datum = Int32GetDatum(state->bit); + state->bit++; + SRF_RETURN_NEXT(funcctx, datum); + } + else { + SRF_RETURN_DONE(funcctx); + } +} + +PG_FUNCTION_INFO_V1(veil_bitmap_range); +/** + * veil_bitmap_range(name text) returns veil_range_t + * Return composite type giving the range of the specified Bitmap or + * BitmapRef. + * + * @param fcinfo name text The name of the bitmap. + * @return veil_range_t Composite type containing the min + * and max values of the bitmap's range + */ +Datum +veil_bitmap_range(PG_FUNCTION_ARGS) +{ + char *name; + Bitmap *bitmap; + + ensure_init(); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + bitmap = GetBitmap(name, false, true); + + if (!bitmap) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Bitmap %s is not defined", name), + errhint("Perhaps the name is mis-spelled, or its " + "definition is missing from veil_init()."))); + } + + PG_RETURN_DATUM(datum_from_range(bitmap->bitzero, bitmap->bitmax)); +} + + +PG_FUNCTION_INFO_V1(veil_init_bitmap_array); +/** + * veil_init_bitmap_array(text, text, text) returns bool + * Create or reset a BitmapArray. + * An error will be raised if any parameter is not of the correct type. + * + * @param fcinfo bmarray text The name of the bitmap array. + *
array_range text Name of the Range variable that + * provides the range of the array part of the bitmap array. + *
bitmap_range text Name of the Range variable that + * provides the range of each bitmap in the array. + * @return bool True + */ +Datum +veil_init_bitmap_array(PG_FUNCTION_ARGS) +{ + char *bmarray_name; + char *arrayrange_name; + char *maprange_name; + VarEntry *bmarray_var; + BitmapArray *bmarray; + Range *arrayrange; + Range *maprange; + + ensure_init(); + + bmarray_name = strfromtext(PG_GETARG_TEXT_P(0)); + bmarray_var = vl_lookup_variable(bmarray_name); + bmarray = GetBitmapArrayFromVar(bmarray_var, true); + + arrayrange_name = strfromtext(PG_GETARG_TEXT_P(1)); + arrayrange = GetRange(arrayrange_name, false); + maprange_name = strfromtext(PG_GETARG_TEXT_P(2)); + maprange = GetRange(maprange_name, false); + + vl_NewBitmapArray(&bmarray, bmarray_var->shared, + arrayrange->min, arrayrange->max, + maprange->min, maprange->max); + + bmarray_var->obj = (Object *) bmarray; + + PG_RETURN_BOOL(true); +} + +PG_FUNCTION_INFO_V1(veil_clear_bitmap_array); +/** + * veil_clear_bitmap_array(bmarray text) returns bool + * Clear the bits in an existing BitmapArray. + * An error will be raised if the parameter is not of the correct type. + * + * @param fcinfo bmarray text The name of the BitmapArray. + * @return bool True + */ +Datum +veil_clear_bitmap_array(PG_FUNCTION_ARGS) +{ + char *bmarray_name; + VarEntry *bmarray_var; + BitmapArray *bmarray; + + ensure_init(); + + bmarray_name = strfromtext(PG_GETARG_TEXT_P(0)); + bmarray_var = vl_lookup_variable(bmarray_name); + bmarray = GetBitmapArrayFromVar(bmarray_var, false); + + vl_ClearBitmapArray(bmarray); + + PG_RETURN_BOOL(true); +} + +PG_FUNCTION_INFO_V1(veil_bitmap_from_array); +/** + * veil_bitmap_from_array(bmref text, bmarray text, index int4) returns text + * Place a reference to the specified Bitmap from a BitmapArray into + * the specified BitmapRef + * An error will be raised if any parameter is not of the correct type. + * + * @param fcinfo bmref text The name of the BitmapRef into which + * a reference to the relevant Bitmap will be placed. + *
bmarray text Name of the BitmapArray containing the Bitmap + * in which we are interested. + *
index int4 Index into the array of the bitmap in question. + * @return text The name of the BitmapRef + */ +Datum +veil_bitmap_from_array(PG_FUNCTION_ARGS) +{ + text *bmref_text; + char *bmref_name; + BitmapRef *bmref; + char *bmarray_name; + BitmapArray *bmarray; + int32 arrayelem; + Bitmap *bitmap; + + bmref_text = PG_GETARG_TEXT_P(0); + bmref_name = strfromtext(bmref_text); + bmref = GetBitmapRef(bmref_name); + + bmarray_name = strfromtext(PG_GETARG_TEXT_P(1)); + bmarray = GetBitmapArray(bmarray_name, false); + + arrayelem = PG_GETARG_INT32(2); + bitmap = vl_BitmapFromArray(bmarray, arrayelem); + if (!bitmap) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Range error for BitmapArray %s, %d not in %d - %d", + bmarray_name, arrayelem, + bmarray->arrayzero, bmarray->arraymax))); + } + + bmref->bitmap = bitmap; + bmref->xid = GetCurrentTransactionId(); + PG_RETURN_TEXT_P(bmref_text); +} + +PG_FUNCTION_INFO_V1(veil_bitmap_array_testbit); +/** + * veil_bitmap_array_testbit(bmarray text, arr_idx int4, bitno int4) returns bool + * Test a specified bit within a BitmapArray + * + * An error will be raised if the first parameter is not a BitmapArray. + * + * @param fcinfo bmarray text The name of the BitmapArray + *
arr_idx int4 Index of the Bitmap within the array. + *
bitno int4 Bit id of the bit within the Bitmap. + * @return bool True if the bit was set, false otherwise. + */ +Datum +veil_bitmap_array_testbit(PG_FUNCTION_ARGS) +{ + char *name; + BitmapArray *bmarray; + Bitmap *bitmap; + int32 arrayelem; + int32 bit; + + ensure_init(); + + arrayelem = PG_GETARG_INT32(1); + bit = PG_GETARG_INT32(2); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + bmarray = GetBitmapArray(name, false); + + bitmap = vl_BitmapFromArray(bmarray, arrayelem); + if (bitmap) { + PG_RETURN_BOOL(vl_BitmapTestbit(bitmap, bit)); + } + else { + PG_RETURN_BOOL(false); + } +} + + +PG_FUNCTION_INFO_V1(veil_bitmap_array_setbit); +/** + * veil_bitmap_array_setbit(bmarray text, arr_idx int4, bitno int4) returns bool + * Set a specified bit within a BitmapArray + * + * An error will be raised if the first parameter is not a BitmapArray. + * + * @param fcinfo bmarray text The name of the BitmapArray + *
arr_idx int4 Index of the Bitmap within the array. + *
ibitno nt4 Bit id of the bit within the Bitmap. + * @return bool True + */ +Datum +veil_bitmap_array_setbit(PG_FUNCTION_ARGS) +{ + char *name; + BitmapArray *bmarray; + Bitmap *bitmap; + int32 arrayelem; + int32 bit; + + ensure_init(); + + arrayelem = PG_GETARG_INT32(1); + bit = PG_GETARG_INT32(2); + name = strfromtext(PG_GETARG_TEXT_P(0)); + bmarray = GetBitmapArray(name, false); + + bitmap = vl_BitmapFromArray(bmarray, arrayelem); + if (bitmap) { + vl_BitmapSetbit(bitmap, bit); + PG_RETURN_BOOL(true); + } + else { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Bitmap Array range error (%d not in %d..%d)", + arrayelem, bmarray->arrayzero, bmarray->arraymax), + errdetail("Attempt to reference BitmapArray element " + "outside of the BitmapArray's defined range"))); + } + PG_RETURN_BOOL(true); +} + + +PG_FUNCTION_INFO_V1(veil_bitmap_array_clearbit); +/** + * veil_bitmap_array_clearbit(bmarray text, arr_idx int4, bitno int4) returns bool + * Clear a specified bit within a BitmapArray + * + * An error will be raised if the first parameter is not a BitmapArray. + * + * @param fcinfo bmarray text The name of the BitmapArray + *
arr_idx int4 Index of the Bitmap within the array. + *
bitno int4 Bit id of the bit within the Bitmap. + * @return bool True + */ +Datum +veil_bitmap_array_clearbit(PG_FUNCTION_ARGS) +{ + char *name; + BitmapArray *bmarray; + Bitmap *bitmap; + int32 arrayelem; + int32 bit; + + ensure_init(); + + arrayelem = PG_GETARG_INT32(1); + bit = PG_GETARG_INT32(2); + name = strfromtext(PG_GETARG_TEXT_P(0)); + bmarray = GetBitmapArray(name, false); + + bitmap = vl_BitmapFromArray(bmarray, arrayelem); + if (bitmap) { + vl_BitmapClearbit(bitmap, bit); + PG_RETURN_BOOL(true); + } + else { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Bitmap Array range error (%d not in %d..%d)", + arrayelem, bmarray->arrayzero, bmarray->arraymax), + errdetail("Attempt to reference BitmapArray element " + "outside of the BitmapArray's defined range"))); + } + PG_RETURN_BOOL(true); +} + + +PG_FUNCTION_INFO_V1(veil_union_from_bitmap_array); +/** + * veil_union_from_bitmap_array(bitmap text, bmarray text, arr_idx int4) returns bool + * Union a Bitmap with the specified Bitmap from a BitmapArray with the + * result placed into the first parameter. + * + * An error will be raised if the parameters are not of the correct types. + * + * @param fcinfo bitmap text The name of the Bitmap into which the + * resulting union will be placed. + *
bmarray text Name of the BitmapArray + *
arr_idx int4 Index of the required bitmap in the array + * @return bool True + */ +Datum +veil_union_from_bitmap_array(PG_FUNCTION_ARGS) +{ + char *bitmap_name; + char *bmarray_name; + Bitmap *target; + BitmapArray *bmarray; + Bitmap *bitmap; + int32 arrayelem; + + ensure_init(); + + arrayelem = PG_GETARG_INT32(2); + + bitmap_name = strfromtext(PG_GETARG_TEXT_P(0)); + bmarray_name = strfromtext(PG_GETARG_TEXT_P(1)); + target = GetBitmap(bitmap_name, false, true); + bmarray = GetBitmapArray(bmarray_name, false); + + bitmap = vl_BitmapFromArray(bmarray, arrayelem); + if (bitmap) { + vl_BitmapUnion(target, bitmap); + } + PG_RETURN_BOOL(true); +} + + +PG_FUNCTION_INFO_V1(veil_intersect_from_bitmap_array); +/** + * veil_intersect_from_bitmap_array(bitmap text, bmarray text, arr_idx int4) returns bool + * Intersect a Bitmap with the specified Bitmap from a BitmapArray with the + * result placed into the first parameter. + * + * An error will be raised if the parameters are not of the correct types. + * + * @param fcinfo bitmap text The name of the Bitmap into which the + * resulting intersection will be placed. + *
bmarray text Name of the BitmapArray + *
arr_idx int4 Index of the required bitmap in the array + * @return bool True + */ +Datum +veil_intersect_from_bitmap_array(PG_FUNCTION_ARGS) +{ + char *bitmap_name; + char *bmarray_name; + Bitmap *target; + BitmapArray *bmarray; + Bitmap *bitmap; + int32 arrayelem; + + ensure_init(); + + arrayelem = PG_GETARG_INT32(2); + + bitmap_name = strfromtext(PG_GETARG_TEXT_P(0)); + bmarray_name = strfromtext(PG_GETARG_TEXT_P(1)); + target = GetBitmap(bitmap_name, false, true); + bmarray = GetBitmapArray(bmarray_name, false); + + bitmap = vl_BitmapFromArray(bmarray, arrayelem); + if (bitmap) { + vl_BitmapIntersect(target, bitmap); + } + PG_RETURN_BOOL(true); +} + + +PG_FUNCTION_INFO_V1(veil_bitmap_array_bits); +/** + * veil_bitmap_array_bits(bmarray text, arr_idx int4) returns setof int4 + * Return the set of all bits set in the specified Bitmap from the + * BitmapArray. + * + * @param fcinfo bmarray text The name of the bitmap array. + *
arr_idx int4 Index of the required bitmap in the array + * @return setof int4The set of bits that are set in the + * bitmap. + */ +Datum +veil_bitmap_array_bits(PG_FUNCTION_ARGS) +{ + struct bitmap_array_bits_state { + Bitmap *bitmap; + int32 bit; + } *state; + FuncCallContext *funcctx; + MemoryContext oldcontext; + char *name; + bool found; + BitmapArray *bmarray; + int arrayelem; + Datum datum; + + if (SRF_IS_FIRSTCALL()) + { + /* Only do this on first call for this result set */ + ensure_init(); + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + state = palloc(sizeof(struct bitmap_array_bits_state)); + MemoryContextSwitchTo(oldcontext); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + arrayelem = PG_GETARG_INT32(1); + bmarray = GetBitmapArray(name, false); + + if (!bmarray) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("BitmapArray %s is not defined", + name), + errhint("Perhaps the name is mis-spelled, or its " + "definition is missing from " + "veil_init()."))); + } + + state->bitmap = vl_BitmapFromArray(bmarray, arrayelem); + if (!state->bitmap) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Bitmap Array range error (%d not in %d..%d)", + arrayelem, bmarray->arrayzero, bmarray->arraymax), + errdetail("Attempt to reference BitmapArray element " + "outside of the BitmapArray's defined range"))); + } + + state->bit = state->bitmap->bitzero; + funcctx->user_fctx = state; + } + + funcctx = SRF_PERCALL_SETUP(); + state = funcctx->user_fctx; + + state->bit = vl_BitmapNextBit(state->bitmap, state->bit, &found); + + if (found) { + datum = Int32GetDatum(state->bit); + state->bit++; + SRF_RETURN_NEXT(funcctx, datum); + } + else { + SRF_RETURN_DONE(funcctx); + } +} + +PG_FUNCTION_INFO_V1(veil_bitmap_array_arange); +/** + * veil_bitmap_array_arange(bmarray text) returns veil_range_t + * Return composite type giving the range of the array part of the + * specified BitmapArray + * + * @param fcinfo bmarray text The name of the bitmap array. + * @return veil_range_t Composite type containing the min + * and max indices of the array + */ +Datum +veil_bitmap_array_arange(PG_FUNCTION_ARGS) +{ + char *name; + BitmapArray *bmarray; + + ensure_init(); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + bmarray = GetBitmapArray(name, false); + + if (!bmarray) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("BitmapArray %s is not defined", + name), + errhint("Perhaps the name is mis-spelled, or its " + "definition is missing from veil_init()."))); + } + + PG_RETURN_DATUM(datum_from_range(bmarray->arrayzero, bmarray->arraymax)); +} + + +PG_FUNCTION_INFO_V1(veil_bitmap_array_brange); +/** + * veil_bitmap_array_brange(bmarray text) returns veil_range_t + * Return composite type giving the range of every Bitmap within + * the BitmapArray. + * + * @param fcinfo bmarray text The name of the bitmap array. + * @return veil_range_t Composite type containing the min + * and max values of the bitmap array's range + */ +Datum +veil_bitmap_array_brange(PG_FUNCTION_ARGS) +{ + char *name; + BitmapArray *bmarray; + + ensure_init(); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + bmarray = GetBitmapArray(name, false); + + if (!bmarray) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("BitmapArray %s is not defined", + name), + errhint("Perhaps the name is mis-spelled, or its " + "definition is missing from veil_init()."))); + } + + PG_RETURN_DATUM(datum_from_range(bmarray->bitzero, bmarray->bitmax)); +} + + + +PG_FUNCTION_INFO_V1(veil_init_bitmap_hash); +/** + * veil_init_bitmap_hash(bmhash text, range text) returns bool + * Create or reset a BitmapHash. + * An error will be raised if any parameter is not of the correct type. + * + * @param fcinfo bmhash text The name of the bitmap hash. + *
range text Name of the Range variable that provides the + * range of each bitmap in the hash. + * @return bool True + */ +Datum +veil_init_bitmap_hash(PG_FUNCTION_ARGS) +{ + char *bmhash_name; + char *range_name; + VarEntry *bmhash_var; + BitmapHash *bmhash; + Range *range; + + ensure_init(); + + bmhash_name = strfromtext(PG_GETARG_TEXT_P(0)); + bmhash_var = vl_lookup_variable(bmhash_name); + bmhash = GetBitmapHashFromVar(bmhash_var, true); + + range_name = strfromtext(PG_GETARG_TEXT_P(1)); + range = GetRange(range_name, false); + + if (bmhash_var->shared) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("illegal attempt to define shared BitmapHash %s", + bmhash_name), + errhint("BitmapHashes may only be defined as session, " + "not shared, variables."))); + } + vl_NewBitmapHash(&bmhash, bmhash_name, + range->min, range->max); + + bmhash_var->obj = (Object *) bmhash; + + PG_RETURN_BOOL(true); +} + + +PG_FUNCTION_INFO_V1(veil_clear_bitmap_hash); +/** + * veil_clear_bitmap_hash(bmhash text) returns bool + * Clear the bits in an existing BitmapHash. + * An error will be raised if the parameter is not of the correct type. + * + * @param fcinfo bmhash text The name of the BitmapHash. + * @return bool True + */ +Datum +veil_clear_bitmap_hash(PG_FUNCTION_ARGS) +{ + char *bmhash_name; + VarEntry *bmhash_var; + BitmapHash *bmhash; + + ensure_init(); + + bmhash_name = strfromtext(PG_GETARG_TEXT_P(0)); + bmhash_var = vl_lookup_variable(bmhash_name); + bmhash = GetBitmapHashFromVar(bmhash_var, true); + + vl_NewBitmapHash(&bmhash, bmhash_name, + bmhash->bitzero, bmhash->bitmax); + + bmhash_var->obj = (Object *) bmhash; + + PG_RETURN_BOOL(true); +} + + +PG_FUNCTION_INFO_V1(veil_bitmap_hash_key_exists); +/** + * veil_bitmap_hashkey_exists(bmhash text, key text) returns bool + * Return true if the key exists in the bitmap hash. + * + * @param fcinfo bmhash text Name of the BitmapHashin which we are + * interested. + *
key text Key, into the hash, of the bitmap in question. + * @return boolean Whether the key is present in the BitmapHash + */ +Datum +veil_bitmap_hash_key_exists(PG_FUNCTION_ARGS) +{ + char *bmhash_name; + BitmapHash *bmhash; + char *hashelem; + bool found; + + bmhash_name = strfromtext(PG_GETARG_TEXT_P(0)); + bmhash = GetBitmapHash(bmhash_name, false); + + hashelem = strfromtext(PG_GETARG_TEXT_P(1)); + + found = vl_BitmapHashHasKey(bmhash, hashelem); + + PG_RETURN_BOOL(found); +} + +PG_FUNCTION_INFO_V1(veil_bitmap_from_hash); +/** + * veil_bitmap_from_hash(bmref text, bmhash text, key text) returns text + * Place a reference to the specified Bitmap from a BitmapHash into + * the specified BitmapRef + * An error will be raised if any parameter is not of the correct type. + * + * @param fcinfo bmref text The name of the BitmapRef into which + * a reference to the relevant Bitmap will be placed. + *
bmhash text Name of the BitmapHash containing the Bitmap + * in which we are interested. + *
key text Key, into the hash, of the bitmap in question. + * @return text The name of the BitmapRef + */ +Datum +veil_bitmap_from_hash(PG_FUNCTION_ARGS) +{ + text *bmref_text; + char *bmref_name; + BitmapRef *bmref; + char *bmhash_name; + BitmapHash *bmhash; + char *hashelem; + Bitmap *bitmap; + + bmref_text = PG_GETARG_TEXT_P(0); + bmref_name = strfromtext(bmref_text); + bmref = GetBitmapRef(bmref_name); + + bmhash_name = strfromtext(PG_GETARG_TEXT_P(1)); + bmhash = GetBitmapHash(bmhash_name, false); + + hashelem = strfromtext(PG_GETARG_TEXT_P(2)); + bitmap = vl_AddBitmapToHash(bmhash, hashelem); + + bmref->bitmap = bitmap; + bmref->xid = GetCurrentTransactionId(); + PG_RETURN_TEXT_P(bmref_text); +} + + +PG_FUNCTION_INFO_V1(veil_bitmap_hash_testbit); +/** + * veil_bitmap_hash_testbit(bmhash text, key text, bitno int4) returns bool + * Test a specified bit within a BitmapHash + * + * An error will be raised if the first parameter is not a BitmapHash. + * + * @param fcinfo bmhash text The name of the BitmapHash + *
key text Key of the Bitmap within the hash. + *
bitno int4 Bit id of the bit within the Bitmap. + * @return bool True if the bit was set, false otherwise. + */ +Datum +veil_bitmap_hash_testbit(PG_FUNCTION_ARGS) +{ + char *name; + BitmapHash *bmhash; + char *hashelem; + Bitmap *bitmap; + int32 bit; + + ensure_init(); + + hashelem = strfromtext(PG_GETARG_TEXT_P(1)); + bit = PG_GETARG_INT32(2); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + bmhash = GetBitmapHash(name, false); + + bitmap = vl_BitmapFromHash(bmhash, hashelem); + if (bitmap) { + PG_RETURN_BOOL(vl_BitmapTestbit(bitmap, bit)); + } + else { + PG_RETURN_BOOL(false); + } +} + + +PG_FUNCTION_INFO_V1(veil_bitmap_hash_setbit); +/** + * veil_bitmap_hash_setbit(bmhash text, key text, bitno int4) returns bool + * Set a specified bit within a BitmapHash + * + * An error will be raised if the first parameter is not a BitmapHash. + * + * @param fcinfo bmhash text The name of the BitmapHash + *
key text Key of the Bitmap within the hash. + *
bitno int4 Bit id of the bit within the Bitmap. + * @return bool True + */ +Datum +veil_bitmap_hash_setbit(PG_FUNCTION_ARGS) +{ + char *name; + BitmapHash *bmhash; + Bitmap *bitmap; + char *hashelem; + int32 bit; + + ensure_init(); + + hashelem = strfromtext(PG_GETARG_TEXT_P(1)); + bit = PG_GETARG_INT32(2); + name = strfromtext(PG_GETARG_TEXT_P(0)); + bmhash = GetBitmapHash(name, false); + + bitmap = vl_AddBitmapToHash(bmhash, hashelem); + + vl_BitmapSetbit(bitmap, bit); + PG_RETURN_BOOL(true); +} + + +PG_FUNCTION_INFO_V1(veil_bitmap_hash_clearbit); +/** + * veil_bitmap_hash_clearbit(bmhash text, key text, bitno int4) returns bool + * Clear a specified bit within a BitmapHash + * + * An error will be raised if the first parameter is not a BitmapHash. + * + * @param fcinfo bmhash text The name of the BitmapHash + *
key text Key of the Bitmap within the hash. + *
bitno int4 Bit id of the bit within the Bitmap. + * @return bool True + */ +Datum +veil_bitmap_hash_clearbit(PG_FUNCTION_ARGS) +{ + char *name; + BitmapHash *bmhash; + Bitmap *bitmap; + char *hashelem; + int32 bit; + + ensure_init(); + + hashelem = strfromtext(PG_GETARG_TEXT_P(1)); + bit = PG_GETARG_INT32(2); + name = strfromtext(PG_GETARG_TEXT_P(0)); + bmhash = GetBitmapHash(name, false); + + bitmap = vl_AddBitmapToHash(bmhash, hashelem); + + vl_BitmapClearbit(bitmap, bit); + PG_RETURN_BOOL(true); +} + + +PG_FUNCTION_INFO_V1(veil_union_into_bitmap_hash); +/** + * veil_union_into_bitmap_hash(bmhash text, key text, bitmap text) returns bool + * Union a Bitmap with the specified Bitmap from a BitmapHash with the + * result placed into the bitmap hash. + * + * An error will be raised if the parameters are not of the correct types. + * + * @param fcinfo bmhash text Name of the BitmapHash + *
key text Key of the required bitmap in the hash + *
bitmap text The name of the Bitmap into which the + * resulting union will be placed. + * @return bool True + */ +Datum +veil_union_into_bitmap_hash(PG_FUNCTION_ARGS) +{ + char *bitmap_name; + char *bmhash_name; + Bitmap *target; + BitmapHash *bmhash; + Bitmap *bitmap; + char *hashelem; + + ensure_init(); + + bmhash_name = strfromtext(PG_GETARG_TEXT_P(0)); + hashelem = strfromtext(PG_GETARG_TEXT_P(1)); + + bitmap_name = strfromtext(PG_GETARG_TEXT_P(2)); + bitmap = GetBitmap(bitmap_name, false, true); + bmhash = GetBitmapHash(bmhash_name, false); + + target = vl_AddBitmapToHash(bmhash, hashelem); + if (target && bitmap) { + vl_BitmapUnion(target, bitmap); + } + PG_RETURN_BOOL(true); +} + +PG_FUNCTION_INFO_V1(veil_union_from_bitmap_hash); +/** + * veil_union_from_bitmap_hash(bmhash text, key text, bitmap text) returns bool + * Union a Bitmap with the specified Bitmap from a BitmapHash with the + * result placed into the bitmap parameter. + * + * An error will be raised if the parameters are not of the correct types. + * + * @param fcinfo bmhash text The name of the Bitmap into which the + * resulting union will be placed. + *
key text Name of the BitmapHash + *
bitmap text Key of the required bitmap in the hash + * @return bool True + */ +Datum +veil_union_from_bitmap_hash(PG_FUNCTION_ARGS) +{ + char *bitmap_name; + char *bmhash_name; + Bitmap *target; + BitmapHash *bmhash; + Bitmap *bitmap; + char *hashelem; + + ensure_init(); + + hashelem = strfromtext(PG_GETARG_TEXT_P(2)); + + bitmap_name = strfromtext(PG_GETARG_TEXT_P(0)); + bmhash_name = strfromtext(PG_GETARG_TEXT_P(1)); + target = GetBitmap(bitmap_name, false, true); + bmhash = GetBitmapHash(bmhash_name, false); + + bitmap = vl_BitmapFromHash(bmhash, hashelem); + if (bitmap) { + vl_BitmapUnion(target, bitmap); + } + PG_RETURN_BOOL(true); +} + +PG_FUNCTION_INFO_V1(veil_intersect_from_bitmap_hash); +/** + * veil_intersect_from_bitmap_hash(bitmap text, bmhash text, key text) returns bool + * Intersect a Bitmap with the specified Bitmap from a BitmapArray with the + * result placed into the bitmap parameter. + * + * An error will be raised if the parameters are not of the correct types. + * + * @param fcinfo bitmap text The name of the Bitmap into which the + * resulting intersection will be placed. + *
bmhash text Name of the BitmapHash + *
key text Key of the required bitmap in the hash + * @return bool True + */ +Datum +veil_intersect_from_bitmap_hash(PG_FUNCTION_ARGS) +{ + char *bitmap_name; + char *bmhash_name; + Bitmap *target; + BitmapHash *bmhash; + Bitmap *bitmap; + char *hashelem; + + ensure_init(); + + hashelem = strfromtext(PG_GETARG_TEXT_P(2)); + + bitmap_name = strfromtext(PG_GETARG_TEXT_P(0)); + bmhash_name = strfromtext(PG_GETARG_TEXT_P(1)); + target = GetBitmap(bitmap_name, false, true); + bmhash = GetBitmapHash(bmhash_name, false); + + bitmap = vl_BitmapFromHash(bmhash, hashelem); + if (bitmap) { + vl_BitmapIntersect(target, bitmap); + } + else { + /* The bitmap from the hash does not exist, so it is logically + * empty. Intersection with an empty set yields an empty set. */ + vl_ClearBitmap(target); + } + PG_RETURN_BOOL(true); +} + +PG_FUNCTION_INFO_V1(veil_bitmap_hash_bits); +/** + * veil_bitmap_hash_bits(bmhash text, key text) returns setof int4 + * Return the set of all bits set in the specified Bitmap from the + * BitmapHash. + * + * @param fcinfo bmhashtext The name of the bitmap hash. + *
key text Key of the required bitmap in the hash + * @return setof int4The set of bits that are set in the + * bitmap. + */ +Datum +veil_bitmap_hash_bits(PG_FUNCTION_ARGS) +{ + struct bitmap_hash_bits_state { + Bitmap *bitmap; + int32 bit; + } *state; + FuncCallContext *funcctx; + MemoryContext oldcontext; + char *name; + bool found; + BitmapHash *bmhash; + char *hashelem; + Datum datum; + + if (SRF_IS_FIRSTCALL()) + { + /* Only do this on first call for this result set */ + ensure_init(); + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + state = palloc(sizeof(struct bitmap_hash_bits_state)); + MemoryContextSwitchTo(oldcontext); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + hashelem = strfromtext(PG_GETARG_TEXT_P(1)); + bmhash = GetBitmapHash(name, false); + + if (!bmhash) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Bitmap Hash %s not defined", name))); + } + + state->bitmap = vl_BitmapFromHash(bmhash, hashelem); + if (!state->bitmap) { + SRF_RETURN_DONE(funcctx); + } + + state->bit = state->bitmap->bitzero; + funcctx->user_fctx = state; + } + + funcctx = SRF_PERCALL_SETUP(); + state = funcctx->user_fctx; + + state->bit = vl_BitmapNextBit(state->bitmap, state->bit, &found); + + if (found) { + datum = Int32GetDatum(state->bit); + state->bit++; + SRF_RETURN_NEXT(funcctx, datum); + } + else { + SRF_RETURN_DONE(funcctx); + } +} + +PG_FUNCTION_INFO_V1(veil_bitmap_hash_range); +/** + * veil_bitmap_hash_range(bmhash text) returns veil_range_t + * Return composite type giving the range of every Bitmap within + * the BitmapHash. + * + * @param fcinfo bmhash text The name of the bitmap array. + * @return veil_range_t Composite type containing the min + * and max values of the bitmap hash's range + */ +Datum +veil_bitmap_hash_range(PG_FUNCTION_ARGS) +{ + char *name; + BitmapHash *bmhash; + + ensure_init(); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + bmhash = GetBitmapHash(name, false); + + if (!bmhash) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Bitmap Hash %s not defined", name))); + } + + PG_RETURN_DATUM(datum_from_range(bmhash->bitzero, bmhash->bitmax)); +} + + +PG_FUNCTION_INFO_V1(veil_bitmap_hash_entries); +/** + * veil_bitmap_hash_entries(bmhash text) returns setof text + * Return the key of every Bitmap within the BitmapHash. + * + * @param fcinfo bmhash text The name of the bitmap hash. + * @return setof text Every key in the hash. + */ +Datum +veil_bitmap_hash_entries(PG_FUNCTION_ARGS) +{ + struct bitmap_hash_entries_state { + BitmapHash *bmhash; + VarEntry *var; + } *state; + FuncCallContext *funcctx; + MemoryContext oldcontext; + char *name; + Datum datum; + text *result; + + if (SRF_IS_FIRSTCALL()) + { + /* Only do this on first call for this result set */ + ensure_init(); + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + state = palloc(sizeof(struct bitmap_hash_entries_state)); + MemoryContextSwitchTo(oldcontext); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + state->bmhash = GetBitmapHash(name, false); + + if (!state->bmhash) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Bitmap Hash %s not defined", name))); + } + + state->var = NULL; + funcctx->user_fctx = state; + } + + funcctx = SRF_PERCALL_SETUP(); + state = funcctx->user_fctx; + + state->var = vl_NextHashEntry(state->bmhash->hash, state->var); + + if (state->var) { + result = textfromstrn(state->var->key, HASH_KEYLEN); + datum = PointerGetDatum(result); + SRF_RETURN_NEXT(funcctx, datum); + } + else { + SRF_RETURN_DONE(funcctx); + } +} + + +PG_FUNCTION_INFO_V1(veil_int4_set); +/** + * veil_int4_set(name text,value int4) returns int4 + * Set an Int4Var variable type to a specified value. + * An Error will be raised if the variable is not defined or is of the + * wrong type. + * + * @param fcinfo name text The name of the int4 variable. + * @param fcinfo value int4 The value to be set (may be null). + * @return int4 The new value of the variable. + */ +Datum +veil_int4_set(PG_FUNCTION_ARGS) +{ + char *name; + Int4Var *var; + int32 value; + + ensure_init(); + + if (PG_ARGISNULL(0)) { + PG_RETURN_NULL(); + } + + name = strfromtext(PG_GETARG_TEXT_P(0)); + var = GetInt4Var(name, true); + + if (PG_ARGISNULL(1)) { + var->isnull = true; + PG_RETURN_NULL(); + } + else { + value = PG_GETARG_INT32(1); + var->isnull = false; + var->value = value; + PG_RETURN_INT32(value); + } +} + +PG_FUNCTION_INFO_V1(veil_int4_get); +/** + * veil_int4_get(name text) returns int4 + * Return the value of an Int4Var variable. + * An Error will be raised if the variable is not defined or is of the + * wrong type. + * + * @param fcinfo name text The name of the int4 variable. + * @return int4 The value of the variable. + */ +Datum +veil_int4_get(PG_FUNCTION_ARGS) +{ + char *name; + Int4Var *var; + + ensure_init(); + + name = strfromtext(PG_GETARG_TEXT_P(0)); + var = GetInt4Var(name, true); + + if (var->isnull) { + PG_RETURN_NULL(); + } + else { + PG_RETURN_INT32(var->value); + } +} + +PG_FUNCTION_INFO_V1(veil_init_int4array); +/** + * veil_init_int4array(arrayname text, range text) returns bool + * Initialise an Int4Array variable. Each entry in the array will be + * zeroed. + * + * @param fcinfo arrayname text The name of the Int4Array variable. + *
range text Name of the range variable defining the size of + * the array. + * @return bool True + */ +Datum +veil_init_int4array(PG_FUNCTION_ARGS) +{ + char *array_name; + char *range_name; + VarEntry *array_var; + Int4Array *array; + Range *range; + + ensure_init(); + + array_name = strfromtext(PG_GETARG_TEXT_P(0)); + array_var = vl_lookup_variable(array_name); + array = GetInt4ArrayFromVar(array_var, true); + + range_name = strfromtext(PG_GETARG_TEXT_P(1)); + range = GetRange(range_name, false); + + array = vl_NewInt4Array(array, array_var->shared, range->min, range->max); + array_var->obj = (Object *) array; + + PG_RETURN_BOOL(true); +} + +PG_FUNCTION_INFO_V1(veil_clear_int4array); +/** + * veil_clear_int4array(name text) returns bool + * Clear an Int4Array variable. Each entry in the array will be + * zeroed. + * + * @param fcinfo name text The name of the Int4Array variable. + * @return bool True + */ +Datum +veil_clear_int4array(PG_FUNCTION_ARGS) +{ + char *array_name; + Int4Array *array; + + ensure_init(); + + array_name = strfromtext(PG_GETARG_TEXT_P(0)); + array = GetInt4Array(array_name, false); + + vl_ClearInt4Array(array); + + PG_RETURN_BOOL(true); +} + +PG_FUNCTION_INFO_V1(veil_int4array_set); +/** + * veil_int4array_set(array text, idx int4, value int4) returns int4 + * Set an Int4Array entry. + * + * @param fcinfo array text The name of the Int4Array variable. + *
idx int4 Index of the entry to be set + *
value int4 Value to which the entry will be set + * @return int4 The new value of the array entry + */ +Datum +veil_int4array_set(PG_FUNCTION_ARGS) +{ + char *array_name; + Int4Array *array; + int32 idx; + int32 value; + + ensure_init(); + + array_name = strfromtext(PG_GETARG_TEXT_P(0)); + array = GetInt4Array(array_name, false); + idx = PG_GETARG_INT32(1); + value = PG_GETARG_INT32(2); + vl_Int4ArraySet(array, idx, value); + + PG_RETURN_INT32(value); +} + +PG_FUNCTION_INFO_V1(veil_int4array_get); +/** + * veil_int4array_get(array text, idx int4) returns int4 + * Get an Int4Array entry. + * + * @param fcinfo array text The name of the Int4Array variable. + *
idx int4 Index of the entry to be retrieved + * @return int4 The value of the array entry + */ +Datum +veil_int4array_get(PG_FUNCTION_ARGS) +{ + char *array_name; + Int4Array *array; + int32 idx; + int32 value; + + ensure_init(); + + array_name = strfromtext(PG_GETARG_TEXT_P(0)); + array = GetInt4Array(array_name, false); + idx = PG_GETARG_INT32(1); + value = vl_Int4ArrayGet(array, idx); + + PG_RETURN_INT32(value); +} + + +PG_FUNCTION_INFO_V1(veil_init); +/** + * veil_init(doing_reset bool) returns bool + * Initialise or reset a veil session. + * The boolean parameter will be false when called for initialisation, + * and true when performing a reset. + * + * This function must be redefined as a custom function in your + * implementation. + * + * @param fcinfo doing_reset bool Whether we are being + * called in order to reset (true) the session or (false) simply to + * initialise it. + * @return bool True + */ +Datum +veil_init(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("default veil version of veil_init() has been called"), + errhint("You must define your own version of this function."))); + + PG_RETURN_BOOL(true); +} + +PG_FUNCTION_INFO_V1(veil_perform_reset); +/** + * veil_perform_reset() returns bool + * Reset veil shared memory for this database. This creates a new + * shared memory context with none of the existing shared variables. + * All current transactions will be able to continue using the current + * variables, all new transactions will see the new set, once this + * function has completed. + * + * @param fcinfo + * @return bool True if the function is able to complete + * successfully. If it is unable, no harm will have been done but + * neither will a memory reset have been performed. + */ +Datum +veil_perform_reset(PG_FUNCTION_ARGS) +{ + bool success = true; + bool result; + int ok; + + ensure_init(); + ok = vl_spi_connect(); + if (ok != SPI_OK_CONNECT) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to perform reset"), + errdetail("SPI_connect() failed, returning %d.", ok))); + } + + success = vl_prepare_context_switch(); + if (success) { + result = vl_bool_from_query("select veil_init(TRUE)", &success); + elog(NOTICE, "veil_init returns %s to veil_perform_reset", + result? "true": "false"); + success = vl_complete_context_switch(); + elog(NOTICE, + "vl_complete_context_switch returns %s to veil_perform_reset", + success? "true": "false"); + success &= result; + } + else { + ereport(WARNING, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to perform reset"), + errdetail("Unable to prepare for memory reset. " + "Maybe another process is performing a reset, " + "or maybe there is a long-running transaction that " + "is still using the previous memory context."))); + } + + ok = vl_spi_finish(); + PG_RETURN_BOOL(success); +} + +PG_FUNCTION_INFO_V1(veil_force_reset); +/** + * veil_force_reset() returns bool + * Reset veil shared memory for this database, ignoring existing + * transactions. This function will always reset the shared memory + * context, even for sessions that are still using it. Having taken + * this drastic action, it will then cause a panic to reset the server. + * + * Question - won't a panic reset the shared memory in any case? Is the + * panic superfluous, or maybe is this entire function superfluous? + * + * @param fcinfo + * @return bool True. + */ +Datum +veil_force_reset(PG_FUNCTION_ARGS) +{ + ensure_init(); + vl_force_context_switch(); + PG_RETURN_BOOL(true); +} + + +PG_FUNCTION_INFO_V1(veil_version); +/** + * veil_version() returns text + * Return a string describing this version of veil. + * + * @param fcinfo + * @return text String describing the version. + */ +Datum +veil_version(PG_FUNCTION_ARGS) +{ + char *version_str; + text *version_text; + + version_str = palloc(sizeof(char) * strlen(VEIL_VERSION) + + strlen(VEIL_VERSION_INFO) + 4); + sprintf(version_str, "%s (%s)", VEIL_VERSION, VEIL_VERSION_INFO); + + version_text = textfromstr(version_str); + PG_RETURN_TEXT_P(version_text); +} + +PG_FUNCTION_INFO_V1(veil_serialise); +/** + * veil_serialise(varname text) returns text + * Return a string representing the contents of our variable. + * + * @param fcinfo + *
varname text Name of the variable to be serialised. + * @return text String containing the serialised variable. + */ +Datum +veil_serialise(PG_FUNCTION_ARGS) +{ + char *name; + char *result; + + ensure_init(); + + if (PG_ARGISNULL(0)) { + PG_RETURN_NULL(); + } + + name = strfromtext(PG_GETARG_TEXT_P(0)); + result = vl_serialise_var(name); + + if (result) { + PG_RETURN_TEXT_P(textfromstr(result)); + } + else { + PG_RETURN_NULL(); + } +} + + +PG_FUNCTION_INFO_V1(veil_deserialise); +/** + * veil_deserialise(stream text) returns text + * Create or reset variables based on the output of previous + * veil_serialise calls. + * + * @param fcinfo + *
stream text Serialised variables string + * @return int4 Count of the items de-serialised from + * the string. + */ +Datum +veil_deserialise(PG_FUNCTION_ARGS) +{ + char *stream; + int4 result; + text *txt; + + ensure_init(); + + if (PG_ARGISNULL(0)) { + PG_RETURN_NULL(); + } + + txt = PG_GETARG_TEXT_P(0); + stream = strfromtext(txt); + result = vl_deserialise(&stream); + + PG_RETURN_INT32(result); +} + + diff --git a/src/veil_interface.d b/src/veil_interface.d new file mode 100644 index 0000000..a242884 --- /dev/null +++ b/src/veil_interface.d @@ -0,0 +1,5 @@ +src/veil_interface.o src/veil_interface.d: \ + src/veil_interface.c \ + src/veil_version.h \ + src/veil_funcs.h \ + src/veil_datatypes.h diff --git a/src/veil_interface.sqs b/src/veil_interface.sqs new file mode 100644 index 0000000..25b0036 --- /dev/null +++ b/src/veil_interface.sqs @@ -0,0 +1,743 @@ +/* ---------- + * veil_interface.sqs (or a file derived from it) + * + * Source file from which veil_interface_trial.sql and + * veil--.sql is generated using sed. + * + * Copyright (c) 2005 - 2011 Marc Munro + * Author: Marc Munro + * License: BSD + * + * ---------- + */ + +create type veil_range_t as ( + min int4, + max int4 +); +comment on type veil_range_t is +'Veil type used to record ranges. A range is a pair of integers identifying +the minimum and maximum values of the range. Ranges are used to +constrain the size of a bitmap or bitmap array.'; + +create type veil_variable_t as ( + name text, + type text, + shared bool +); +comment on type veil_variable_t is +'Veil type used as the result type of veil_variables(), to describe each +variable known to a veil instance.'; + + +create or replace +function veil_share(name text) returns bool + as '@LIBPATH@', 'veil_share' + language C stable strict; + +comment on function veil_share(name text) is +'Create a shared variable named NAME. + +Return TRUE if successful, else raise an error. + +Create a veil variable as a shared variable. Variables are named +containers for various types of values. They are referenced by name, +passing the name as a string to the various veil functions. + +Variables may be shared variables in which case they are available, with +the same value, to all sessions, or session variables in which case each +session will have its own instance. Variables are by default created as +session variables, and are created by assignment, or initialisation. To +create a shared variable, call veil_share() with the name of the +variable you wish to create, then create and use the variable as you +would a session variable. Shared variables should only be created and +initialised from veil_init() in order to prevent race conditions. If a +variable has already been created as a session variable, it cannot be +repurposed as a shared variable.'; + + +create or replace +function veil_variables() returns setof veil_variable_t + as '@LIBPATH@', 'veil_variables' + language C stable; + +comment on function veil_variables() is +'List all current veil_variables. +Return a set of veil_variable_t results, detailing each existant +variable + +This is intended for interactive use for debugging purposes.'; + + +create or replace +function veil_init_range(name text, min int4, max int4) returns int4 + as '@LIBPATH@', 'veil_init_range' + language C stable strict; + +comment on function veil_init_range(text, int4, int4) is +'Initialise a Range variable called NAME constrained by MIN and MAX. + +Return the number of elements in the range. + +Ranges may be examined using the veil_range() function.'; + + +create or replace +function veil_range(name text) returns veil_range_t + as '@LIBPATH@', 'veil_range' + language C stable strict; + +comment on function veil_range(name text) is +'Return the range for range variable NAME.'; + + +create or replace +function veil_init_bitmap(bitmap_name text, range_name text) returns bool + as '@LIBPATH@', 'veil_init_bitmap' + language C stable strict; + +comment on function veil_init_bitmap(text, text) is +'Create or re-initialise the Bitmap named BITMAP_NAME, for the range of +bits given by RANGE_NAME. + +Return TRUE on success, raise an error otherwise. + +All bits in the bitmap will be zero (cleared).'; + + +create or replace +function veil_clear_bitmap(name text) returns bool + as '@LIBPATH@', 'veil_clear_bitmap' + language C stable strict; + +comment on function veil_clear_bitmap(text) is +'Clear the Bitmap or BitmapRef identified by NAME. + +Return TRUE, or raise an error. + +Clear (set to zero) all bits in the named bitmap.'; + + +create or replace +function veil_bitmap_setbit(name text, bit_number int4) returns bool + as '@LIBPATH@', 'veil_bitmap_setbit' + language C stable strict; + +comment on function veil_bitmap_setbit(text, int4) is +'In the Bitmap or BitmapRef identified by NAME, set the bit given by +BIT_NUMBER. + +Return TRUE or raise an error. + +Set to 1, the identified bit.'; + + +create or replace +function veil_bitmap_clearbit(name text, bit_number int4) returns bool + as '@LIBPATH@', 'veil_bitmap_clearbit' + language C stable strict; + +comment on function veil_bitmap_clearbit(text, int4) is +'In the Bitmap or BitmapRef identified by NAME, clear the bit given by +BIT_NUMBER. + +Return TRUE or raise an error. + +Set to 0, the identified bit.'; + + +create or replace +function veil_bitmap_testbit(name text, bit_number int4) returns bool + as '@LIBPATH@', 'veil_bitmap_testbit' + language C stable strict; + +comment on function veil_bitmap_testbit(text, int4) is +'In the Bitmap or BitmapRef identified by NAME, test the bit given by +BIT_NUMBER. + +Return TRUE if the bit is set, FALSE if it is zero.'; + + +create or replace +function veil_bitmap_union(result_name text, name2 text) returns bool + as '@LIBPATH@', 'veil_bitmap_union' + language C stable strict; + +comment on function veil_bitmap_union(text, text) is +'Union two Bitmaps, RESULT_NAME and NAME2, with the result going into +the first. + +Return TRUE, or raise an error.'; + + +create or replace +function veil_bitmap_intersect(result_name text, name2 text) returns bool + as '@LIBPATH@', + 'veil_bitmap_intersect' + language C stable strict; + +comment on function veil_bitmap_intersect(text, text) is +'Intersect two Bitmaps, RESULT_NAME and NAME2, with the result going into +the first. + +Return TRUE, or raise an error.'; + + +create or replace +function veil_bitmap_bits(name text) returns setof int4 + as '@LIBPATH@', 'veil_bitmap_bits' + language C stable strict; + +comment on function veil_bitmap_bits(text) is +'Return each bit in the bitmap NAME. + +This is primarily intended for interactive use for debugging, etc.'; + + +create or replace +function veil_bitmap_range(name text) returns veil_range_t + as '@LIBPATH@', 'veil_bitmap_range' + language C stable strict; + +comment on function veil_bitmap_range(text) is +'Return the range of bitmap NAME. + +It is primarily intended for interactive use.'; + + + +create or replace +function veil_init_bitmap_array(bmarray text, array_range text, + bitmap_range text) returns bool + as '@LIBPATH@', 'veil_init_bitmap_array' + language C stable strict; + +comment on function veil_init_bitmap_array(text, text, text) is +'Creates or resets (clears) BMARRAY, to have ARRAY_RANGE bitmaps of +BITMAP_RANGE bits. + +Returns TRUE or raises an error'; + + +create or replace +function veil_clear_bitmap_array(bmarray text) returns bool + as '@LIBPATH@', 'veil_clear_bitmap_array' + language C stable strict; + +comment on function veil_clear_bitmap_array(text) is +'Clear all bits in all bitmaps of bitmap array BMARRAY. + +Return TRUE or raise an error'; + + +create or replace +function veil_bitmap_from_array(bmref text, bmarray text, + index int4) returns text + as '@LIBPATH@', + 'veil_bitmap_from_array' + language C stable strict; + +comment on function veil_bitmap_from_array(text, text, int4) is +'Set BitmapRef BMREF to the bitmap from BMARRAY indexed by INDEX. + +Return the name of the BitmapRef. + +This is used to isolate a single bitmap from a bitmap array, recording +it in a BitmapRef. That bitmap can then be manipulated by ordinary veil +bitmap functions. Note that BitMapRefs can only be referenced within +the transaction they are defined.'; + + +create or replace +function veil_bitmap_array_testbit( + bmarray text, arr_idx int4, bitno int4) returns bool + as '@LIBPATH@', + 'veil_bitmap_array_testbit' + language C stable strict; + +comment on function veil_bitmap_array_testbit(text, int4, int4) is +'Test a bit in BMARRAY, from the bitmap indexed by ARR_IDX, checking the +bit identified by BITNO. + +Return TRUE if the bit is set, else FALSE'; + + +create or replace +function veil_bitmap_array_setbit( + bmarray text, arr_idx int4, bitno int4) returns bool + as '@LIBPATH@', + 'veil_bitmap_array_setbit' + language C stable strict; + +comment on function veil_bitmap_array_setbit(text, int4, int4) is +'Set a bit in BMARRAY, from the bitmap indexed by ARR_IDX, setting the +bit identified by BITNO. + +Return TRUE'; + + +create or replace +function veil_bitmap_array_clearbit( + bmarray text, arr_idx int4, bitno int4) returns bool + as '@LIBPATH@', + 'veil_bitmap_array_clearbit' + language C stable strict; + +comment on function veil_bitmap_array_clearbit(text, int4, int4) is +'Clear a bit in BMARRAY, from the bitmap indexed by ARR_IDX, clearing the +bit identified by BITNO. + +Return TRUE'; + + +create or replace +function veil_union_from_bitmap_array( + bitmap text, bmarray text, arr_idx int4) returns bool + as '@LIBPATH@', + 'veil_union_from_bitmap_array' + language C stable strict; + +comment on function veil_union_from_bitmap_array(text, text, int4) is +'Union BITMAP with BMARRAY[ARR_IDX], with the result going into bitmap. + +Return TRUE'; + + +create or replace +function veil_intersect_from_bitmap_array( + bitmap text, bmarray text, arr_idx int4) returns bool + as '@LIBPATH@', + 'veil_intersect_from_bitmap_array' + language C stable strict; + +comment on function veil_intersect_from_bitmap_array(text, text, int4) is +'Intersect BITMAP with BMARRAY[ARR_IDX], with the result going into bitmap. + +Return TRUE'; + + +create or replace +function veil_bitmap_array_bits(bmarray text, arr_idx int4) returns setof int4 + as '@LIBPATH@', + 'veil_bitmap_array_bits' + language C stable strict; + +comment on function veil_bitmap_array_bits(text, int4) is +'Return all bits in the bitmap given by BMARRAY[ARR_IDX]. + +This is primarily intended for interactive use: for debugging, etc.'; + + +create or replace +function veil_bitmap_array_arange(bmarray text) returns veil_range_t + as '@LIBPATH@', + 'veil_bitmap_array_arange' + language C stable strict; + +comment on function veil_bitmap_array_arange(text) is +'Return the array bounds for BMARRAY.'; + + +create or replace +function veil_bitmap_array_brange(bmarray text) returns veil_range_t + as '@LIBPATH@', + 'veil_bitmap_array_brange' + language C stable strict; + +comment on function veil_bitmap_array_brange(text) is +'Return the range of the bitmaps in BMARRAY.'; + + + +create or replace +function veil_init_bitmap_hash(bmhash text, range text) returns bool + as '@LIBPATH@', 'veil_init_bitmap_hash' + language C stable strict; + +comment on function veil_init_bitmap_hash(text, text) is +'Initialise a bitmap hash variable called BMHASH to contain bitmaps of +size RANGE. + +Return TRUE.'; + + +create or replace +function veil_clear_bitmap_hash(bmhash text) returns bool + as '@LIBPATH@', 'veil_clear_bitmap_hash' + language C stable strict; + +comment on function veil_clear_bitmap_hash(text) is +'Clear all bits in an existing bitmap hash named BMHASH. + +Return TRUE.'; + + +create or replace +function veil_bitmap_hash_key_exists(bmhash text, key text) returns bool + as '@LIBPATH@', + 'veil_bitmap_hash_key_exists' + language C stable strict; + +comment on function veil_bitmap_hash_key_exists(text, text) is +'Determine whether in BMHASH the given KEY already exists. + +Return TRUE if the key exists, else FALSE.'; + + +create or replace +function veil_bitmap_from_hash(text, text, text) returns text + as '@LIBPATH@', + 'veil_bitmap_from_hash' + language C stable strict; + +comment on function veil_bitmap_from_hash(text, text, text) is +'Set BitmapRef BMREF to the bitmap from BMHASH identfied by KEY. + +Return the name of BMREF.'; + + +create or replace +function veil_bitmap_hash_testbit(text, text, int4) returns bool + as '@LIBPATH@', + 'veil_bitmap_hash_testbit' + language C stable strict; + +comment on function veil_bitmap_hash_testbit(text, text, int4) is +'Test the bit, in the bitmap from BMHASH identified by KEY, given by +BITNO. + +Return TRUE if the bit is set, else FALSE.'; + + +create or replace +function veil_bitmap_hash_setbit(text, text, int4) returns bool + as '@LIBPATH@', + 'veil_bitmap_hash_setbit' + language C stable strict; + +comment on function veil_bitmap_hash_setbit(text, text, int4) is +'Set the bit, in the bitmap from BMHASH identified by KEY, given by +BITNO to TRUE. + +Return TRUE.'; + + +create or replace +function veil_bitmap_hash_clearbit(text, text, int4) returns bool + as '@LIBPATH@', + 'veil_bitmap_hash_clearbit' + language C stable strict; + +comment on function veil_bitmap_hash_clearbit(text, text, int4) is +'Set the bit, in the bitmap from BMHASH identified by KEY, given by +BITNO to FALSE. + +Return TRUE.'; + + +create or replace +function veil_union_into_bitmap_hash(text, text, text) returns bool + as '@LIBPATH@', + 'veil_union_into_bitmap_hash' + language C stable strict; + +comment on function veil_union_into_bitmap_hash(text, text, text) is +'Into the bitmap from BMHASH, identified by KEY, and union the bits from +BITMAP (which may be a bitmap or bitmap_ref). + +Return TRUE.'; + + +create or replace +function veil_union_from_bitmap_hash(text, text, text) returns bool + as '@LIBPATH@', + 'veil_union_from_bitmap_hash' + language C stable strict; +comment on function veil_union_from_bitmap_hash(text, text, text) is +'Retrieve the bitmap from BMHASH, identified by KEY, and union it into +BITMAP (which may be a bitmap or bitmap_ref). + +Return TRUE.'; + + +create or replace +function veil_intersect_from_bitmap_hash(text, text, text) returns bool + as '@LIBPATH@', + 'veil_intersect_from_bitmap_hash' + language C stable strict; +comment on function veil_intersect_from_bitmap_hash(text, text, text) is +'Into BITMAP, intersect the bits from the bitmap in BMHASH identified by +KEY. + +Return TRUE.'; + + +create or replace +function veil_bitmap_hash_bits(text, text) returns setof int4 + as '@LIBPATH@', + 'veil_bitmap_hash_bits' + language C stable strict; + +comment on function veil_bitmap_hash_bits(text, text) is +'Return the set of bits in the bitset from BMHASH identfied by KEY.'; + + +create or replace +function veil_bitmap_hash_range(text) returns veil_range_t + as '@LIBPATH@', + 'veil_bitmap_hash_range' + language C stable strict; + +comment on function veil_bitmap_hash_range(text) is +'Return the range of all bitmaps in BMHASH.'; + + +create or replace +function veil_bitmap_hash_entries(text) returns setof text + as '@LIBPATH@', + 'veil_bitmap_hash_entries' + language C stable strict; + +comment on function veil_bitmap_hash_entries(text) is +'Return the keys of all bitmaps in BMHASH.'; + + +create or replace +function veil_int4_set(text, int4) returns int4 + as '@LIBPATH@', + 'veil_int4_set' + language C stable; + +comment on function veil_int4_set(text, int4) is +'Set the int4 variable NAME to VALUE. + +Return the new value'; + + +create or replace +function veil_int4_get(text) returns int4 + as '@LIBPATH@', + 'veil_int4_get' + language C stable strict; + +comment on function veil_int4_get(text) is +'Return the value of int4 variable NAME.'; + + +create or replace +function veil_init_int4array(text, text) returns bool + as '@LIBPATH@', 'veil_init_int4array' + language C stable strict; + +comment on function veil_init_int4array(text, text) is +'Initialise the int4 array ARRAYNAME, with an index range of RANGE. + +Each entry in the array is set to zero. + +Return TRUE.'; + + +create or replace +function veil_clear_int4array(text) returns bool + as '@LIBPATH@', + 'veil_clear_int4array' + language C stable strict; +comment on function veil_clear_int4array(text) is +'Reset each entry in the int4 array ARRAYNAME to zero. + +Return TRUE.'; + + + +create or replace +function veil_int4array_set(text, int4, int4) returns int4 + as '@LIBPATH@', + 'veil_int4array_set' + language C stable; + +comment on function veil_int4array_set(text, int4, int4) is +'Set the ARRAY element IDX to VALUE. + +Return the new value.'; + + +create or replace +function veil_int4array_get(text, int4) returns int4 + as '@LIBPATH@', + 'veil_int4array_get' + language C stable strict; + +comment on function veil_int4array_get(text, int4) is +'Return the value of ARRAY element IDX.'; + + +create or replace +function veil_init(bool) returns bool + as '@LIBPATH@', + 'veil_init' + language C stable strict; + +comment on function veil_init(bool) is +'This is the default version of veil_init, which does nothing except +raise an error.'; + + +create or replace +function veil_perform_reset() returns bool + as '@LIBPATH@', 'veil_perform_reset' + language C stable; + +comment on function veil_perform_reset() is +'Allow userspace to force call of veil_init. + +This function may fail due to outstanding transactions possibly holding +shared memory that we wish to re-use. In this case a warning will be +issued. Once any long running transactions have completed, a retry +should succeed. + +Return TRUE if successful, FALSE otherwise.'; + + +create or replace +function veil_force_reset() returns bool + as '@LIBPATH@', 'veil_force_reset' + language C stable; + +comment on function veil_force_reset() is +'Allow userspace to force an unconditional reset of veil shared memory. + +This always causes a PANIC, causing the database to fully reset.'; + + +create or replace +function veil_version() returns text + as '@LIBPATH@', 'veil_version' + language C stable; + +comment on function veil_version() is +'Return a text string identifying the current veil version.'; + + + +create or replace +function veil_serialise(varname text) returns text + as '@LIBPATH@', + 'veil_serialise' + language C stable strict; + +comment on function veil_serialise(varname text) is +'Return a serialised copy of a variable VARNAME in text form. + +This is intended to be used with pgmemcache so that session variables +can be efficiently cached. Serialised values can be concatenated +together as a single string and then deserialised in a single operation. + +The value can be restored by de-serialising it.'; + + + +create or replace +function veil_serialize(text) returns text + as '@LIBPATH@', + 'veil_serialise' + language C stable strict; + +comment on function veil_serialize(varname text) is +'Return a serialised copy of a variable VARNAME in text form. + +This is intended to be used with pgmemcache so that session variables +can be efficiently cached. Serialized values can be concatenated +together as a single string and then deserialized in a single operation. + +The value can be restored by de-serializing it.'; + + + +create or replace +function veil_deserialise(text) returns int4 + as '@LIBPATH@', + 'veil_deserialise' + language C stable strict; + +comment on function veil_deserialise(text) is +'Reset the contents of a set of serialised variable from STREAM. + +Return the number of items de-serialised.'; + + +-- Ditto for victims of webster. +create or replace +function veil_deserialize(text) returns int4 + as '@LIBPATH@', + 'veil_deserialise' + language C stable strict; + +comment on function veil_deserialize(text) is +'Reset the contents of a set of serialized variable from STREAM. + +Return the number of items de-serialized.'; + + +revoke execute on function veil_share(text) from public; +revoke execute on function veil_variables() from public; +revoke execute on function veil_init_range(text, int4,int4) from public; +revoke execute on function veil_range(text) from public; + +revoke execute on function veil_init_bitmap(text, text) from public; +revoke execute on function veil_clear_bitmap(text) from public; +revoke execute on function veil_bitmap_setbit(text, int4) from public; +revoke execute on function veil_bitmap_testbit(text, int4) from public; +revoke execute on function veil_bitmap_bits(text) from public; +revoke execute on function veil_bitmap_range(text) from public; + +revoke execute on function veil_init_bitmap_array(text, text, text) + from public; +revoke execute on function veil_clear_bitmap_array(text) from public; +revoke execute on function veil_bitmap_from_array(text, text, int4) + from public; +revoke execute on function veil_bitmap_array_setbit(text, int4, int4) + from public; +revoke execute on function veil_bitmap_array_testbit(text, int4, int4) + from public; +revoke execute on function veil_union_from_bitmap_array(text, text, int4) + from public; +revoke execute on function veil_intersect_from_bitmap_array(text, text, int4) + from public; +revoke execute on function veil_bitmap_array_bits(text, int4) from public; +revoke execute on function veil_bitmap_array_arange(text) from public; +revoke execute on function veil_bitmap_array_brange(text) from public; + + +revoke execute on function veil_init_bitmap_hash(text, text) from public; +revoke execute on function veil_clear_bitmap_hash(text) from public; +revoke execute on function veil_bitmap_hash_key_exists(text, text) + from public; +revoke execute on function veil_bitmap_from_hash(text, text, text) + from public; +revoke execute on function veil_bitmap_hash_setbit(text, text, int4) + from public; +revoke execute on function veil_bitmap_hash_testbit(text, text, int4) + from public; +revoke execute on function veil_union_into_bitmap_hash(text, text, text) + from public; +revoke execute on function veil_union_from_bitmap_hash(text, text, text) + from public; +revoke execute on function veil_intersect_from_bitmap_hash(text, text, text) + from public; +revoke execute on function veil_bitmap_hash_bits(text, text) from public; +revoke execute on function veil_bitmap_hash_range(text) from public; +revoke execute on function veil_bitmap_hash_entries(text) from public; + +revoke execute on function veil_init_int4array(text, text) from public; +revoke execute on function veil_clear_int4array(text) from public; +revoke execute on function veil_int4array_set(text, int4, int4) from public; +revoke execute on function veil_int4array_get(text, int4) from public; + +revoke execute on function veil_init(bool) from public; + +revoke execute on function veil_serialise(text) from public; +revoke execute on function veil_serialize(text) from public; +revoke execute on function veil_deserialise(text) from public; +revoke execute on function veil_deserialize(text) from public; + + diff --git a/src/veil_mainpage.c b/src/veil_mainpage.c new file mode 100644 index 0000000..0f2f715 --- /dev/null +++ b/src/veil_mainpage.c @@ -0,0 +1,2229 @@ +/* ---------- + * veil_mainpage.c + * + * Doxygen documentation root for Veil + * + * Copyright (c) 2005 - 2011 Marc Munro + * Author: Marc Munro + * License: BSD + * + */ + + +/*! \mainpage Veil +\version 1.0.0 (Stable)) +\section license License +BSD +\section intro_sec Introduction + +Veil is a data security add-on for Postgres. It provides an API +allowing you to control access to data at the row, or even column, +level. Different users will be able to run the same query and see +different results. Other database vendors describe this as a Virtual +Private Database. + +\section Why Why do I need this? +If you have a database-backed application that stores sensitive data, +you will be taking at least some steps to protect that data. Veil +provides a way of protecting your data with a security mechanism +within the database itself. No matter how you access the database, +whether you are a legitimate user or not, you cannot by-pass Veil +without superuser privileges. + +\subsection Advantages Veil Advantages +By placing security mechanisms within the database itself we get a +number of advantages: +- Ubiquity. Security is always present, no matter what application or +tool is used to connect to the database. If your application is +compromised, your data is still protected by Veil. If an intruder gets +past your outer defences and gains access to psql, your data is still +protected. +- Single Security Policy and Implementation. If you have N applications +to secure, you have to implement your security policy N times. With +Veil, all applications may be protected by a single implementation. +- Strength in Depth. For the truly security conscious, Veil provides +yet another level of security. If you want strength in depth, with +layers and layers of security like an onion, Veil gives you that extra +layer. +- Performance. Veil is designed to be both flexible and efficient. +With a good implementation it is possible to build access controls with +a very low overhead, typically much lower than building the equivalent +security in each application. +- Cooperation. The Veil security model is designed to cooperate with your +applications. Although Veil is primarily concerned with data access +controls, it can also be used to provide function-level privileges. If +your application has a sensitive function X, it can query the database, +through Veil functions, to ask the question, "Does the current user have +execute_X privilege?". Also, that privilege can be managed in exactly +the same way as any other privilege. +- Flexibility. Veil is a set of tools rather than a product. How you +use it is up to you. + +\subsection Limitations Veil Limitations +Veil can restrict the data returned by queries but cannot prevent the +query engine itself from seeing restricted data. In particular, +functions executed during evaluation of the where clause of a query may +be able to see data that Veil is supposed to restrict access to. + +As an example let's assume that we have a secured view, users, that +allows a user to see only their own record. When Alice queries the +view, she will see this: + +\code +select * from users; + + userid | username +----------+----------- + 12345 | Alice +\endcode + +Alice should not be able to see any details for Bob or even, strictly +speaking, tell whether there is an entry for Bob. This query though: + +\verbatim +select * from users +where 0 = 9 / (case username when 'Bob' then 0 else 1 end); +\endverbatim + +will raise an exception if the where clause encounters the username +'Bob'. So Alice can potentially craft queries that will enable her to +discover whether specific names appear in the database. + +This is not something that Veil is intended to, or is able to, prevent. +Changes to the underlying query engine to attempt to plug such holes +have been proposed, but they all have their limitations and are likely +to lead to degraded performance. + +A more serious problem occurs if a user is able to create user defined +functions as these can easily provide covert channels for leaking data. +Consider this query: + +\verbatim +select * from users where leak_this(username); +\endverbatim + +Although the query will only return what the secured view allows it to, +the where clause, if the function is deemed inexpensive enough, will see +every row in the table and so will be able to leak supposedly protected +data. This type of exploit can be protected against easily enough by +preventing users from defining their own functions, however there are +postgres' builtins that can be potentially be exploited in the same way. + +\subsection GoodNews The Good News + +The news is not all bad. Although Veil can be circumvented, as shown +above, a database protected by Veil is a lot more secure than one which +is not. Veil can provide extra security over and above that provided by +your application, and in combination with a well designed application +can provide security that is more than adequate for most commercial +purposes. + +\section the-rest Veil Documentation +- \subpage overview-page +- \subpage API-page +- \subpage Building +- \subpage Demo +- \subpage Management +- \subpage Esoteria +- \subpage install +- \subpage History +- \subpage Feedback +- \subpage Performance +- \subpage Credits + +Next: \ref overview-page + +*/ +/*! \page overview-page Overview: a quick introduction to Veil + +\section Overview-section Introduction +The section introduces a number of key concepts, and shows the basic +components of a Veil-protected system: +- \ref over-views +- \ref over-connections +- \ref over-privs +- \ref over-contexts +- \ref over-funcs2 +- \ref over-roles + +\subsection over-views Secured Views and Access Functions +Access controls are implemented using secured views and instead-of triggers. +Users connect to an account that has access only to the secured views. +For a table defined thus: +\verbatim +create table persons ( + person_id integer not null, + person_name varchar(80) not null +); +\endverbatim +The secured view would be defined something like this: +\verbatim +create view persons( + person_id, + person_name) as +select person_id, + person_name +from persons +where i_have_personal_priv(10013, person_id); +\endverbatim + +A query performed on the view will return rows only for those persons +where the current user has privilege 10013 +(SELECT_PERSONS). We call the function +i_have_personal_priv(), an access function. Such +functions are user-defined, and are used to determine whether the +connected user has a specific privilege in any of a number of security +contexts (see \ref over-contexts). The example above is +taken from the Veil demo application (\ref demo-sec) and +checks for privilege in the global and personal contexts. + +\subsection over-connections The Connected User and Connection Functions +To determine a user's privileges, we have to know who that user is. +At the start of each database session the user must be identified, and +their privileges must be determined. This is done by calling a +connection function, eg: +\verbatim +select connect_person('Wilma', 'AuthenticationTokenForWilma'); +\endverbatim +The connection function performs authentication, and stores the user's +access privileges in Veil state variables. These variables are then +interrogated by the access functions used in the secured views. + +Prior to connection, or in the event of the connection failing, the +session will have no privileges and will probably be unable to see any +data. Like access functions, connection functions are user-defined and +may be written in any language supported by PostgreSQL. + +\subsection over-privs Privileges +Veil-based systems define access rights in terms of privileges. A +privilege is a named thing with a numerical value (actually, the name +is kind of optional). + +An example will probably help. Here is a definition of a privileges +table and a subset of its data: +\verbatim +create table privileges ( + privilege_id integer not null, + privilege_name varchar(80) not null +); + +copy privileges (privilege_id, privilege_name) from stdin; +10001 select_privileges +10002 insert_privileges +10003 update_privileges +10004 delete_privileges +. . . +10013 select_persons +10014 insert_persons +10015 update_persons +10016 delete_persons +10017 select_projects +10018 insert_projects +10019 update_projects +10020 delete_projects +. . . +10100 can_connect +\. + +\endverbatim +Each privilege describes something that a user can do. It is up to the +access and connection functions to make use of these privileges; the +name of the privilege is only a clue to its intended usage. In the +example we might expect that a user that has not been given the +can_connect privilege would not be able to authenticate +using a connection function but this is entirely dependent on the +implementation. + +\subsection over-contexts Security Contexts + +Users may be assigned privileges in a number of different ways. They +may be assigned directly, indirectly through various relationships, or +may be inferred by some means. To aid in the discussion and design of a +Veil-based security model we introduce the concept of security +contexts, and we say that a user has a given set of privileges in a +given context. There are three types of security context: + + - Global Context. This refers to privileges that a user has been given + globally. If a user has select_persons privilege in the + global context, they will be able to select every record in the + persons table. Privileges in global context are exactly like + database-level privileges: there is no row-level element to them. + + - Personal Context. This context provides privileges on data that you + may be said to own. If you have select_persons + privilege in only the personal context, you will only be able to + select your own persons record. Assignment of privileges in the + personal context is often defined implicitly or globally, for all + users, rather than granted explicitly to each user. It is likely + that everyone should have the same level of access to their own data + so it makes little sense to have to explicitly assign the privileges + for each individual user. + + - Relational Contexts. These are the key to most row-level access + controls. Privileges assigned in a relational context are assigned + through relationships between the connected user and the data to be + accessed. Examples of relational contexts include: assignments to + projects, in which a user will gain access to project data only if + they have been assigned to the project; and the management hierarchy + within a business, in which a manager may have specific access to + data about a staff member. Note that determining a user's access + rights in a relational context may require extra queries to be + performed for each function call. Your design should aim to minimise + this. Some applications may require a number of distinct relational + contexts. + +\subsection over-funcs2 Access Functions and Security Contexts +Each access function will operate on privileges for a specific set of +contexts. For some tables, access will only be through global context. +For others, it may be through global and personal as well as a number of +different relational contexts. Here, from the demo application, are a +number of view definitions, each using a different access function that +checks different contexts. +\verbatim +create view privileges( + privilege_id, + privilege_name) as +select privilege_id, + privilege_name +from privileges +where i_have_global_priv(10001); + +. . . + +create view persons( + person_id, + person_name) as +select person_id, + person_name +from persons +where i_have_personal_priv(10013, person_id); + +. . . + +create view projects( + project_id, + project_name) as +select project_id, + project_name +from projects +where i_have_project_priv(10017, project_id); + +. . . + +create view assignments ( + project_id, + person_id, + role_id) as +select project_id, + person_id, + role_id +from assignments +where i_have_proj_or_pers_priv(10025, project_id, person_id); +\endverbatim + +In the privileges view, we only check for privilege in the +global context. This is a look-up view, and should be visible to all +authenticated users. + +The persons view checks for privilege in both the global +and personal contexts. It takes an extra parameter identifying the +person who owns the record. If that person is the same as the connected +user, then privileges in the personal context may be checked. If not, +only the global context applies. + +The projects view checks global and project contexts. The +project context is a relational context. In the demo application, a +user gains privileges in the project context through assignments. An +assignment is a relationship between a person and a project. Each +assignment record has a role. This role describes the set of privileges +the assignee (person) has within the project context. + +The assignments view checks all three contexts (global, +personal and project). An assignment contains data about a person and a +project so privileges may be acquired in either of the relational +contexts, or globally. + +\subsection over-roles Grouping Privileges by Roles +Privileges operate at a very low-level. In a database of 100 tables, +there are likely to be 500 to 1,000 privileges in use. Managing +users access at the privilege level is, at best, tedious. Instead, we +tend to group privileges into roles, and assign only roles to individual +users. Roles act as function-level collections of privileges. For +example, the role project-readonly might contain all of the +select_xxx privileges required to read all project data. + +A further refinement allows roles to be collections of sub-roles. +Defining suitable roles for a system is left as an exercise for the +reader. + +Next: \ref API-page + +*/ +/*! \page API-page The Veil API +\section API-sec The Veil API +This section describes the Veil API. It consists of the following +sections + +- \ref API-intro +- \subpage API-variables +- \subpage API-simple +- \subpage API-bitmaps +- \subpage API-bitmap-arrays +- \subpage API-bitmap-hashes +- \subpage API-int-arrays +- \subpage API-serialisation +- \subpage API-control + +\section API-intro Veil API Overview +Veil is an API that simply provides a set of state variable types, and +operations on those variable types, which are optimised for privilege +examination and manipulation. + +The fundamental data type is the bitmap. Bitmaps are used to +efficiently record and test sets of privileges. Bitmaps may be combined +into bitmap arrays, which are contiguous groups of bitmaps indexed by +integer, and bitmap hashes which are non-contiguous and may be indexed +by text strings. + +In addition to the bitmap-based types, there are a small number of +support types that just help things along. If you think you have a case +for defining a new type, please +\ref Feedback "contact" +the author. + +Next: \ref API-variables +*/ +/*! \page API-variables Variables +Veil variables exist to record session and system state. They retain +their values across transactions. Variables may be defined as either +session variables or shared variables. + +All variables are referenced by name; the name of the variable is +passed as a text string to Veil functions. + +Session variables are private to the connected session. They are +created when first referenced and, once defined, their type is set for +the life of the session. + +Shared variables are global across all sessions. Once a shared variable +is defined, all sessions will have access to it. Shared variables are +defined in two steps. First, the variable is defined as shared, and +then it is initialised and accessed in the same way as for session +variables. Note that shared variables should only be modified within +the function veil_init(). + +Note that bitmap refs and bitmap hashes may not be stored in shared +variables. + +The following types of variable are supported by Veil, and are described +in subsequent sections: +- integers +- ranges +- bitmaps +- bitmap refs +- bitmap arrays +- bitmap hashes +- integer arrays + +The following functions comprise the Veil variables API: + +- \ref API-variables-share +- \ref API-variables-var + +Note again that session variables are created on usage. Their is no +specific function for creating a variable in the variables API. For an +example of a function to create a variable see \ref API-bitmap-init. + +\section API-variables-share veil_share(name text) +\code +function veil_share(name text) returns bool +\endcode + +This is used to define a specific variable as being shared. A shared +variable is accessible to all sessions and exists to reduce the need for +multiple copies of identical data. For instance in the Veil demo, +role_privileges are recorded in a shared variable as they will be +identical for all sessions, and to create a copy for each session would +be an unnecessary overhead. This function should only be called from +veil_init(). + +\section API-variables-var veil_variables() +\code +function veil_variables() returns setof veil_variable_t +\endcode + +This function returns a description for each variable known to the +session. It provides the name, the type, and whether the variable is +shared. It is primarily intended for interactive use when developing +and debugging Veil-based systems. + +Next: \ref API-simple +*/ +/*! \page API-simple Basic Types: Integers and Ranges + +Veil's basic types are those that do not contain repeating groups +(arrays, hashes, etc). + +Ranges consist of a pair of values and are generally used to initialise +the bounds of array and bitmap types. Ranges may not contain nulls. + +The int4 type is used to record a simple nullable integer. This is +typically used to record the id of the connected user in a session. + +The following functions comprise the Veil basic types API: + +- \ref API-basic-init-range +- \ref API-basic-range +- \ref API-basic-int4-set +- \ref API-basic-int4-get + +\section API-basic-init-range veil_init_range(name text, min int4, max int4) +\code +function veil_init_range(name text, min int4, max int4) returns int4 +\endcode + +This defines a range, and returns the extent of that range. + +\section API-basic-range veil_range(name text) +\code +function veil_range(name text) returns veil_range_t +\endcode + +This returns the contents of a range. It is intended primarily for +interactive use. + +\section API-basic-int4-set veil_int4_set(text, int4) +\code +function veil_int4_set(text, int4) returns int4 +\endcode + +Sets an int4 variable to a value, returning that same value. + +\section API-basic-int4-get veil_int4_get(text) +\code +function veil_int4_get(text) returns int4 +\endcode + +Returns the value of an int4 variable. + + +Next: \ref API-bitmaps +*/ +/*! \page API-bitmaps Bitmaps and Bitmap Refs +Bitmaps are used to implement bounded sets. Each bit in the bitmap may +be on or off representing presence or absence of a value in the set. +Typically bitmaps are used to record sets of privileges. + +A bitmap ref is a variable that may temporarily reference another +bitmap. These are useful for manipulating specific bitmaps within +bitmap arrays or bitmap hashes. All bitmap operations except for \ref +API-bitmap-init may take the name of a bitmap ref instead of a bitmap. + +Bitmap refs may not be shared, and the reference is only accessible +within the transaction that created it. These restrictions exist to +eliminate the possibility of references to deleted objects or to objects +from other sessions. + +The following functions comprise the Veil bitmaps API: + +- \ref API-bitmap-init +- \ref API-bitmap-clear +- \ref API-bitmap-setbit +- \ref API-bitmap-clearbit +- \ref API-bitmap-testbit +- \ref API-bitmap-union +- \ref API-bitmap-intersect +- \ref API-bitmap-bits +- \ref API-bitmap-range + +\section API-bitmap-init veil_init_bitmap(bitmap_name text, range_name text) +\code +function veil_init_bitmap(bitmap_name text, range_name text) returns bool +\endcode +This is used to create or resize a bitmap. The first parameter provides +the name of the bitmap, the second is the name of a range variable that +will govern the size of the bitmap. + +\section API-bitmap-clear veil_clear_bitmap(name text) +\code +function veil_clear_bitmap(name text) returns bool +\endcode +This is used to clear (set to zero) all bits in the bitmap. + +\section API-bitmap-setbit veil_bitmap_setbit(name text, bit_number int4) +\code +function veil_bitmap_setbit(text, int4) returns bool +\endcode +This is used to set a specified bit in a bitmap. + +\section API-bitmap-clearbit veil_bitmap_clearbit(name text, bit_number int4) +\code +function veil_bitmap_clearbit(name text, bit_number int4) returns bool +\endcode +This is used to clear (set to zero) a specified bit in a bitmap. + +\section API-bitmap-testbit veil_bitmap_testbit(name text, bit_number int4) +\code +function veil_bitmap_testbit(name text, bit_number int4) returns bool +\endcode +This is used to test a specified bit in a bitmap. It returns true if +the bit is set, false otherwise. + +\section API-bitmap-union veil_bitmap_union(result_name text, name2 text) +\code +function veil_bitmap_union(result_name text, name2 text) returns bool +\endcode +Form the union of two bitmaps with the result going into the first. + +\section API-bitmap-intersect veil_bitmap_intersect(result_name text, name2 text) +\code +function veil_bitmap_intersect(result_name text, name2 text) returns bool +\endcode +Form the intersection of two bitmaps with the result going into the first. + +\section API-bitmap-bits veil_bitmap_bits(name text) +\code +function veil_bitmap_bits(name text) returns setof int4 +\endcode +This is used to list all bits set within a bitmap. It is primarily for +interactive use during development and debugging of Veil-based systems. + +\section API-bitmap-range veil_bitmap_range(name text) +\code +function veil_bitmap_range(name text) returns veil_range_t +\endcode +This returns the range of a bitmap. It is primarily intended for +interactive use. + +Next: \ref API-bitmap-arrays +*/ +/*! \page API-bitmap-arrays Bitmap Arrays +A bitmap array is an array of identically-ranged bitmaps, indexed +by an integer value. They are initialised using two ranges, one for the +range of each bitmap, and one providing the range of indices for the +array. + +Typically bitmap arrays are used for collections of privileges, where +each element of the collection is indexed by something like a role_id. + +The following functions comprise the Veil bitmap arrays API: + +- \ref API-bmarray-init +- \ref API-bmarray-clear +- \ref API-bmarray-bmap +- \ref API-bmarray-testbit +- \ref API-bmarray-setbit +- \ref API-bmarray-clearbit +- \ref API-bmarray-union +- \ref API-bmarray-intersect +- \ref API-bmarray-bits +- \ref API-bmarray-arange +- \ref API-bmarray-brange + +\section API-bmarray-init veil_init_bitmap_array(bmarray text, array_range text, bitmap_range text) +\code +function veil_init_bitmap_array(bmarray text, array_range text, bitmap_range text) returns bool +\endcode +Creates or resets (clears) a bitmap array. + +\section API-bmarray-clear veil_clear_bitmap_array(bmarray text) +\code +function veil_clear_bitmap_array(bmarray text) returns bool +\endcode +Clear all bits in all bitmaps of a bitmap array + +\section API-bmarray-bmap veil_bitmap_from_array(bmref text, bmarray text, index int4) +\code +function veil_bitmap_from_array(bmref text, bmarray text, index int4) returns text +\endcode +Generate a reference to a specific bitmap in a bitmap array + +\section API-bmarray-testbit veil_bitmap_array_testbit(bmarray text, arr_idx int4, bitno int4) +\code +function veil_bitmap_array_testbit(bmarray text, arr_idx int4, bitno int4) returns bool +\endcode +Test a specific bit in a bitmap array. + +\section API-bmarray-setbit veil_bitmap_array_setbit(bmarray text, arr_idx int4, bitno int4) +\code +function veil_bitmap_array_setbit(bmarray text, arr_idx int4, bitno int4) returns bool +\endcode +Set a specific bit in a bitmap array. + +\section API-bmarray-clearbit veil_bitmap_array_clearbit(bmarray text, arr_idx int4, bitno int4) +\code +function veil_bitmap_array_clearbit(bmarray text, arr_idx int4, bitno int4) returns bool +\endcode +Clear a specific bit in a bitmap array. + +\section API-bmarray-union veil_union_from_bitmap_array(bitmap text, bmarray text, arr_idx int4) +\code +function veil_union_from_bitmap_array(bitmap text, bmarray text, arr_idx int4) returns bool +\endcode +Union a bitmap with a specified bitmap from an array, with the result in +the bitmap. This is a faster shortcut for: + + +veil_bitmap_union(<bitmap>, veil_bitmap_from_array(<bitmap_array>, <index>)) +. + +\section API-bmarray-intersect veil_intersect_from_bitmap_array(bitmap text, bmarray text, arr_idx int4) +\code +function veil_intersect_from_bitmap_array(bitmap text, bmarray text, arr_idx int4) returns bool +\endcode +Intersect a bitmap with a specified bitmap from an array, with the result in +the bitmap. This is a faster shortcut for: + + +veil_bitmap_intersect(<bitmap>, veil_bitmap_from_array(<bitmap_array>, <index>)) +. + +\section API-bmarray-bits veil_bitmap_array_bits(bmarray text, arr_idx int4) +\code +function veil_bitmap_array_bits(bmarray text, arr_idx int4) returns setof int4 +\endcode +Show all bits in the specific bitmap within an array. This is primarily +intended for interactive use when developing and debugging Veil-based +systems. + +\section API-bmarray-arange veil_bitmap_array_arange(bmarray text) +\code +function veil_bitmap_array_arange(bmarray text) returns veil_range_t +\endcode +Show the range of array indices for the specified bitmap array. +Primarily for interactive use. + +\section API-bmarray-brange veil_bitmap_array_brange(bmarray text) +\code +function veil_bitmap_array_brange(bmarray text) returns veil_range_t +\endcode +Show the range of all bitmaps in the specified bitmap array. +Primarily for interactive use. + + +Next: \ref API-bitmap-hashes +*/ +/*! \page API-bitmap-hashes Bitmap Hashes +A bitmap hashes is a hash table of identically-ranged bitmaps, indexed +by a text key. + +Typically bitmap hashes are used for sparse collections of privileges. + +Note that bitmap hashes may not be stored in shared variables as hashes +in shared memory are insufficiently dynamic. + +The following functions comprise the Veil bitmap hashes API: + +- \ref API-bmhash-init +- \ref API-bmhash-clear +- \ref API-bmhash-key-exists +- \ref API-bmhash-from +- \ref API-bmhash-testbit +- \ref API-bmhash-setbit +- \ref API-bmhash-clearbit +- \ref API-bmhash-union-into +- \ref API-bmhash-union-from +- \ref API-bmhash-intersect-from +- \ref API-bmhash-bits +- \ref API-bmhash-range +- \ref API-bmhash-entries + +\section API-bmhash-init veil_init_bitmap_hash(bmhash text, range text) +\code +function veil_init_bitmap_hash(bmhash text, range text) returns bool +\endcode +Creates, or resets, a bitmap hash. + +\section API-bmhash-clear veil_clear_bitmap_hash(bmhash text) +\code +function veil_clear_bitmap_hash(bmhash text) returns bool +\endcode +Clear all bits in a bitmap hash. + +\section API-bmhash-key-exists veil_bitmap_hash_key_exists(bmhash text, key text) +\code +function veil_bitmap_hash_key_exists(bmhash text, key text) returns bool +\endcode +Determine whether a given key exists in the hash (contains a bitmap). + +\section API-bmhash-from veil_bitmap_from_hash(text, text, text) +\code +function veil_bitmap_from_hash(text, text, text) returns text +\endcode +Generate a reference to a specific bitmap in a bitmap hash. + +\section API-bmhash-testbit veil_bitmap_hash_testbit(text, text, int4) +\code +function veil_bitmap_hash_testbit(text, text, int4) returns bool +\endcode +Test a specific bit in a bitmap hash. + +\section API-bmhash-setbit veil_bitmap_hash_setbit(text, text, int4) +\code +function veil_bitmap_hash_setbit(text, text, int4) returns bool +\endcode +Set a specific bit in a bitmap hash. + +\section API-bmhash-clearbit veil_bitmap_hash_clearbit(text, text, int4) +\code +function veil_bitmap_hash_clearbit(text, text, int4) returns bool +\endcode +Clear a specific bit in a bitmap hash. + +\section API-bmhash-union-into veil_union_into_bitmap_hash(text, text, text) +\code +function veil_union_into_bitmap_hash(text, text, text) returns bool +\endcode +Union a specified bitmap from a hash with a bitmap, with the result in +the bitmap hash. This is a faster shortcut for: + + +veil_bitmap_union(veil_bitmap_from_hash(<bitmap_hash>, <key>), <bitmap>) +. + +\section API-bmhash-union-from veil_union_from_bitmap_hash(text, text, text) +\code +function veil_union_from_bitmap_hash(text, text, text) returns bool +\endcode +Union a bitmap with a specified bitmap from a hash, with the result in +the bitmap. This is a faster shortcut for: + + +veil_bitmap_union(<bitmap>, veil_bitmap_from_hash(<bitmap_array>, <key>)) +. + +\section API-bmhash-intersect-from veil_intersect_from_bitmap_hash(text, text, text) +\code +function veil_intersect_from_bitmap_hash(text, text, text) returns bool +\endcode +Intersect a bitmap with a specified bitmap from a hash, with the result in +the bitmap. This is a faster shortcut for: + + +veil_bitmap_intersect(<bitmap>, veil_bitmap_from_hash(<bitmap_array>, <key>)) +. + +\section API-bmhash-bits veil_bitmap_hash_bits(text, text) +\code +function veil_bitmap_hash_bits(text, text) returns setof int4 +\endcode +Show all bits in the specific bitmap within a hash. This is primarily +intended for interactive use when developing and debugging Veil-based +systems. + +\section API-bmhash-range veil_bitmap_hash_range(text) +\code +function veil_bitmap_hash_range(text) returns veil_range_t +\endcode +Show the range of all bitmaps in the hash. Primarily intended for +interactive use. + +\section API-bmhash-entries veil_bitmap_hash_entries(text) +\code +function veil_bitmap_hash_entries(text) returns setof text +\endcode +Show every key in the hash. Primarily intended for interactive use. + +Next: \ref API-int-arrays +*/ +/*! \page API-int-arrays Integer Arrays +Integer arrays are used to store simple mappings of keys to values. In +the Veil demo (\ref demo-sec) they are used to record the extra privilege +required to access person_details and project_details of each +detail_type: the integer array being used to map the detail_type_id to +the privilege_id. + +Note that integer array elements cannot be null. + +The following functions comprise the Veil int arrays API: + +- \ref API-intarray-init +- \ref API-intarray-clear +- \ref API-intarray-set +- \ref API-intarray-get + +\section API-intarray-init veil_init_int4array(text, text) +\code +function veil_init_int4array(text, text) returns bool +\endcode +Creates, or resets the ranges of, an int array. + +\section API-intarray-clear veil_clear_int4array(text) +\code +function veil_clear_int4array(text) returns bool +\endcode +Clears (zeroes) an int array. + +\section API-intarray-set veil_int4array_set(text, int4, int4) +\code +function veil_int4array_set(text, int4, int4) returns int4 +\endcode +Set the value of an element in an int array. + +\section API-intarray-get veil_int4array_get(text, int4) +\code +function veil_int4array_get(text, int4) returns int4 +\endcode +Get the value of an element from an int array. + +Next: \ref API-serialisation +*/ +/*! \page API-serialisation Veil Serialisation Functions +With modern web-based applications, database connections are often +pooled, with each connection representing many different users. In +order to reduce the overhead of connection functions for such +applications, Veil provides a serialisation API. This allows session +variables for a connected user to be saved for subsequent re-use. This +is particularly effective in combination with pgmemcache +http://pgfoundry.org/projects/pgmemcache/ + +Only session variables may be serialised. + +The following functions comprise the Veil serialisatation API: + +- \ref API-serialise +- \ref API-deserialise +- \ref API-serialize +- \ref API-deserialize + +\section API-serialise veil_serialise(text) +\code +function veil_serialise(text) returns text +\endcode +This creates a serialised textual representation of the named session +variable. The results of this function may be concatenated into a +single string, which can be deserialised in a single call to +veil_deserialise() + +\section API-deserialise veil_deserialise(text) +\code +function veil_deserialise(text) returns text +\endcode +This takes a serialised representation of one or more variables as +created by concatenating the results of veil_serialise(), and +de-serialises them, creating new variables as needed and resetting their +values to those they had when they were serialised. + +\section API-serialize veil_serialize(text) +\code +function veil_serialize(text) returns text +\endcode +Synonym for veil_serialise() + +\section API-deserialize veil_deserialize(text) +\code +function veil_deserialize(text) returns text +\endcode +Synonym for veil_deserialise() + +Next: \ref API-control +*/ +/*! \page API-control Veil Control Functions +Veil generally requires no management. The exception to this is when +you wish to reset shared variables. You may wish to do this because +your underlying security definitions have changed, or because you have +added new features. In this case, you may use veil_perform_reset() to +re-initialise your shared variables. This function replaces the current +set of shared variables with a new set in a transaction-safe manner. +All current transactions will complete with the old set of variables in +place. All subsequent transactions will see the new set. + +The following functions comprise the Veil control functions API: + +- \ref API-control-init +- \ref API-control-reset +- \ref API-control-force +- \ref API-version + +\section API-control-init veil_init(bool) +\code +function veil_init(bool) returns bool +\endcode +This function must be redefined by the application. The default +installed version simply raises an error telling you to redefine it. +See \ref Implementation for a more detailed description of this function. + +\section API-control-reset veil_perform_reset() +\code +function veil_perform_reset() returns bool +\endcode +This is used to reset Veil's shared variables. It causes veil_init() to +be called. + +\section API-control-force veil_force_reset(bool) +\code +function veil_force_reset() returns bool +\endcode +In the event of veil_perform_reset() failing to complete and leaving +shared variables in a state of limbo, this function may be called to +force the reset. After forcing the reset, this function raises a panic +which will reset the database server. Use this at your peril. + +\section API-version veil_version() +\code +function veil_version() returns text +\endcode +This function returns a string describing the installed version of +veil. + +Next: \ref Building + +*/ +/*! \page Building Building a Veil-based secure database +\section Build-sec Building a Veil-based secure database + +This section describes the steps necessary to secure a database using +Veil. The steps are: +- \ref Policy +- \ref Schemas +- \ref Design +- \ref Implementation +- \ref Implementation2 +- \ref Implementation3 +- \ref Implementation4 +- \ref Testing + +\subsection Policy Determine your Policies + +You must identify which security contexts exist for your application, +and how privileges should be assigned to users in those contexts. You +must also figure out how privileges, roles, and the assignment of roles +to users are to be managed. You must identify each object that is to be +protected by Veil, identify the security contexts applicable for that +object, and determine the privileges that will apply to each object in +each possible mode of use. Use the Veil demo application (\ref +demo-sec) as a guide. + +For data access controls, typically you will want specific privileges +for select, insert, update and delete on each table. You may also want +separate admin privileges that allow you to grant those rights. + +At the functional level, you will probably have an execute privilege for +each callable function, and you will probably want similar privileges +for individual applications and components of applications. Eg, to +allow the user to execute the role_manager component of admintool, you +would probably create a privilege called +exec_admintool_roleman. + +The hardest part of this is figuring out how you will securely manage +these privileges. A useful, minimal policy is to not allow anyone to +assign a role that they themselves have not been assigned. + +\subsection Schemas Design Your Database-Level Security + +Veil operates within the security provided by PostgreSQL. If you wish +to use Veil to protect underlying tables, then those tables must not be +directly accessible to the user. Also, the Veil functions themselves, +as they provide privileged operations, must not be accessible to user +accounts. + +A sensible basic division of schema responsibilities would be as follows: + +- An "owner" user will own the underlying objects (tables, views, + functions, etc) that are to be secured. Access to these objects will + be granted only to "Veil". The "owner" user will connect only when + the underlying objects are to be modified. No-one but a DBA will ever + connect to this account, and generally, the password for this account + should be disabled. + +- A "Veil" user will own all secured views and access functions (see + \ref over-views). Access to these objects will be granted to the + "Accessor" user. Like the "owner" user, this user should not be + directly used except by DBAs performing maintenance. It will also own + the Veil API, ie this is the account where Veil itself will be + installed. Direct access to Veil API functions should not be granted + to other users. If access to a specific function is needed, it should + be wrapped in a local function to which access may then be granted. + +- "Accessor" users are the primary point of contact. These must have no + direct access to the underlying objects owned by owner. They will have + access only to the secured views and access functions. All + applications may connect to these user accounts. + +\subsection Design Design Your Access Functions + +Provide a high-level view of the workings of each access function. You +will need this in order to figure out what session and shared variables +you will need. The following is part of the design from the Veil demo +application: +\verbatim +Access Functions are required for: +- Global context only (lookup-data, eg privileges, roles, etc) +- Personal and Global Context (personal data, persons, assignments, etc) +- Project and Global (projects, project_details) +- All 3 (assignments) + +Determining privilege in Global Context: + +User has priv X, if X is in role_privileges for any role R, that has +been assigned to the user. + +Role privileges are essentially static so may be loaded into memory as a +shared variable. When the user connects, the privileges associated with +their roles may be loaded into a session variable. + +Shared initialisation code: + role_privs ::= shared array of privilege bitmaps indexed by role. + Populate role_privs with: + select bitmap_array_setbit(role_privs, role_id, privilege_id) + from role_privileges; + +Connection initialisation code: + global_privs ::= session privileges bitmap + Clear global_privs and then initialise with: + select bitmap_union(global_privs, role_privs[role_id]) + from person_roles + where person_id = connected_user; + +i_have_global_priv(x): + return bitmap_testbit(global_privs, x); + +\endverbatim + +This gives us the basic structure of each function, and identifies what +must be provided by session and system initialisation to support those +functions. It also allows us to identify the overhead that Veil imposes. + +In the case above, there is a connect-time overhead of one extra query +to load the global_privs bitmap. This is probably a quite acceptable +overhead as typically each user will have relatively few roles. + +If the overhead of any of this seems too significant there are +essentially 4 options: +- Simplify the design. +- Defer the overhead until it is absolutely necessary. This can be done + with connection functions where we may be able to defer the overhead + of loading relational context data until the time that we first need + it. +- Implement a caching solution (check out pgmemcache). Using an + in-memory cache will save data set-up queries from having to be + repeated. This is pretty complex though and may require you to write + code in C. +- Suffer the performance hit. + +\subsection Implementation Implement the Initialisation Function + +The initialisation function \ref API-control-init is a critical +function and must be defined. It will be called by Veil, when the first +in-built Veil function is invoked. It is responsible for three distinct +tasks: + +- Initialisation of session variables +- Initialisation of shared variables +- Re-initialisation of variables during reset + +The boolean parameter to veil_init will be false on initial session +startup, and true when performing a reset (\ref API-control-reset). + +Shared variables are created using \ref API-variables-share. This +returns a boolean result describing whether the variable already +existed. If so, and we are not performing a reset, the current session +need not initialise it. + +Session variables are simply created by referring to them. It is worth +creating and initialising all session variables to "fix" their data +types. This will prevent other functions from misusing them. + +If the boolean parameter to veil_init is true, then we are performing a +memory reset, and all shared variables should be re-initialised. A +memory reset will be performed whenever underlying, essentially static, +data has been modified. For example, when new privileges have been +added, we must rebuild all privilege bitmaps to accommodate the new +values. + +\subsection Implementation2 Implement the Connection Functions + +The connection functions have to authenticate the connecting user, and +then initialise the user's session. + +Authentication should use a secure process in which no plaintext +passwords are ever sent across the wire. Veil does not provide +authentication services. For your security needs you should probably +check out pgcrypto. + +Initialising a user session is generally a matter of initialising +bitmaps that describe the user's base privileges, and may also involve +setting up bitmap hashes of their relational privileges. Take a look at +the demo (\ref demo-sec) for a working example of this. + +\subsection Implementation3 Implement the Access Functions + +Access functions provide the low-level access controls to individual +records. As such their performance is critical. It is generally better +to make the connection functions to more work, and the access functions +less. Bear in mind that if you perform a query that returns 10,000 rows +from a table, your access function for that view is going to be called +10,000 times. It must be as fast as possible. + +When dealing with relational contexts, it is not always possible to keep +all privileges for every conceivable relationship in memory. When this +happens, your access function will have to perform a query itself to +load the specific data into memory. If your application requires this, +you should: + +- Ensure that each such query is as simple and efficient as possible +- Cache your results in some way + +You may be able to trade-off between the overhead of connection +functions and that of access functions. For instance if you have a +relational security context based upon a tree of relationships, you may +be able to load all but the lowest level branches of the tree at connect +time. The access function then has only to load the lowest level branch +of data at access time, rather than having to perform a full tree-walk. + +Caching can be very effective, particularly for nested loop joins. If +you are joining A with B, and they both have the same access rules, once +the necessary privilege to access a record in A has been determined and +cached, we will be able to use the cached privileges when checking for +matching records in B (ie we can avoid repeating the fetch). + +\subsection Implementation4 Implement the views and instead-of triggers + +This is the final stage of implementation. For every base table you +must create a secured view and a set of instead-of triggers for insert, +update and delete. Refer to the demo (\ref demo-sec) for details of +this. + +\subsection Testing Testing + +Be sure to test it all. Specifically, test to ensure that failed +connections do not provide any privileges, and to ensure that all +privileges assigned to highly privileged users are cleared when a more +lowly privileged user takes over a connection. Also ensure that +the underlying tables and raw veil functions are not accessible from +user accounts. + +\section Automation Automatic code generation + +Note that the bulk of the code in a Veil application is in the +definition of secured views and instead-of triggers, and that this code +is all very similar. Consider using a code-generation tool to implement +this. A suitable code-generator for Veil may be provided in subsequent +releases. + +Next: \ref Demo + +*/ +/*! \page Demo A Full Example Application: The Veil Demo +\section demo-sec The Veil Demo Application + +The Veil demo application serves two purposes: +- it provides a demonstration of Veil-based access controls; +- it provides a working example of how to build a secured system using Veil. + +This section covers the following topics: + +- \ref demo-install +- \subpage demo-model +- \subpage demo-security +- \subpage demo-explore +- \subpage demo-code +- \subpage demo-uninstall + +\subsection demo-install Installing the Veil demo + +The veil_demo application, is packaged as an extension just like Veil +itself, and is installed as part of the installation of Veil. To create +a database containing the veil_demo application: +- create a new database +- connect to that database +- execute create extension veil; +- execute create extension veil_demo;can_connect. + +\subsection demo-roles Roles + +A role is a named collection of privileges. Privileges are assigned to +roles through role_privileges. Roles exist to reduce the number of +individual privileges that have to be assigned to users, etc. Instead +of assigning twenty or more privileges, we assign a single role that +contains those privileges. + +In this application there is a special role, Personal +Context that contains the set of privileges that apply to all +users in their personal context. This role does not need to be +explicitly assigned to users, and should probably never be explicitly +assigned. + +Assignments of roles in the global context are made through +person_roles, and in the project (relational) context through +assignments. + +\subsection demo-role-privs Role_Privileges + +Role privileges describe the set of privileges for each role. + +\subsection demo-role-roles Role_Roles + +This is currently unused in the Veil demo application. Role roles +provides the means to assign roles to other roles. This allows new +roles to be created as combinations of existing roles. The use of this +table is currently left as an exercise for the reader. + +\subsection demo-persons Persons + +This describes each person. A person is someone who owns data and who +may connect to the database. This table should contain authentication +information etc. In actuality it just maps a name to a person_id. + +\subsection demo-projects Projects + +A project represents a real-world project, to which many persons may be +assigned to work. + +\subsection demo-person-roles Person_Roles + +This table describes the which roles have been assigned to users in the +global context. + +\subsection demo-assignments Assignments + +This describes the roles that have been assigned to a person on a +specific project. Assignments provide privilege to a user in the +project context. + +\subsection demo-detail_types Detail_Types + +This is a lookup-table that describes general-purpose attributes that +may be assigned to persons or project. An example of an attribute for a +person might be birth-date. For a project it might be inception date. +This allows new attributes to be recorded for persons, projects, etc +without having to add columns to the table. + +Each detail_type has a required_privilege field. This identifies the +privilege that a user must have in order to be able to see attributes of +the specific type. + +\subsection demo-person_details Person_Details + +These are instances of specific attributes for specific persons. + +\subsection demo-project-details Project_Details + +These are instances of specific attributes for specific projects. + +\section Demo-Views The Demo Application's Helper Views + +Getting security right is difficult. The Veil demo provides a number of +views that help you view the privileges you have in each context. + +- my_global_privs shows you the privileges you have in the global + context +- my_personal_privs shows you the privileges you have in the + personal context +- my_project_privs shows you the privileges you have for each project + in the project context +- my_privs shows you all your privileges in all contexts +- my_projects shows you all the projects to which you have been assigned + +Using these views, access control mysteries may be more easily tracked +down. + +Next: \ref demo-security + +*/ +/*! \page demo-security The Demo Database Security Model +\section demo-secmodel The Demo Database Security Model + +The Veil demo has three security contexts. + +- Personal Context applies to personal data that is owned by the + connected user. All users have the same privileges in personal + context, as defined by the role Personal Context. +- Global Context applies equally to every record in a table. If a user + has SELECT_X privilege in the global context, they will + be able to select every record in X, regardless of + ownership. Privileges in global context are assigned through + person_roles. +- Project Context is a relational context and applies to project data. + If you are assigned a role on a project, you will be given specific + access to certain project tables. The roles you have been assigned + will define your access rights. + +The following sections identify which tables may be accessed in which +contexts. + +\subsection demo-global-context The Global Context +The global context applies to all tables. All privilege checking +functions will always look for privileges in the global context. + +\subsection demo-personal-context Personal Context +The following tables may be accessed using rights assigned in the +personal context: +- persons +- assignments +- person_details + +\subsubsection demo-project-context Project Context +The following tables may be accessed using rights assigned in the +project context: +- projects +- assignments +- project_details + +Next: \ref demo-explore + +*/ +/*! \page demo-explore Exploring the Demo +\section demo-use Exploring the Demo +\subsection demo-connect Accessing the Demo Database +Using your favourite tool connect to your veil_demo database. + +You will be able to see all of the demo views, both the secured views and +the helpers. But you will not initially be able to see any records: +each view will appear to contain no data. To gain some privileges you +must identify yourself using the connect_person() function. + +There are 6 persons in the demo. You may connect as any of them and see +different subsets of data. The persons are + +- 1 Deb (the DBA). Deb has global privileges on everything. She needs + them as she is the DBA. +- 2 Pat (the PM). Pat has the manager role globally, and is the project + manager of project 102. Pat can see all but the most confidential + personal data, and all data about her project. +- 3 Derick (the director). Derick can see all personal and project + data. He is also the project manager for project 101, the secret + project. +- 4 Will (the worker). Will has been assigned to both projects. He has + minimal privileges and cannot access project confidential data. +- 5 Wilma (the worker). Willma has been assigned to project 101. She has + minimal privileges and cannot access project confidential data. +- 6 Fred (the fired DBA). Fred has all of the privileges of Deb, except + for can_connect privilege. This prevents Fred from being able to do + anything. + +Here is a sample session, showing the different access enjoyed by +different users. + +\verbatim +veildemo=> select connect_person(4); + connect_person +---------------- + t +(1 row) + +veildemo=> select * from persons; + person_id | person_name +-----------+------------------- + 4 | Will (the worker) +(1 row) + +veildemo=> select * from person_details; + person_id | detail_type_id | value +-----------+----------------+-------------- + 4 | 1003 | 20050105 + 4 | 1002 | Employee + 4 | 1004 | 30,000 + 4 | 1005 | 19660102 + 4 | 1006 | 123456789 + 4 | 1007 | Subservience +(6 rows) + +veildemo=> select * from project_details; + project_id | detail_type_id | value +------------+----------------+---------- + 102 | 1001 | 20050101 + 102 | 1002 | Ongoing +(2 rows) + +veildemo=> select connect_person(2); + connect_person +---------------- + t +(1 row) + +veildemo=> select * from person_details; + person_id | detail_type_id | value +-----------+----------------+------------------- + 1 | 1003 | 20050102 + 2 | 1003 | 20050103 + 3 | 1003 | 20050104 + 4 | 1003 | 20050105 + 5 | 1003 | 20050106 + 6 | 1003 | 20050107 + 1 | 1002 | Employee + 2 | 1002 | Employee + 3 | 1002 | Employee + 4 | 1002 | Employee + 5 | 1002 | Employee + 6 | 1002 | Terminated + 2 | 1004 | 50,000 + 1 | 1005 | 19610102 + 2 | 1005 | 19600102 + 3 | 1005 | 19650102 + 4 | 1005 | 19660102 + 5 | 1005 | 19670102 + 2 | 1006 | 123456789 + 1 | 1007 | Oracle, C, SQL + 2 | 1007 | Soft peoply-stuff + 3 | 1007 | None at all + 4 | 1007 | Subservience + 5 | 1007 | Subservience +(24 rows) + +veildemo=> select * from project_details; + project_id | detail_type_id | value +------------+----------------+---------- + 102 | 1001 | 20050101 + 102 | 1002 | Ongoing + 102 | 1008 | $100,000 +(3 rows) + +veildemo=> + +\endverbatim + +Next: \ref demo-code + +*/ +/*! \page demo-code The Demo Code +\dontinclude funcs.sql +\section demo-codesec The Code +\subsection demo-code-veil-init veil_init(bool) + +This function is called at the start of each session, and whenever +\ref API-control-reset is called. The parameter, doing_reset, is +false when called to initialise a session and true when called from +veil_perform_reset(). + +This definition replaces the standard default, do-nothing, +implementation that is shipped with Veil (see \ref API-control-init). + +\skip veil_init(bool) +\until veil_share(''det_types_privs'') + +The first task of veil_init() is to declare a set of Veil shared +variables. This is done by calling \ref API-variables-share. This function +returns true if the variable already exists, and creates the variable +and returns false, if not. + +These variables are defined as shared because they will be identical for +each session. Making them shared means that only one session has to +deal with the overhead of their initialisation. + +\until end if; + +We then check whether the shared variables must be initialised. We will +initialise them if they have not already been initialised by another +session, or if we are performing a reset (see \ref API-control-reset). + +Each variable is initialised in its own way. + +Ranges are set by a single call to \ref API-basic-init-range. Ranges are +used to create bitmap and array types of a suitable size. + +Int4Arrays are used to record mappings of one integer to another. In +the demo, they are used to record the mapping of detail_type_id to +required_privilege_id. We use this variable so that we can look-up the +privilege required to access a given project_detail or person_detail +without having to explicitly fetch from attribute_detail_types. + +Int4Arrays are initialised by a call to \ref API-intarray-init, and +are populated by calling \ref API-intarray-set for each value to +be recorded. Note that rather than using a cursor to loop through each +detail_type record, we use select count(). This requires less code and +has the same effect. + +We use a BitmapArray to record the set of privileges for each role. Its +initialisation and population is handled in much the same way as +described above for Int4Arrays, using the functions \ref +API-bmarray-init and \ref API-bmarray-setbit. + +\until end; + +The final section of code defines and initialises a set of session +variables. These are defined here to avoid getting undefined variable +errors from any access function that may be called before an +authenticated connection has been established. + +Note that this and all Veil related functions are defined with +security definer attributes. This means that the function +will be executed with the privileges of the function's owner, rather +than those of the invoker. This is absolutely critical as the invoker +must have no privileges on the base objects, or on the raw Veil +functions themselves. The only access to objects protected by Veil must +be through user-defined functions and views. + +\subsection demo-code-connect-person connect_person(int4) + +This function is used to establish a connection from a specific person. +In a real application this function would be provided with some form of +authentication token for the user. For the sake of simplicity the demo +allows unauthenticated connection requests. + +\skip connect_person(int4) +\until end; + +This function identifies the user, ensures that they have can_connect +privilege. It initialises the global_context bitmap to contain the +union of all privileges for each role the person is assigned through +person_roles. It also sets up a bitmap hash containing a bitmap of +privileges for each project to which the person is assigned. + +\subsection demo-code-global-priv i_have_global_priv(int4) + +This function is used to determine whether a user has a specified +privilege in the global context. It tests that the user is connected +using veil_int4_get(), and then checks whether the +specified privilege is present in the global_context +bitmap. + +\skip function i_have_global_priv(int4) +\until security definer; + +The following example shows this function in use by the secured view, +privileges: + +\dontinclude views.sql +\skip create view privileges +\until i_have_global_priv(10004); + +The privileges used above are select_privileges (10001), +insert_privileges (10002), update_privileges +(10003), and delete_privileges (10004). + +\subsection demo-code-personal-priv i_have_personal_priv(int4, int4) + +This function determines whether a user has a specified privilege to a +specified user's data, in the global or personal contexts. It performs +the same tests as for i_have_global_context(). If the user +does not have access in the global context, and the connected user is +the same user as the owner of the data we are looking at, then we test +whether the specified privilege exists in the role_privs +bitmap array for the Personal Context role. + +\dontinclude funcs.sql +\skip function i_have_personal_priv(int4, int4) +\until end; + +Here is an example of this function in use from the persons secured view: + +\dontinclude views.sql +\skip create view persons +\until i_have_personal_priv(10013, person_id); + +\subsection demo-code-project-priv i_have_project_priv(int4, int4) +This function determines whether a user has a specified privilege in the +global or project contexts. If the user does not have the global +privilege, we check whether they have the privilege defined in the +project_context BitmapHash. + +\dontinclude funcs.sql +\skip function i_have_project_priv(int4, int4) +\until security definer; + +Here is an example of this function in use from the instead-of insert +trigger for the projects secured view: + +\dontinclude views.sql +\skip create rule ii_projects +\until i_have_project_priv(10018, new.project_id); + +\subsection demo-code-proj-pers-priv i_have_proj_or_pers_priv(int4, int4, int4) +This function checks all privileges. It starts with the cheapest check +first, and short-circuits as soon as a privilege is found. + +\dontinclude funcs.sql +\skip function i_have_proj_or_pers_priv(int4, int4, int4) +\until security definer; + +Here is an example of this function in use from the instead-of update +trigger for the assignments secured view: + +\dontinclude views.sql +\skip create rule ii_assignments +\until i_have_proj_or_pers_priv(10027, old.project_id, old.person_id); + +\subsection demo-code-pers-detail-priv i_have_person_detail_priv(int4, int4) +This function is used to determine which types of person details are +accessible to each user. This provides distinct access controls to each +attribute that may be recorded for a person. + +\dontinclude funcs.sql +\skip function i_have_person_detail_priv(int4, int4) +\until security definer; + +The function is shown in use, below, in the instead-of delete trigger +for person_details. Note that two distinct access functions are being +used here. + +\dontinclude views.sql +\skip create rule id_person_details +\until i_have_person_detail_priv(old.detail_type_id, old.person_id); + +Next: \ref demo-uninstall + +*/ +/*! \page demo-uninstall Removing The Demo Database +\section demo-clean-up Removing The Demo Database +In your veil_demo database execute: +- drop extension veil_demo; + +Next: \ref Management + +*/ +/*! \page Management Managing Privileges, etc +\section Management-sec Managing Privileges, etc +The management of privileges and their assignments to roles, persons, +etc are the key to securing a veil-based application. It is therefore +vital that privilege assignment is itself a privileged operation. + +The veil demo does not provide an example of how to do this, and this +section does little more than raise the issue. + +IT IS VITAL THAT YOU CAREFULLY LIMIT HOW PRIVILEGES ARE MANIPULATED AND +ASSIGNED! + +Here are some possible rules of thumb that you may wish to apply: + +- give only the most senior and trusted users the ability to assign + privileges; +- allow only the DBAs to create privileges; +- allow only 1 or 2 security administrators to manage roles; +- allow roles or privileges to be assigned only by users that have both + the "assign_privileges"/"assign_roles" privileges, and that themselves + have the privilege or role they are assigning; +- consider having an admin privilege for each table and only allow users + to assign privileges on X if they have "admin_x" privilege; +- limit the users who have access to the role/privilege management + functions, and use function-level privileges to enforce this; +- audit/log all assignments of privileges and roles; +- send email to the security administrator whenever role_privileges are + manipulated and when roles granting high-level privileges are granted. + +Next: \ref Esoteria + +*/ +/*! \page Esoteria Exotic and Esoteric uses of Veil + +\section Esoteria-sec Exotic and Esoteric uses of Veil +Actually this is neither exotic nor particularly esoteric. The title is +simply wishful thinking on the author's part. +\subsection layered-sessions Multi-Layered Connections +So far we have considered access controls based only on the user. If we +wish to be more paranoid, and perhaps we should, we may also consider +limiting the access rights of each application. + +This might mean that reporting applications would have no ability to +update data, that financial applications would have no access to +personnel data, and that personnel apps would have no access to business +data. + +This can be done in addition to the user-level checks, so that even if I +have DBA privilege, I can not subvert the personnel reporting tools to +modify financial data. + +All access functions would check the service's privileges in addition to +the user's before allowing any operation. + +This could be implemented with a connect_service() function that would +be called only once per session and that *must* be called prior to +connecting any users. Alternatively, the connected service could be +inferred from the account to which the service is connected. + +\subsection columns Column-Level Access Controls + +Although veil is primarily intended for row-based access controls, +column-based is also possible. If this is required it may be better to +use a highly normalised data model where columns are converted instead +into attributes, much like the person_details and project_details tables +from the demo application (\ref demo-sec). + +If this is not possible then defining access_views that only show +certain columns can be done something like this: + +\verbatim +create view wibble(key, col1, col2, col3) as +select key, + case when have_col_priv(100001) then col1 else null end, + case when have_col_priv(100002) then col2 else null end, + case when have_col_priv(100003) then col3 else null end +where have_row_priv(1000); +\endverbatim + +The instead-of triggers for this are left as an exercise. + +Next: \ref install + +*/ +/*! \page install Installation and Configuration +\section install_sec Installation +\subsection Get Getting Veil +Veil can be downloaded as a gzipped tarball from +http://pgfoundry.org/projects/veil/ + +The latest development version can also be retrieved from the cvs +repository using the following commands. When prompted for a password +for anonymous, simply press the Enter key. + +\verbatim +cvs -d :pserver:anonymous@cvs.pgfoundry.org:/cvsroot/veil login +cvs -d :pserver:anonymous@cvs.pgfoundry.org:/cvsroot/veil checkout veil +\endverbatim + +\subsection Pre-requisites Pre-requisites +You must have a copy of the Postgresql header files available in order +to build Veil. For this, you may need to install the postgres developer +packages for your OS, or even build postgres from source. +\subsection build-sub Building Veil +Veil is built using the postgresql extension building infastructure, +PGXS. In order to be able to build using PGXS, the following command +must return the location of the PGXS makefile: +\verbatim +$ pg_config --pgxs +\endverbatim +You may need to modify your PATH variable to make pg_config usable, or +ensure it is for the correct postgresql version. + +To build the postgres extensions, simply run make with no target: +\verbatim +$ make +\endverbatim + +To build the veil documentation (the documentation you are now reading) +use make docs. + +Note that the build system deliberately avoids using make recursively. +Search the Web for "Recursive Make Considered Harmful" for the reasons +why. This makes the construction of the build system a little different +from what you may be used to. This may or may not turn out to be a good +thing. \ref Feedback "Feedback" is welcomed. + +\subsection Install Installing Veil +As the postgres, pgsql, or root user, run make install. +\verbatim +make install +\endverbatim + +\section configuration Configuration +To configure Veil, the following lines should be added to your +postgresql.conf: +\code +shared_preload_libraries = '/veil.so' + +custom_variable_classes = 'veil' + +#veil.dbs_in_cluster = 1 +#veil.shared_hash_elems = 32 +#veil.shmem_context_size = 16384 +\endcode + +The three configuration options, commented out above, are: +- dbs_in_cluster + The number of databases, within the database cluster, that + will use Veil. Each such database will be allocated 2 chunks of + shared memory (of shmem_context_size), and a single LWLock. + It defaults to 1. + +- shared_hash_elems + This describes how large a hash table should be created for veil + shared variables. It defaults to 32. If you have more than about 20 + shared variables you may want to increase this to improve + performance. This setting does not limit the number of variables that + may be defined, it just limits how efficiently they may be accessed. + +- shmem_context_size + This sets an upper limit on the amount of shared memory for a single + Veil shared memory context (there will be two of these). It defaults + to 16K. Increase this if you have many shared memory structures. + +\subsection Regression Regression Tests +Veil comes with a built-in regression test suite. Use make +regress or make check (after installing and +configuring Veil) to run this. You will need superuser access to +Postgres in order to create the regression test database. The +regression test assumes you will have a postgres superuser account named +the same as your OS account. If pg_hba.conf disallows "trust"ed access +locally, then you will need to provide a password for this account in +your .pgpass file (see postgres documentation for details). + +The regression tests are all contained within the regress directory and +are run by the regress.sh shell script. Use the -h option to get +fairly detailed help. + +\subsection Demodb_install Demo Database +As with the regression tests, you will need to be a privileged database +user to be able to create the demo database. For more on installing the +demo database see \ref demo-install. + +\subsection Debugging Debugging +If you encounter problems with Veil, you may want to try building with +debug enabled. Define the variable VEIL_DEBUG on the make command line +to add extra debug code to the executable: +\verbatim +$ make clean; make VEIL_DEBUG=1 all +\endverbatim + +This is a new feature and not yet fully formed but is worth trying if +Veil appears to be misbehaving. If any of the debug code encounters a +problem, ERRORs will be raised. + +Next: \ref History + +*/ +/*! \page History History and Compatibility +\section past Changes History +\subsection v1_0 Version 1.0.0 (Stable) (2011-07-22) +This is the first version of Veil to be considered production ready, and +completely stable. It is for use only with PostgreSQL 9.1. Support for +older versions of PostgreSQL has been removed in this version. + +Major changes include: +- revamp of the build system to use PGXS and the new PostgreSQL 9.1 + extensions mechanism. Veil is now built as an extension. +- removal of the old veil_trial mechanism, which allowed Veil to be + tried out without fully installing it. This has removed much + unnecessary complexity. +- much general code cleanup, including the removal of conditional code + for older PostgreSQL versions. +- documentation changes, including improved comments for Veil + functions. + +\subsection v9_12 Version 0.9.12 (2010-11-19) +Release for compatibility with PostgreSQL V9.0. Minor bugfixes and +improvements to the build system. Also added documentation about Veil's +limitations. + +\subsection v9_11 Version 0.9.11 (2010-03-12) +Bugfix release, fixing a serious memory corruption bug that has existed +in all previous versions. Users are strongly encouraged to avoid using +older versions of Veil. + +The version number has been deliberatley bumped past 0.9.10 to emphasize +that the last part of the version is a two digit number. + +\subsection v9_9 Version 0.9.9 (2009-07-06) +New release to coincide with PostgreSQL V8.4. + +\subsection v9_8 Version 0.9.8 (2008-02-06) +This is the first Beta release. It incorporates a few bug fixes, a new +serialisation API, improvements to the autoconf setup and makefiles, and +some documentation improvements. The status of Veil has been raised to +Beta in recognition of its relative stability. + +\subsection v9_6 Version 0.9.6 (2008-02-06) +This release has minor changes to support PostgreSQL 8.3. + +\subsection v9_5 Version 0.9.5 (2007-07-31) +This is a bugifx release, fixing a memory allocation bug in the use of +bitmap_refs. There are also fixes for minor typos, etc. + +\subsection v9_4 Version 0.9.4 (2007-02-21) +This is a bugifx release, providing: + - fix for major bug with recursive handling of spi connect, etc; + - improvement to session initialisation code to do more up-front work + in ensure_init(); + - safer initialisation of malloc'd data structures; + - improved error messages for shared memory exhaustion cases; + - addition of debug code including canaries in data structures; + - improvement to autoconf to better support Debian GNU/Linux, and OSX; + - improvement to autoconf/make for handling paths containing spaces; + - improvement to regression tests to better support OSX; + - removal of spurious debug warning messages. + +\subsection v9_3 Version 0.9.3 (2006-10-31) +This version uses the new Postgres API for reserving shared memory for +add-ins. It also allows the number of Veil-enabled databases for a +cluster to be configured, and refactors much of the shared memory code. +A small fix for the Darwin makefile was also made. + +\subsection v9_2 Version 0.9.2 (2006-10-01) +This version was released to coincide with Postgres 8.2beta1 and first +made use of new Postgres APIs to allow Veil to be a good Postgres +citizen. + +With prior versions of Veil, or prior versions of Postgres, Veil steals +from Postgres the shared memory that it requires. This can lead to the +exhaustion of Postgres shared memory. + +Unfortunately, the Postgres API for shared memory reservation had to +change follwing 8.2.beta1, and this version of Veil is therefore deprecated. + +\subsection v9_1 Version 0.9.1 (2006-07-04) +This release fixed a small number of bugs and deficiencies: +- major error in veil_perform_reset that prevented proper use of the two +interdependant shared memory contexts +- minor improvements in the build process to "configure" and friends +- minor documentation improvements + +\subsection v9_0 Version 0.9.0 (2005-10-04) +This was the first public alpha release of Veil. + +\section forecast Change Forecast +New versions will be released with each new major version of +PostgreSQL. Once there are three PostgreSQL versions for which Veil has +been at production status, the change history and support matrix for for +pre-production versions will be removed from this documentation. + +\section compatibility Supported versions of Postgres + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Veil versionPostgres Version
7.48.08.18.2beta18.28.38.49.09.1
0.9.0 Alpha111------
0.9.1 Alpha111------
0.9.2 Alpha-112-----
0.9.3 Alpha-11-3----
0.9.4 Alpha-11-3----
0.9.5 Alpha-11-3----
0.9.6 Alpha-11-33---
0.9.8 Beta-11-33---
0.9.9 Beta-11-333--
0.9.11 Beta-11-333--
0.9.12 Beta-----333-
1.0.0 (Stable)--------Yes
+Notes: + +1) These combinations of Veil and Postgres provide no configuration + options for shared memory. Veil's shared memory may be exhausted by + too many requests for large shared objects. Furthermore, Postgres' + own shared memory may be easily exhausted by creating too many + Veil-using databases within a cluster. + +2) This version is deprecated + +3) These combinations of Veil and Postgres provide full configuration + options for shared memory usage, and Veil cooperates with Postgres + for the allocation of such memory meaning that it is not possible to + use Veil to exhaust Postgres' shared memory. This is the minimum + Veil configuration recommended for production use. + +\section platforms Supported Platforms +Veil should be buildable on any platform supported by PostgreSQL and +PGXS. + +Next: \ref Feedback + +*/ +/*! \page Feedback Bugs and Feedback +\section Feedback Bugs and Feedback +For general feedback, to start and follow discussions, etc please join +the veil-general@pgfoundry.org mailing list. + +If you wish to report a bug or request a feature, please send mail to +veil-general@pgfoundry.org + +If you encounter a reproducible veil bug that causes a database server +crash, a gdb backtrace would be much appreciated. To generate a +backtrace, you will need to login to the postgres owner account on the +database server. Then identify the postgres backend process associated +with the database session that is going to crash. The following command +identifies the backend pid for a database session started by marc: + +\verbatim +$ ps auwwx | grep ^postgres.*ma[r]c | awk '{print $2}' +\endverbatim + +Now you invoke gdb with the path to the postgres binary and the pid for +the backend, eg: + +\verbatim +$ gdb /usr/lib/postgresql/9.1/bin/postgres 5444 +\endverbatim + +Hit c and Enter to get gdb to allow the session to continue. Now, +reproduce the crash from your database session. When the crash occurs, +your gdb session will return to you. Now type bt and Enter to get a +backtrace. + +If you wish to contact the author offlist, you can find him at his website +http://bloodnok.com/Marc.Munro + +Next: \ref Performance + +*/ +/*! \page Performance Performance +\section perf Performance +Attempts to benchmark veil using pgbench have not been very successful. +It seems that the overhead of veil is small enough that it is +overshadowed by the level of "noise" within pgbench. + +Based on this inability to properly benchmark veil, the author is going +to claim that "it performs well". + +To put this into perspective, if your access functions do not require +extra fetches to be performed in order to establish your access rights, +you are unlikely to notice or be able to measure any performance hit +from veil. + +If anyone can provide good statistical evidence of a performance hit, +the author would be most pleased to hear from you. + +Next: \ref Credits + +*/ +/*! \page Credits Credits +\section Credits +The Entity Relationship Diagram in section \ref demo-erd was produced +automatically from an XML definition of the demo tables, using Autograph +from CF Consulting. Thanks to Colin Fox for allowing its use. + +Thanks to the PostgreSQL core team for providing PostgreSQL. + +Thanks to pgfoundry for providing a home for this project. +*/ + diff --git a/src/veil_mainpage.d b/src/veil_mainpage.d new file mode 100644 index 0000000..f9995de --- /dev/null +++ b/src/veil_mainpage.d @@ -0,0 +1,2 @@ +src/veil_mainpage.o src/veil_mainpage.d: \ + src/veil_mainpage.c diff --git a/src/veil_query.c b/src/veil_query.c new file mode 100644 index 0000000..af9234f --- /dev/null +++ b/src/veil_query.c @@ -0,0 +1,302 @@ +/** + * @file veil_query.c + * \code + * Author: Marc Munro + * Copyright (c) 2005 - 2011 Marc Munro + * License: BSD + * + * \endcode + * @brief + * Functions to simplify SPI-based queries. These are way more + * sophisticated than veil really needs but are nice and generic. + * + */ + + +#include +#include "postgres.h" +#include "catalog/pg_type.h" +#include "executor/spi.h" +#include "veil_version.h" +#include "access/xact.h" +#include "veil_funcs.h" + +/** + * A Fetch_fn is a function that processes records, one at a time, + * returned from a query. + */ +typedef bool (Fetch_fn)(HeapTuple, TupleDesc, void *); + + + +/** + * The number of records to fetch in one go from the query executor. + */ +#define FETCH_SIZE 20 + + +/** + * Counter to assess depth of recursive spi calls, so that we can + * sensibly and safely use spi_push and spi_pop when appropriate. + */ +static int4 query_depth = 0; + +/** + * State variable used to assess whther query_depth may have been left + * in an invalid state following an error being raised. + */ +static TransactionId connection_xid = 0; + +/** + * If already connected in this session, push the current connection, + * and get a new one. + * We are already connected, if: + * - are within a query + * - and the current transaction id matches the saved transaction id + */ +int +vl_spi_connect(void) +{ + TransactionId xid = GetCurrentTransactionId(); + + if (query_depth > 0) { + if (xid == connection_xid) { + SPI_push(); + } + else { + /* The previous transaction must have aborted without + * resetting query_depth */ + query_depth = 0; + } + } + + connection_xid = xid; + return SPI_connect(); +} + +/** + * Reciprocal function for vl_spi_connect() + */ +int +vl_spi_finish(void) +{ + int spi_result = SPI_finish(); + if (query_depth > 0) { + SPI_pop(); + } + + return spi_result; +} + +/** + * Prepare a query for query(). This creates and executes a plan. The + * caller must have established SPI_connect. It is assumed that no + * parameters to the query will be null. + * \param qry The text of the SQL query to be performed. + * \param nargs The number of input parameters ($1, $2, etc) to the query + * \param argtypes Pointer to an array containing the OIDs of the data + * \param args Actual parameters + * types of the parameters + * \param read_only Whether the query should be read-only or not + * \param saved_plan Adress of void pointer into which the query plan + * will be saved. Passing the same void pointer on a subsequent call + * will cause the saved query plan to be re-used. + */ +static void +prepare_query(const char *qry, + int nargs, + Oid *argtypes, + Datum *args, + bool read_only, + void **saved_plan) +{ + void *plan; + int exec_result; + + if (saved_plan && *saved_plan) { + /* A previously prepared plan is available, so use it */ + plan = *saved_plan; + } + else { + if (!(plan = SPI_prepare(qry, nargs, argtypes))) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("prepare_query fails"), + errdetail("SPI_prepare('%s') returns NULL " + "(SPI_result = %d)", + qry, SPI_result))); + } + + if (saved_plan) { + /* We have somewhere to put the saved plan, so save it. */ + *saved_plan = SPI_saveplan(plan); + } + } + + exec_result = SPI_execute_plan(plan, args, NULL, read_only, 0); + if (exec_result < 0) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("prepare_query fails"), + errdetail("SPI_execute_plan('%s') returns error %d", + qry, exec_result))); + } +} + +/** + * Prepare and execute a query. Query execution consists of a call to + * process_row for each returned record. Process_row can return a + * single value to the caller of this function through the fn_param + * parameter. It is the caller's responsibility to establish an SPI + * connection with SPI_connect. It is assumed that no parameters to + * the query, and no results will be null. + * \param qry The text of the SQL query to be performed. + * \param nargs The number of input parameters ($1, $2, etc) to the query + * \param argtypes Pointer to an array containing the OIDs of the data + * \param args Actual parameters + * types of the parameters + * \param read_only Whether the query should be read-only or not + * \param saved_plan Adress of void pointer into which the query plan + * will be saved. Passing the same void pointer on a subsequent call + * will cause the saved query plan to be re-used. + * \param process_row The ::Fetch_fn function to be called for each + * fetched row to process it. If this is null, we simply count the row, + * doing no processing on the tuples returned. + * \param fn_param An optional parameter to the process_row function. + * This may be used to return a value to the caller. + * \return The total number of records fetched and processed by + * process_row. + */ +static int +query(const char *qry, + int nargs, + Oid *argtypes, + Datum *args, + bool read_only, + void **saved_plan, + Fetch_fn process_row, + void *fn_param) +{ + int row; + int fetched = 0; + + query_depth++; + prepare_query(qry, nargs, argtypes, args, read_only, saved_plan); + + for(row = 0; row < SPI_processed; row++) { + fetched++; + /* Process a row using the processor function */ + if (!process_row(SPI_tuptable->vals[row], + SPI_tuptable->tupdesc, + fn_param)) + { + break; + } + } + query_depth--; + return fetched; +} + + +/** + * ::Fetch_fn function for processing a single row of a single integer for + * ::query. + * \param tuple The row to be processed + * \param tupdesc Descriptor for the types of the fields in the tuple. + * \param p_result Pointer to an int4 variable into which the value + * returned from the query will be placed. + * \return false. This causes ::query to terminate after processing a + * single row. + */ +static bool +fetch_one_bool(HeapTuple tuple, TupleDesc tupdesc, void *p_result) +{ + static bool ignore_this = false; + bool col = DatumGetBool(SPI_getbinval(tuple, tupdesc, 1, &ignore_this)); + *((bool *) p_result) = col; + + return false; +} + +/** + * ::Fetch_fn function for processing a single row of a single integer for + * ::query. + * \param tuple The row to be processed + * \param tupdesc Descriptor for the types of the fields in the tuple. + * \param p_result Pointer to an int4 variable into which the value + * returned from the query will be placed. + * \return false. This causes ::query to terminate after processing a + * single row. + */ +static bool +fetch_one_str(HeapTuple tuple, TupleDesc tupdesc, void *p_result) +{ + char *col = SPI_getvalue(tuple, tupdesc, 1); + char **p_str = (char **) p_result; + *p_str = col; + + return false; +} + +/** + * Executes a query that returns a single bool value. + * + * @param qry The text of the query to be performed. + * @param result Variable into which the result of the query will be placed. + * + * @return true if the query returned a record, false otherwise. + */ +bool +vl_bool_from_query(const char *qry, + bool *result) +{ + int rows; + Oid argtypes[0]; + Datum args[0]; + rows = query(qry, 0, argtypes, args, false, NULL, + fetch_one_bool, (void *)result); + return (rows > 0); +} + +/** + * Executes a query by oid, that returns a single string value. + * + * @param qry The text of the query to be performed. + * @param param The oid of the row to be fetched. + * @param result Variable into which the result of the query will be placed. + * + * @return true if the query returned a record, false otherwise. + */ +static bool +str_from_oid_query(const char *qry, + const Oid param, + char *result) +{ + int rows; + Oid argtypes[1] = {OIDOID}; + Datum args[1]; + + args[0] = ObjectIdGetDatum(param); + rows = query(qry, 1, argtypes, args, false, NULL, + fetch_one_str, (void *)result); + return (rows > 0); +} + +/** + * Determine whether the given oid represents an existing database or not. + * + * @param db_id Oid of the database in which we are interested. + * + * @result True if the database exists. + */ + +extern bool +vl_db_exists(Oid db_id) +{ + char dbname[NAMEDATALEN + 1]; + + return str_from_oid_query("select datname from pg_database where oid = $1", + db_id, dbname); +} + + diff --git a/src/veil_query.d b/src/veil_query.d new file mode 100644 index 0000000..dcda96c --- /dev/null +++ b/src/veil_query.d @@ -0,0 +1,5 @@ +src/veil_query.o src/veil_query.d: \ + src/veil_query.c \ + src/veil_version.h \ + src/veil_funcs.h \ + src/veil_datatypes.h diff --git a/src/veil_serialise.c b/src/veil_serialise.c new file mode 100644 index 0000000..1da479c --- /dev/null +++ b/src/veil_serialise.c @@ -0,0 +1,958 @@ +/** + * @file veil_serialise.c + * \code + * Author: Marc Munro + * Copyright (c) 2009 - 2011 Marc Munro + * License: BSD + * + * \endcode + * @brief + * Functions serialising and de-serialising session variables. The + * purpose of this is to allow the contents of session variables to be + * saved for later re-use. They may be saved in files, temporary tables + * or using some smart cache such as memcached (thru the pgmemcache + * add-in). + * + */ + +/*TODO: Provide a patch to postgres to make b64_encode and + * b64_decode extern functions and to elimiate the + * redundant copies from pgcrypto + */ + +#include "postgres.h" +#include "veil_version.h" +#include "veil_funcs.h" +#include "veil_datatypes.h" + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +#define INT4VAR_HDR 'V' +#define RANGE_HDR 'R' +#define BITMAP_HDR 'B' +#define BITMAP_ARRAY_HDR 'A' +#define BITMAP_HASH_HDR 'H' +#define INT4_ARRAY_HDR 'I' +#define BITMAP_HASH_MORE '>' +#define BITMAP_HASH_DONE '.' + + +#define HDRLEN 8 /* HDR field plus int32 for length of + * item */ +#define INT32SIZE_B64 7 +#define BOOLSIZE 1 + + +/* BEGIN SECTION OF CODE COPIED FROM pgcrypto.c */ + +static const char _base64[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static const int8 b64lookup[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 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, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, +}; + +static unsigned +b64_encode(const char *src, unsigned len, char *dst) +{ + char *p, + *lend = dst + 76; + const char *s, + *end = src + len; + int pos = 2; + uint32 buf = 0; + + s = src; + p = dst; + + while (s < end) + { + buf |= (unsigned char) *s << (pos << 3); + pos--; + s++; + + /* write it out */ + if (pos < 0) + { + *p++ = _base64[(buf >> 18) & 0x3f]; + *p++ = _base64[(buf >> 12) & 0x3f]; + *p++ = _base64[(buf >> 6) & 0x3f]; + *p++ = _base64[buf & 0x3f]; + + pos = 2; + buf = 0; + } + if (p >= lend) + { + *p++ = '\n'; + lend = p + 76; + } + } + if (pos != 2) + { + *p++ = _base64[(buf >> 18) & 0x3f]; + *p++ = _base64[(buf >> 12) & 0x3f]; + *p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '='; + *p++ = '='; + } + + return p - dst; +} + +static unsigned +b64_decode(const char *src, unsigned len, char *dst) +{ + const char *srcend = src + len, + *s = src; + char *p = dst; + char c; + int b = 0; + uint32 buf = 0; + int pos = 0, + end = 0; + + while (s < srcend) + { + c = *s++; + + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') + continue; + + if (c == '=') + { + /* end sequence */ + if (!end) + { + if (pos == 2) + end = 1; + else if (pos == 3) + end = 2; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unexpected \"=\""))); + } + b = 0; + } + else + { + b = -1; + if (c > 0 && c < 127) + b = b64lookup[(unsigned char) c]; + if (b < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid symbol"))); + } + /* add it to buffer */ + buf = (buf << 6) + b; + pos++; + if (pos == 4) + { + *p++ = (buf >> 16) & 255; + if (end == 0 || end > 1) + *p++ = (buf >> 8) & 255; + if (end == 0 || end > 2) + *p++ = buf & 255; + buf = 0; + pos = 0; + } + } + + if (pos != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid end sequence"))); + + return p - dst; +} + +/* END SECTION OF CODE COPIED FROM pgcrypto.c */ + +#endif + +/** + * Return the length of a base64 encoded stream for a binary stream of + * ::bytes length. + * + * @param bytes The length of the input binary stream in bytes + * @return The length of the base64 character stream required to + * represent the input stream. + */ +static int +streamlen(int bytes) +{ + return (4 * ((bytes + 2) / 3)); +} + +/** + * Return the length of the header part of a serialised data stream for + * the given named variable. Note that the header contains the name and + * a base64 encode length indicator for the name. + * + * @param name The variable name to be recorded in the header + * @return The length of the base64 character stream required to + * hold the serialised header for the named variable. + */ +static int +hdrlen(char *name) +{ + return HDRLEN + INT32SIZE_B64 + strlen(name); +} + + +/** + * Serialise an int4 value as a base64 stream (truncated to save a + * byte) into *p_stream. + * + * @param p_stream Pointer into stream currently being written. This + * must be large enought to take the contents to be written. This + * pointer is updated to point to the next free slot in the stream after + * writing the int4 value, and is null terminated at that position. + * @param value The value to be written to the stream. + */ +static void +serialise_int4(char **p_stream, int4 value) +{ + int len = b64_encode((char *) &value, sizeof(int32), *p_stream); + (*p_stream) += (len - 1); /* X: dumb optimisation saves a byte */ + (**p_stream) = '\0'; +} + + +/** + * De-serialise an int4 value from a base64 character stream. + * + * @param p_stream Pointer into the stream currently being read. + * must be large enought to take the contents to be written. This + * pointer is updated to point to the next free slot in the stream after + * reading the int4 value. + * @return the int4 value read from the stream + */ +static int4 +deserialise_int4(char **p_stream) +{ + int4 value; + char *endpos = (*p_stream) + INT32SIZE_B64; + char endchar = *endpos; + *endpos = '='; /* deal with dumb optimisation (X) above */ + b64_decode(*p_stream, INT32SIZE_B64 + 1, (char *) &value); + *endpos = endchar; + (*p_stream) += INT32SIZE_B64; + return value; +} + +/** + * Serialise a binary stream as a base64 stream into *p_stream. + * + * @param p_stream Pointer into stream currently being written. This + * pointer is updated to point to the next free slot in the stream after + * writing the contents of instream and is null terminated at that + * position. + * @param bytes The number of bytes to be written. + * @param instream The binary stream to be written. + */ +static void +serialise_stream(char **p_stream, int4 bytes, char *instream) +{ + int len = b64_encode(instream, bytes, *p_stream); + (*p_stream)[len] = '\0'; + (*p_stream) += len; +} + +/** + * De-serialise a binary stream. + * + * @param p_stream Pointer into the stream currently being read. + * pointer is updated to point to the next free slot in the stream after + * reading the stream. + * @param bytes The number of bytes to be read + * @param outstream Pointer into the pre-allocated memory are into which + * the binary from p_stream is to be written. + */ +static void +deserialise_stream(char **p_stream, int4 bytes, char *outstream) +{ + int4 len = streamlen(bytes); + b64_decode(*p_stream, len, outstream); + (*p_stream) += len; +} + + +/** + * Serialise a boolean value into *p_stream. + * + * @param p_stream Pointer into stream currently being written. This + * pointer is updated to point to the next free slot in the stream after + * writing the contents of value and is null terminated at that + * position. A true value is written as 'T', and false as 'F'. + * @param value The boolean value to be written. + */ +static void +serialise_bool(char **p_stream, bool value) +{ + (**p_stream) = value? 'T': 'F'; + (*p_stream)++; + (**p_stream) = '\0'; +} + +/** + * De-serialise a boolean value. + * + * @param p_stream Pointer into the stream currently being read. + * pointer is updated to point to the next free slot in the stream after + * reading the stream. + * @return True or false depending on the contents of p_stream. + */ +static bool +deserialise_bool(char **p_stream) +{ + bool result = (**p_stream) == 'T'; + (*p_stream)++; + + return result; +} + +/** + * Serialise a character value into *p_stream. + * + * @param p_stream Pointer into stream currently being written. This + * pointer is updated to point to the next free slot in the stream after + * writing the contents of value and is null terminated at that + * position. The character is written as a single byte character. + * @param value The character value to be written. + */ +static void +serialise_char(char **p_stream, char value) +{ + (**p_stream) = value; + (*p_stream)++; + (**p_stream) = '\0'; +} + +/** + * De-serialise a character value. + * + * @param p_stream Pointer into the stream currently being read. + * pointer is updated to point to the next free slot in the stream after + * reading the stream. + * @return The value of the character read from p_stream. + */ +static char +deserialise_char (char **p_stream) +{ + char result = **p_stream; + (*p_stream)++; + + return result; +} + +/** + * Serialise a string (containing a name) into *p_stream. + * + * @param p_stream Pointer into stream currently being written. This + * pointer is updated to point to the next free slot in the stream after + * writing the contents of value and is null terminated at that + * position. The character is written as a single byte character. + * @param name The string to be written. + */ +static void +serialise_name(char **p_stream, char *name) +{ + static char *blank_name = ""; + char *safe_name; + if (name) { + safe_name = name; + } + else { + safe_name = blank_name; + } + + serialise_int4(p_stream, strlen(safe_name)); + strcpy((*p_stream), safe_name); + (*p_stream) += strlen(safe_name); + (**p_stream) = '\0'; +} + +/** + * De-serialise a string returning a dynamically allocated string. + * + * @param p_stream Pointer into the stream currently being read. + * pointer is updated to point to the next free slot in the stream after + * reading the stream. + * @return Dynamically allocated copy of string read from *p_stream + */ +static char * +deserialise_name(char **p_stream) +{ + int32 name_len = deserialise_int4(p_stream); + char *result = palloc((name_len + 1) * sizeof(char)); + strncpy(result, *p_stream, name_len); + result[name_len] = '\0'; + (*p_stream) += name_len; + return result; +} + +/** + * Serialise a veil integer variable into a dynamically allocated string. + * + * @param var Pointer to the variable to be serialised + * @param name The name of the variable + * @return Dynamically allocated string containing the serialised + * variable + */ +static char * +serialise_int4var(Int4Var *var, char *name) +{ + int stream_len = hdrlen(name) + BOOLSIZE + INT32SIZE_B64 + 1; + char *stream = palloc(stream_len * sizeof(char)); + char *streamstart = stream; + + serialise_char(&stream, INT4VAR_HDR); + serialise_name(&stream, name); + serialise_bool(&stream, var->isnull); + serialise_int4(&stream, var->value); + return streamstart; +} + +/** + * De-serialise a veil integer variable. + * + * @param **p_stream Pointer into the stream currently being read. + * pointer is updated to point to the next free slot in the stream after + * reading the stream + * @return Pointer to the variable created or updated from the stream. + */ +static VarEntry * +deserialise_int4var(char **p_stream) +{ + char *name = deserialise_name(p_stream); + VarEntry *var = vl_lookup_variable(name); + Int4Var *i4v = (Int4Var *) var->obj; + + if (i4v) { + if (i4v->type != OBJ_INT4) { + vl_type_mismatch(name, OBJ_INT4, i4v->type); + } + } + else { + var->obj = (Object *) vl_NewInt4(var->shared); + i4v = (Int4Var *) var->obj; + } + i4v->isnull = deserialise_bool(p_stream); + i4v->value = deserialise_int4(p_stream); + return var; +} + +/** + * Serialise a veil integer array variable into a dynamically allocated + * string. + * + * @param array Pointer to the variable to be serialised + * @param name The name of the variable + * @return Dynamically allocated string containing the serialised + * variable + */ +static char * +serialise_int4array(Int4Array *array, char *name) +{ + int elems = 1 + array->arraymax - array->arrayzero; + int stream_len = hdrlen(name) + (2 * INT32SIZE_B64) + + streamlen(elems * sizeof(int32)) + 1; + char *stream = palloc(stream_len * sizeof(char)); + char *streamstart = stream; + + serialise_char(&stream, INT4_ARRAY_HDR); + serialise_name(&stream, name); + serialise_int4(&stream, array->arrayzero); + serialise_int4(&stream, array->arraymax); + serialise_stream(&stream, elems * sizeof(int32), + (char *) &(array->array[0])); + + return streamstart; +} + +/** + * De-serialise a veil integer array variable. + * + * @param **p_stream Pointer into the stream currently being read. + * pointer is updated to point to the next free slot in the stream after + * reading the stream + * @return Pointer to the variable created or updated from the stream. + */ +static VarEntry * +deserialise_int4array(char **p_stream) +{ + char *name = deserialise_name(p_stream); + int4 arrayzero; + int4 arraymax; + int4 elems; + VarEntry *var = vl_lookup_variable(name); + Int4Array *array = (Int4Array *) var->obj; + + arrayzero = deserialise_int4(p_stream); + arraymax = deserialise_int4(p_stream); + elems = 1 + arraymax - arrayzero; + + if (array) { + if (array->type != OBJ_INT4_ARRAY) { + vl_type_mismatch(name, OBJ_INT4_ARRAY, array->type); + } + } + array = vl_NewInt4Array(array, var->shared, arrayzero, arraymax); + var->obj = (Object *) array; + + deserialise_stream(p_stream, elems * sizeof(int4), + (char *) &(array->array[0])); + return var; +} + +/** + * Serialise a veil range variable into a dynamically allocated + * string. + * + * @param range Pointer to the variable to be serialised + * @param name The name of the variable + * @return Dynamically allocated string containing the serialised + * variable + */ +static char * +serialise_range(Range *range, char *name) +{ + int stream_len = hdrlen(name) + (INT32SIZE_B64 * 2) + 1; + char *stream = palloc(stream_len * sizeof(char)); + char *streamstart = stream; + + serialise_char(&stream, RANGE_HDR); + serialise_name(&stream, name); + serialise_int4(&stream, range->min); + serialise_int4(&stream, range->max); + return streamstart; +} + +/** + * De-serialise a veil range variable. + * + * @param **p_stream Pointer into the stream currently being read. + * pointer is updated to point to the next free slot in the stream after + * reading the stream + * @return Pointer to the variable created or updated from the stream. + */ +static VarEntry * +deserialise_range(char **p_stream) +{ + char *name = deserialise_name(p_stream); + VarEntry *var = vl_lookup_variable(name); + Range *range = (Range *) var->obj; + + if (range) { + if (range->type != OBJ_RANGE) { + vl_type_mismatch(name, OBJ_RANGE, range->type); + } + } + else { + var->obj = (Object *) vl_NewRange(var->shared); + range = (Range *) var->obj; + } + + range->min = deserialise_int4(p_stream); + range->max = deserialise_int4(p_stream); + return var; +} + +/** + * Serialise a single bitmap from a veil bitmap array or bitmap hash. + * + * @param p_stream Pointer into the stream currently being read. + * pointer is updated to point to the next free slot in the stream after + * writing the stream. + * @param bitmap The bitmap to be serialised. + */ +static void +serialise_one_bitmap(char **p_stream, Bitmap *bitmap) +{ + int elems = ARRAYELEMS(bitmap->bitzero, bitmap->bitmax); + serialise_int4(p_stream, bitmap->bitzero); + serialise_int4(p_stream, bitmap->bitmax); + serialise_stream(p_stream, elems * sizeof(int32), + (char *) &(bitmap->bitset)); +} + +/** + * Serialise a veil bitmap variable into a dynamically allocated + * string. + * + * @param bitmap Pointer to the variable to be serialised + * @param name The name of the variable + * @return Dynamically allocated string containing the serialised + * variable + */ +static char * +serialise_bitmap(Bitmap *bitmap, char *name) +{ + int elems = ARRAYELEMS(bitmap->bitzero, bitmap->bitmax); + int stream_len = hdrlen(name) + (INT32SIZE_B64 * 2) + + streamlen(sizeof(int32) * elems) + 1; + char *stream = palloc(stream_len * sizeof(char)); + char *streamstart = stream; + + serialise_char(&stream, BITMAP_HDR); + serialise_name(&stream, name); + serialise_one_bitmap(&stream, bitmap); + return streamstart; +} + +/** + * De-serialise a single bitmap into a veil bitmap array or bitmap hash. + * + * @param p_bitmap Pointer to bitmap pointer. This may be updated to + * contain a dynamically allocated bitmap if none is already present. + * @param name The name of the variable, for error reporting purposes. + * @param shared Whether the bitmap is part of a shared rather than + * session variable. + * @param p_stream Pointer into the stream currently being read. + * pointer is updated to point to the next free slot in the stream after + * reading the stream. + */ +static void +deserialise_one_bitmap(Bitmap **p_bitmap, char *name, + bool shared, char **p_stream) +{ + Bitmap *bitmap = *p_bitmap; + int4 bitzero; + int4 bitmax; + int4 elems; + + bitzero = deserialise_int4(p_stream); + bitmax = deserialise_int4(p_stream); + elems = ARRAYELEMS(bitzero, bitmax); + + if (bitmap) { + if (bitmap->type != OBJ_BITMAP) { + vl_type_mismatch(name, OBJ_BITMAP, bitmap->type); + } + } + /* Check size and re-allocate memory if needed */ + vl_NewBitmap(p_bitmap, shared, bitzero, bitmax); + bitmap = *p_bitmap; + + deserialise_stream(p_stream, elems * sizeof(bitmap->bitset[0]), + (char *) &(bitmap->bitset[0])); + +} + +/** + * De-serialise a veil bitmap variable. + * + * @param **p_stream Pointer into the stream currently being read. + * pointer is updated to point to the next free slot in the stream after + * reading the stream + * @return Pointer to the variable created or updated from the stream. + */ +static VarEntry * +deserialise_bitmap(char **p_stream) +{ + char *name = deserialise_name(p_stream); + VarEntry *var = vl_lookup_variable(name); + Bitmap *bitmap = (Bitmap *) var->obj; + + deserialise_one_bitmap(&bitmap, name, var->shared, p_stream); + var->obj = (Object *) bitmap; + return var; +} + +/** + * Serialise a veil bitmap array variable into a dynamically allocated + * string. + * + * @param bmarray Pointer to the variable to be serialised + * @param name The name of the variable + * @return Dynamically allocated string containing the serialised + * variable + */ +static char * +serialise_bitmap_array(BitmapArray *bmarray, char *name) +{ + int bitset_elems = ARRAYELEMS(bmarray->bitzero, bmarray->bitmax); + int array_elems = 1 + bmarray->arraymax - bmarray->arrayzero; + int bitmap_len = (INT32SIZE_B64 * 2) + + streamlen(sizeof(int32) * bitset_elems); + int stream_len = hdrlen(name) + (INT32SIZE_B64 * 4) + + (bitmap_len * array_elems) + 1; + int idx; + char *stream = palloc(stream_len * sizeof(char)); + char *streamstart = stream; + + serialise_char(&stream, BITMAP_ARRAY_HDR); + serialise_name(&stream, name); + serialise_int4(&stream, bmarray->bitzero); + serialise_int4(&stream, bmarray->bitmax); + serialise_int4(&stream, bmarray->arrayzero); + serialise_int4(&stream, bmarray->arraymax); + for (idx = 0; idx < array_elems; idx++) { + serialise_one_bitmap(&stream, bmarray->bitmap[idx]); + } + return streamstart; +} + +/** + * De-serialise a veil bitmap array variable. + * + * @param **p_stream Pointer into the stream currently being read. + * pointer is updated to point to the next free slot in the stream after + * reading the stream + * @return Pointer to the variable created or updated from the stream. + */ +static VarEntry * +deserialise_bitmap_array(char **p_stream) +{ + char *name = deserialise_name(p_stream); + int4 bitzero; + int4 bitmax; + int4 arrayzero; + int4 arraymax; + int4 array_elems; + int4 idx; + VarEntry *var = vl_lookup_variable(name); + BitmapArray *bmarray = (BitmapArray *) var->obj; + + bitzero = deserialise_int4(p_stream); + bitmax = deserialise_int4(p_stream); + arrayzero = deserialise_int4(p_stream); + arraymax = deserialise_int4(p_stream); + + if (bmarray) { + if (bmarray->type != OBJ_BITMAP_ARRAY) { + vl_type_mismatch(name, OBJ_BITMAP_ARRAY, bmarray->type); + } + } + /* Check size and re-allocate memory if needed */ + vl_NewBitmapArray(&bmarray, var->shared, arrayzero, + arraymax, bitzero, bitmax); + var->obj = (Object *) bmarray; + + array_elems = 1 + arraymax - arrayzero; + for (idx = 0; idx < array_elems; idx++) { + deserialise_one_bitmap(&(bmarray->bitmap[idx]), "", + var->shared, p_stream); + + } + return var; +} + + +/** + * Calculate the size needed for a base64 stream to contain all of the + * bitmaps in a bitmap hash including their keys. + * + * @param bmhash Pointer to the variable to be serialised + * @param bitset_size The size, in bytes, of each bitset in the bitmap + * hash. + * @return Number of bytes required to contain all of the bitmaps and + * keys in the bitmap_hash + */ +static int +sizeof_bitmaps_in_hash(BitmapHash *bmhash, int bitset_size) +{ + VarEntry *var = NULL; + int size = 1; /* Allow for final end of stream indicator */ + while ((var = vl_NextHashEntry(bmhash->hash, var))) { + /* 1 byte below for record/end flag to precede each bitmap in + * the hash */ + size += 1 + bitset_size + hdrlen(var->key); + } + return size; +} + + +/** + * Serialise a veil bitmap hash variable into a dynamically allocated + * string. + * + * @param bmhash Pointer to the variable to be serialised + * @param name The name of the variable + * @return Dynamically allocated string containing the serialised + * variable + */ +static char * +serialise_bitmap_hash(BitmapHash *bmhash, char *name) +{ + int bitset_elems = ARRAYELEMS(bmhash->bitzero, bmhash->bitmax); + int bitset_size = (INT32SIZE_B64 * 2) + + streamlen(sizeof(int32) * bitset_elems); + int all_bitmaps_size = sizeof_bitmaps_in_hash(bmhash, bitset_size); + int stream_len = hdrlen(name) + (INT32SIZE_B64 * 2) + + all_bitmaps_size + 1; + char *stream = palloc(stream_len * sizeof(char)); + char *streamstart = stream; + VarEntry *var = NULL; + + serialise_char(&stream, BITMAP_HASH_HDR); + serialise_name(&stream, name); + serialise_int4(&stream, bmhash->bitzero); + serialise_int4(&stream, bmhash->bitmax); + while ((var = vl_NextHashEntry(bmhash->hash, var))) { + serialise_char(&stream, BITMAP_HASH_MORE); + serialise_name(&stream, var->key); + serialise_one_bitmap(&stream, (Bitmap *) var->obj); + } + serialise_char(&stream, BITMAP_HASH_DONE); + + return streamstart; +} + +/** + * De-serialise a veil bitmap hash variable. + * + * @param **p_stream Pointer into the stream currently being read. + * pointer is updated to point to the next free slot in the stream after + * reading the stream + * @return Pointer to the variable created or updated from the stream. + */ +static VarEntry * +deserialise_bitmap_hash(char **p_stream) +{ + char *name = deserialise_name(p_stream); + char *hashkey; + int4 bitzero; + int4 bitmax; + VarEntry *var = vl_lookup_variable(name); + BitmapHash *bmhash = (BitmapHash *) var->obj; + Bitmap *tmp_bitmap = NULL; + Bitmap *bitmap; + + bitzero = deserialise_int4(p_stream); + bitmax = deserialise_int4(p_stream); + + if (bmhash) { + if (bmhash->type != OBJ_BITMAP_HASH) { + vl_type_mismatch(name, OBJ_BITMAP_HASH, bmhash->type); + } + } + /* Check size and re-allocate memory if needed */ + vl_NewBitmapHash(&bmhash, name, bitzero, bitmax); + var->obj = (Object *) bmhash; + + while (deserialise_char(p_stream) == BITMAP_HASH_MORE) { + hashkey = deserialise_name(p_stream); + deserialise_one_bitmap(&tmp_bitmap, "", var->shared, p_stream); + /* tmp_bitmap now contains a (dynamically allocated) bitmap + * Now we want to copy that into the bmhash. We don't worry + * about memory leaks here since this is allocated only once + * per call of this function, and the memory context will + * eventually be freed anyway. + */ + bitmap = vl_AddBitmapToHash(bmhash, hashkey); + vl_BitmapUnion(bitmap, tmp_bitmap); + } + return var; +} + +/** + * Serialise a veil variable + * + * @param name The name of the variable to be serialised. + * @return Dynamically allocated string containing the serialised value. + */ +extern char * +vl_serialise_var(char *name) +{ + VarEntry *var; + char *result = NULL; + + var = vl_lookup_variable(name); + if (var && var->obj) { + switch (var->obj->type) { + case OBJ_INT4: + result = serialise_int4var((Int4Var *)var->obj, name); + break; + case OBJ_INT4_ARRAY: + result = serialise_int4array((Int4Array *)var->obj, name); + break; + case OBJ_RANGE: + result = serialise_range((Range *)var->obj, name); + break; + case OBJ_BITMAP: + result = serialise_bitmap((Bitmap *)var->obj, name); + break; + case OBJ_BITMAP_ARRAY: + result = serialise_bitmap_array((BitmapArray *)var->obj, name); + break; + case OBJ_BITMAP_HASH: + result = serialise_bitmap_hash((BitmapHash *)var->obj, name); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Unsupported type for variable serialisation"), + errdetail("Cannot serialise objects of type %d.", + (int4) var->obj->type))); + + } + } + return result; +} + +/** + * De-serialise the next veil variable from *p_stream + * + * @param **p_stream Pointer into the stream currently being read. + * pointer is updated to point to the next free slot in the stream after + * reading the stream + * @return The deserialised variable. + */ +extern VarEntry * +vl_deserialise_next(char **p_stream) +{ + VarEntry *var = NULL; + if ((**p_stream) != '\0') { + char type = deserialise_char(p_stream); + switch (type){ + case INT4VAR_HDR: var = deserialise_int4var(p_stream); + break; + case INT4_ARRAY_HDR: var = deserialise_int4array(p_stream); + break; + case RANGE_HDR: var = deserialise_range(p_stream); + break; + case BITMAP_HDR: var = deserialise_bitmap(p_stream); + break; + case BITMAP_ARRAY_HDR: var = deserialise_bitmap_array(p_stream); + break; + case BITMAP_HASH_HDR: var = deserialise_bitmap_hash(p_stream); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Unsupported type for variable deserialisation"), + errdetail("Cannot deserialise objects of type %c.", + type))); + + } + } + return var; +} + +/** + * De-serialise a base64 string containing, possibly many, derialised + * veil variables. + * + * @param **p_stream Pointer into the stream currently being read. + * @return A count of the number of variables that have been de-serialised. + */ +extern int4 +vl_deserialise(char **p_stream) +{ + int count = 0; + while ((**p_stream) != '\0') { + (void) vl_deserialise_next(p_stream); + count++; + } + return count; +} diff --git a/src/veil_serialise.d b/src/veil_serialise.d new file mode 100644 index 0000000..232592e --- /dev/null +++ b/src/veil_serialise.d @@ -0,0 +1,5 @@ +src/veil_serialise.o src/veil_serialise.d: \ + src/veil_serialise.c \ + src/veil_version.h \ + src/veil_funcs.h \ + src/veil_datatypes.h diff --git a/src/veil_shmem.c b/src/veil_shmem.c new file mode 100644 index 0000000..89ed412 --- /dev/null +++ b/src/veil_shmem.c @@ -0,0 +1,710 @@ +/** + * @file veil_shmem.c + * \code + * Author: Marc Munro + * Copyright (c) 2005 - 2011 Marc Munro + * License: BSD + * + * \endcode + * @brief + * Functions for dealing with veil shared memory. + * + * This provides dynamic memory allocation, like malloc, from chunks of + * shared memory allocated from the Postgres shared memory pool. In + * order to be able to reset and reload shared memory structures while + * other backends continue to use the existing structures, a shared + * memory reset creates a new context, or switches to an existing one + * that is no longer in use. No more than two separate contexts will be + * created. + * + * Each context of veil shared memory is associated with a shared hash, + * which is used to store veil's shared variables. A specially named + * variable, VEIL_SHMEMCTL appears only in context0 and contains a + * reference to chunk0, and the ShmemCtl structure. From this structure + * we can identify the current context, the initial chunks for each + * active context, and whether a context switch is in progress. + * + * A context switch takes place in 3 steps: + * - preparation, in which we determine if a context switch is allowed, + * initialise the new context and record the fact that we are in the + * process of switching. All subsequent operations in the current + * backend will work in the new context, while other backends will + * continue to use the original context + * - initialisation of the new context, variables, etc. This is done + * by the user-space function veil_init(). + * - switchover, when all other processes gain access to the newly + * initialised context. They may continue to use the previous + * context for the duration of their current transactions. + * + * To access shared variable "x" in a new session, the following steps + * are taken: + * - We access the hash "VEIL_SHARED1_nnn" (where nnn is the oid of our + * database). This gives us a reference to the ShmemCtl structure. + * We record hash0 and shared_meminfo on the way. + * - We access ShemCtl to identify the current hash and current + * context. + * - We look up variable "x" in the current hash, and if we have to + * allocate space for it, allocate it from the current context. + * + * Note that We use a dynamicall allocated LWLock, VeilLWLock to protect + * our shared control structures. + * + */ + +#include "postgres.h" +#include "utils/hsearch.h" +#include "storage/pg_shmem.h" +#include "storage/shmem.h" +#include "storage/lwlock.h" +#include "storage/procarray.h" +#include "access/xact.h" +#include "access/transam.h" +#include "miscadmin.h" +#include "veil_version.h" +#include "veil_shmem.h" +#include "veil_funcs.h" + +/** + * shared_meminfo provides access to the ShmemCtl structure allocated in + * context 0. + */ +static ShmemCtl *shared_meminfo = NULL; + +/** + * Whether the current backend is in the process of switching contexts. + * If so, it will be setting up the non-current context in readiness for + * making it available to all other backends. + */ +static bool prepared_for_switch = false; + +/** + * The LWLock that Veil will use for managing concurrent access to + * shared memory. It is initialised to a lock id that is distinct + * from any tha twill be dynamically allocated. + */ +static LWLockId VeilLWLock = AddinShmemInitLock; + +/** + * The LWLock to be used while initially setting up shared memory and + * allocating a veil database-specific LWLock. + */ +static LWLockId InitialLWLock = AddinShmemInitLock; + +/** + * Return the index of the other context from the one supplied. + * + * @param x the context for which we want the other one. + * + * @return the opposite context to that of x. + */ +#define OTHER_CONTEXT(x) (x ? 0: 1) + +/** + * Veil's startup function. This should be run when the Veil shared + * library is loaded by postgres. + * + * If shared_preload_libraries is not defined, Veil may still be run but + * it will steal shared memory from postgres, potentially exhausting it. + * + */ +void +_PG_init() +{ + int veil_dbs; + + /* Define GUCs for veil */ + veil_config_init(); + veil_dbs = veil_dbs_in_cluster(); + + /* Request a Veil-specific shared memory context */ + RequestAddinShmemSpace(2 * veil_shmem_context_size() * veil_dbs); + + /* Request a LWLock for later use by all backends */ + RequestAddinLWLocks(veil_dbs); +} + +/** + * Create/attach to the shared hash identified by hashname. Return a + * pointer to an HTAB that references the shared hash. All locking is + * handled by the caller. + * + * @param hashname + * + * @return Pointer to HTAB referencing the shared hash. + */ +static HTAB * +create_shared_hash(const char *hashname) +{ + HASHCTL hashctl; + HTAB *result; + char *db_hashname; + int hash_elems = veil_shared_hash_elems(); + + /* Add the current database oid into the hashname so that it is + * distinct from the shared hash for other databases in the + * cluster. */ + db_hashname = (char *) vl_malloc(HASH_KEYLEN); + (void) snprintf(db_hashname, HASH_KEYLEN - 1, "%s_%u", + hashname, MyDatabaseId); + hashctl.keysize = HASH_KEYLEN; + hashctl.entrysize = sizeof(VarEntry); + + result = ShmemInitHash(db_hashname, hash_elems, + hash_elems, &hashctl, HASH_ELEM); + pfree(db_hashname); + return result; +} + +/** + * Return reference to the HTAB for the shared hash associated with + * context 0. + * + * @return Pointer to HTAB referencing shared hash for context 0. + */ +static HTAB * +get_hash0() +{ + static HTAB *hash0 = NULL; + + if (!hash0) { + hash0 = create_shared_hash("VEIL_SHARED1"); + } + return hash0; +} + +/** + * Return reference to the HTAB for the shared hash associated with + * context 1. + * + * @return Pointer to HTAB referencing shared hash for context 1. + */ +static HTAB * +get_hash1() +{ + static HTAB *hash1 = NULL; + + if (!hash1) { + hash1 = create_shared_hash("VEIL_SHARED2"); + } + + return hash1; +} + + +/** + * Allocate or attach to, a new chunk of shared memory for a named + * memory context. + * + * @param name The name + * @param size The size of the shared memory chunk to be allocated. + * @param p_found Pointer to boolean that will identify whether this + * chunk has already been initialised. + * + * @return Pointer to chunk of shared memory. + */ +static MemContext * +get_shmem_context(char *name, + size_t size, + bool *p_found) +{ + int i; + MemContext *context; + char *uniqname = (char *) vl_malloc(strlen(name) + 16); + int max_dbs = veil_dbs_in_cluster(); + + for (i = 0; i < max_dbs; i++) { + (void) sprintf(uniqname, "%s_%d", name, i); + context = ShmemInitStruct(uniqname, size, p_found);; + if (!context) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("veil: cannot allocate shared memory(1)"))); + } + + if (*p_found) { + /* Already exists. Check database id. */ + if (context->db_id == MyDatabaseId) { + /* This context is the one for the current database, + * nothing else to do. */ + return context; + } + } + else { + /* We Just allocated our first context */ + context->db_id = MyDatabaseId; + context->next = sizeof(MemContext); + context->limit = size; + context->lwlock = VeilLWLock; + return context; + } + } + + /* We reach this point if no existing contexts are allocated to our + * database. Now we check those existing contexts to see whether + * they are still in use. If not, we will redeploy them. */ + + for (i = 0; i < max_dbs; i++) { + (void) sprintf(uniqname, "%s_%d", name, i); + context = ShmemInitStruct(uniqname, size, p_found);; + + if (!context) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("veil: cannot allocate shared memory(2)"))); + } + + if (*p_found) { + /* Is this context for a still existant database? */ + if (!vl_db_exists(context->db_id)) { + /* We can re-use this context. */ + context->db_id = MyDatabaseId; + context->next = sizeof(MemContext); + context->limit = size; + + *p_found = false; /* Tell the caller that init is + * required */ + return context; + } + } + else { + /* We didn't find an unused context, so now we have created + * a new one. */ + + context->db_id = MyDatabaseId; + context->next = sizeof(MemContext); + context->limit = size; + return context; + } + } + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("veil: no more shared memory contexts allowed"))); + return NULL; +} + +/* Forward ref, required by next function. */ +static void shmalloc_init(void); + +/** + * Return the id (index) of the current context for this session + * + * @return The current context id + */ +static int +get_cur_context_id() +{ + static bool initialised = false; + int context; + + if (!initialised) { + shmalloc_init(); + initialised = true; + } + + context = shared_meminfo->current_context; + if (prepared_for_switch) { + context = OTHER_CONTEXT(context); + } + else { + /* Check for the default context being for a later transaction + * than current and, if so, use the other one. */ + if (TransactionIdPrecedes(GetCurrentTransactionId(), + shared_meminfo->xid[context])) + { + context = OTHER_CONTEXT(context); + } + } + + return context; +} + +/** + * Return pointer to shared memory allocated for the current context. + * + * @return The current context. + */ +static MemContext * +get_cur_context() +{ + int context; + context = get_cur_context_id(); + return shared_meminfo->context[context]; +} + +/** + * Dynamically allocate a piece of shared memory from the current + * context, doing no locking. + * + * @param context The context in which we are operating + * @param size The size of the requested piece of memory. + * + * @return Pointer to dynamically allocated memory. + */ +static void * +do_vl_shmalloc(MemContext *context, + size_t size) +{ + void *result = NULL; + size_t amount = (size_t) MAXALIGN(size); + + if ((amount + context->next) <= context->limit) { + result = (void *) ((char *) context + context->next); + context->next += amount; + } + else { + ereport(ERROR, + (ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("veil: out of shared memory")))); + } + return result; +} + +/** + * Dynamically allocate a piece of shared memory from the current context. + * + * @param size The size of the requested piece of memory. + * + * @return Pointer to dynamically allocated memory. + */ +void * +vl_shmalloc(size_t size) +{ + MemContext *context; + void *result; + + context = get_cur_context(); + + LWLockAcquire(VeilLWLock, LW_EXCLUSIVE); + result = do_vl_shmalloc(context, size); + LWLockRelease(VeilLWLock); + + return result; +} + +/** + * Free a piece of shared memory within the current context. Currently + * this does nothing as implementation of freeing of shared memory has + * been deferred. + * + * @param mem Pointer to the memory to be freed. + * + */ +void +vl_free(void *mem) +{ + return; +} + + +/** + * Attach to, creating and initialising as necessary, the shared memory + * control structure. Record this for the session in shared_meminfo. + */ +static void +shmalloc_init(void) +{ + if (!shared_meminfo) { + VarEntry *var; + MemContext *context0; + MemContext *context1; + bool found = false; + HTAB *hash0; + HTAB *hash1; + size_t size; + + size = veil_shmem_context_size(); + + LWLockAcquire(InitialLWLock, LW_EXCLUSIVE); + context0 = get_shmem_context("VEIL_SHMEM0", size, &found); + + if (found && context0->memctl) { + shared_meminfo = context0->memctl; + VeilLWLock = shared_meminfo->veil_lwlock; + /* By aquiring and releasing this lock, we ensure that Veil + * shared memory has been fully initialised, by a process + * following the else clause of this code path. */ + LWLockAcquire(VeilLWLock, LW_EXCLUSIVE); + LWLockRelease(InitialLWLock); + LWLockRelease(VeilLWLock); + } + else { + /* Do minimum amount of initialisation while holding + * the initial lock. We don't want to do anything that + * may cause other locks to be aquired as this could lead + * to deadlock with other add-ins. Instead, we aquire the + * Veil-specific lock before finishing the initialisation. */ + + shared_meminfo = do_vl_shmalloc(context0, sizeof(ShmemCtl)); + + if (context0->lwlock != InitialLWLock) { + /* Re-use the LWLock previously allocated to this memory + * context */ + VeilLWLock = context0->lwlock; + } + else { + /* Allocate new LWLock for this new shared memory + * context */ + VeilLWLock = LWLockAssign(); + } + /* Record the lock id in context0 (for possible re-use if + * the current database is dropped and a new veil-using + * database created), and in the shared_meminfo struct */ + context0->lwlock = VeilLWLock; + shared_meminfo->veil_lwlock = VeilLWLock; + + /* Exchange the initial lock for our Veil-specific one. */ + LWLockAcquire(VeilLWLock, LW_EXCLUSIVE); + LWLockRelease(InitialLWLock); + + /* Now do the rest of the Veil shared memory initialisation */ + + /* Set up the other memory context */ + context1 = get_shmem_context("VEIL_SHMEM1", size, &found); + + /* Record location of shmemctl structure in each context */ + context0->memctl = shared_meminfo; + context1->memctl = shared_meminfo; + + /* Finish initialising the shmemctl structure */ + shared_meminfo->type = OBJ_SHMEMCTL; + shared_meminfo->current_context = 0; + shared_meminfo->total_allocated[0] = size; + shared_meminfo->total_allocated[1] = size; + shared_meminfo->switching = false; + shared_meminfo->context[0] = context0; + shared_meminfo->context[1] = context1; + shared_meminfo->xid[0] = GetCurrentTransactionId(); + shared_meminfo->xid[1] = shared_meminfo->xid[0]; + shared_meminfo->initialised = true; + + /* Set up both shared hashes */ + hash0 = get_hash0(); + hash1 = get_hash1(); + + /* Record the shmemctl structure in hash0 */ + var = (VarEntry *) hash_search(hash0, (void *) "VEIL_SHMEMCTL", + HASH_ENTER, &found); + + var->obj = (Object *) shared_meminfo; + var->shared = true; + + var = (VarEntry *) hash_search(hash0, (void *) "VEIL_SHMEMCTL", + HASH_ENTER, &found); + + LWLockRelease(VeilLWLock); + } + } +} + +/** + * Return the shared hash for the current context. + * + * @return Pointer to the HTAB for the current context's shared hash. + */ +HTAB * +vl_get_shared_hash() +{ + int context; + HTAB *hash; + static bool initialised = false; + + if (!initialised) { + (void) get_cur_context(); /* Ensure shared memory is set up. */ + initialised = true; + } + + context = get_cur_context_id(); + + if (context == 0) { + hash = get_hash0(); + } + else { + hash = get_hash1(); + } + + return hash; +} + +/** + * Reset one of the shared hashes. This is one of the final steps in a + * context switch. + * + * @return hash The shared hash that is to be reset. + */ +static void +clear_hash(HTAB *hash) +{ + static HASH_SEQ_STATUS status; + VarEntry *var; + + hash_seq_init(&status, hash); + while ((var = hash_seq_search(&status))) { + if (strncmp("VEIL_SHMEMCTL", var->key, strlen("VEIL_SHMEMCTL")) != 0) { + (void) hash_search(hash, var->key, HASH_REMOVE, NULL); + } + } +} + +/** + * Prepare for a switch to the alternate context. Switching will + * only be allowed if there are no transactions that may still be using + * the context to which we are switching, and there is no other + * process attempting the switch. + * + * @return true if the switch preparation was successful. + */ +bool +vl_prepare_context_switch() +{ + int context_curidx; + int context_newidx; + HTAB *hash0 = get_hash0(); /* We must not attempt to create hashes + * on the fly below as they also acquire + * the lock */ + HTAB *hash1 = get_hash1(); + TransactionId oldest_xid; + MemContext *context; + + (void) get_cur_context(); /* Ensure shared memory is set up */ + + LWLockAcquire(VeilLWLock, LW_EXCLUSIVE); + + if (shared_meminfo->switching) { + /* Another process is performing the switch */ + LWLockRelease(VeilLWLock); + return false; + } + + shared_meminfo->switching = true; + + /* We have claimed the switch. If we decide that we cannot proceed, + * we will return it to its previous state. */ + + context_curidx = shared_meminfo->current_context; + context_newidx = OTHER_CONTEXT(context_curidx); + + /* In case the alternate context has been used before, we must + * clear it. */ + + oldest_xid = GetOldestXmin(false, true); + if (TransactionIdPrecedes(oldest_xid, + shared_meminfo->xid[context_curidx])) + { + /* There is a transaction running that precedes the time of + * the last context switch. That transaction may still be + * using the chunk to which we wish to switch. We cannot + * allow the switch. */ + shared_meminfo->switching = false; + LWLockRelease(VeilLWLock); + return false; + } + else { + /* It looks like we can safely make the switch. Reset the + * new context, and make it the current context for this + * session only. */ + context = shared_meminfo->context[context_newidx]; + context->next = sizeof(MemContext); + + /* If we are switching to context 0, reset the next field of + * the first chunk to leave space for the ShmemCtl struct. */ + if (context_newidx == 0) { + context->next += sizeof(ShmemCtl); + clear_hash(hash0); + } + else { + clear_hash(hash1); + } + } + + LWLockRelease(VeilLWLock); + prepared_for_switch = true; + return true; +} + +/** + * Complete the context switch started by vl_prepare_context_switch(). + * Raise an ERROR if the context switch cannot be completed. + * + * @return true if the context switch is successfully completed. + */ +bool +vl_complete_context_switch() +{ + int context_curidx; + int context_newidx; + + if (!prepared_for_switch) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to complete context switch"), + errdetail("Not prepared for switch - " + "invalid state for operation"))); + } + + LWLockAcquire(VeilLWLock, LW_EXCLUSIVE); + context_curidx = shared_meminfo->current_context; + context_newidx = OTHER_CONTEXT(context_curidx); + + if (!shared_meminfo->switching) { + /* We do not claim to be switching. We should. */ + LWLockRelease(VeilLWLock); + + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to complete context switch"), + errdetail("Session does not have switching set to true- " + "invalid state for operation"))); + } + + shared_meminfo->switching = false; + shared_meminfo->current_context = context_newidx; + shared_meminfo->xid[context_newidx] = GetCurrentTransactionId(); + LWLockRelease(VeilLWLock); + prepared_for_switch = false; + return true; +} + +/** + * In desparation, if we are unable to complete a context switch, we + * should use this function. + */ +void +vl_force_context_switch() +{ + int context_curidx; + int context_newidx; + MemContext *context; + HTAB *hash0 = get_hash0(); + HTAB *hash1 = get_hash1(); + + (void) get_cur_context(); + + LWLockAcquire(VeilLWLock, LW_EXCLUSIVE); + + context_curidx = shared_meminfo->current_context; + context_newidx = OTHER_CONTEXT(context_curidx); + + /* Clear the alternate context. */ + + context = shared_meminfo->context[context_newidx]; + context->next = sizeof(MemContext); + + /* If we are switching to context 0, reset the next field of + * the first chunk to leave space for the ShmemCtl struct. */ + if (context_newidx == 0) { + context->next += sizeof(ShmemCtl); + clear_hash(hash0); + } + else { + clear_hash(hash1); + } + + shared_meminfo->switching = false; + shared_meminfo->current_context = context_newidx; + shared_meminfo->xid[context_newidx] = GetCurrentTransactionId(); + shared_meminfo->xid[0] = GetCurrentTransactionId(); + LWLockRelease(VeilLWLock); + prepared_for_switch = false; +} + diff --git a/src/veil_shmem.d b/src/veil_shmem.d new file mode 100644 index 0000000..5d3ab9f --- /dev/null +++ b/src/veil_shmem.d @@ -0,0 +1,6 @@ +src/veil_shmem.o src/veil_shmem.d: \ + src/veil_shmem.c \ + src/veil_version.h \ + src/veil_shmem.h \ + src/veil_funcs.h \ + src/veil_datatypes.h diff --git a/src/veil_shmem.h b/src/veil_shmem.h new file mode 100644 index 0000000..5ccd2df --- /dev/null +++ b/src/veil_shmem.h @@ -0,0 +1,260 @@ +/** + * @file veil_shmem.h + * \code + * Author: Marc Munro + * Copyright (c) 2005 - 2011 Marc Munro + * License: BSD + * + * \endcode + * @brief + * Define the basic veil shared memory structures + * + */ + +#ifndef VEIL_DATATYPES +/** Prevent multiple definitions of the contents of this file. + */ +#define VEIL_DATATYPES 1 + +#include "utils/hsearch.h" +#include "storage/lwlock.h" + +/** + * Chunks od shared memory are allocated in multiples of this size. + */ +#define CHUNK_SIZE 8192 + +/** + * Limits the total amount of memory available for veil shared + * variables. + */ +#define MAX_ALLOWED_SHMEM CHUNK_SIZE * 100 + + +/** + * Chunks provide a linked list of dynamically allocated shared memory + * segments, with the most recently allocated chunk at the tail. + * Shmalloc allocates space from this list of chunks, creating new + * chunks as needed up to MAX_ALLOWED_SHMEM. + */ +typedef struct MemChunk { + struct MemChunk *next_chunk; /**< Pointer to next allocated chunk */ + size_t next; /**< Offset, within this chunk, of 1st + * free byte */ + size_t limit; /**< Offset, of 1st byte beyond chunk */ + void *memory[0]; /**< The rest of the chunk, from which + * memory is allocated */ +} MemChunk; + + +/** + * MemContexts are large single chunks of shared memory from which + * smaller allocations may be made + */ +typedef struct MemContext { + Oid db_id; /**< Identifier for the database for + * which this context was created, + * or by which it has been taken + * over. */ + LWLockId lwlock; /**< The LWLock associated with this + * memory context */ + size_t next; /**< Offset of 1st free byte */ + size_t limit; /**< Offset, of 1st byte beyond this + * struct */ + + struct ShmemCtl *memctl; /**< Pointer to shared memory control + * structure. */ + void *memory[0]; /**< The rest of the chunk, from which + * memory is allocated */ +} MemContext; + + + +/** + * The key length for veil hash types. + */ +#define HASH_KEYLEN 60 + + +/** + * Describes the type of an Object record or one of its subtypes. + */ +typedef enum { + OBJ_UNDEFINED = 0, + OBJ_SHMEMCTL, + OBJ_INT4, + OBJ_RANGE, + OBJ_BITMAP, + OBJ_BITMAP_ARRAY, + OBJ_BITMAP_HASH, + OBJ_BITMAP_REF, + OBJ_INT4_ARRAY +} ObjType; + +/** + * General purpose object-type. All veil variables are effectively + * sub-types of this. + */ +typedef struct Object { + ObjType type; +} Object; + +/** + * The ShmemCtl structure is the first object allocated from the first + * chunk of shared memory in context 0. This object describes and + * manages shared memory allocated by shmalloc() + */ +typedef struct ShmemCtl { + ObjType type; /**< This must have the value OBJ_SHMEMCTL */ + bool initialised; /**< Set to true once struct is setup */ + LWLockId veil_lwlock; /** dynamically allocated LWLock */ + int current_context; /**< Index of the current context (0 + * or 1) */ + size_t total_allocated[2]; /**< Total shared memory allocated in + * chunks in each context */ + bool switching; /**< Whether a context-switch is in + * progress */ + MemContext *context[2]; /**< Array (pair) of contexts */ + TransactionId xid[2]; /**< The transaction id of the + * transaction that initialised each + * context: this is used to determine + * whether there are transactions + * still runnning that may be using an + * earlier context. */ +} ShmemCtl; + +/** + * Subtype of Object for storing simple int4 values. These values are + * allowed to be null. + */ +typedef struct Int4Var { + ObjType type; /**< This must have the value OBJ_INT4 */ + bool isnull; /**< if true, the value is null */ + int32 value; /**< the integer value of the variable */ +} Int4Var; + +/** + * Subtype of Object for storing range values. A range has an upper and + * lower bound, both stored as int4s. Nulls are not allowed. + */ +typedef struct Range { + ObjType type; /**< This must have the value OBJ_RANGE */ + int32 min; + int32 max; +} Range; + +/** + * Subtype of Object for storing bitmaps. A bitmap is stored as an + * array of int4 values. See veil_bitmap.c for more information. Note + * that the size of a Bitmap structure is determined dynamically at run + * time as the size of the array is only known then. + */ +typedef struct Bitmap { + ObjType type; /**< This must have the value OBJ_BITMAP */ + int32 bitzero; /**< The index of the lowest bit the bitmap can + * store */ + int32 bitmax; /**< The index of the highest bit the bitmap can + * store */ + uint32 bitset[0]; /**< Element zero of the array of int4 values + * comprising the bitmap. */ +} Bitmap; + +/** + * Subtype of Object for storing bitmap refs. A bitmapref is like a + * bitmap but instead of containing a bitmap it contains a reference to + * one. This reference may be set during a transaction and then + * referenced only from within the setting transaction. + */ +typedef struct BitmapRef { + ObjType type; /**< This must have the value OBJ_BITMAP_REF */ + TransactionId xid; /**< The xid for which this variable is + * valid */ + Bitmap *bitmap; +} BitmapRef; + +/** + * Subtype of Object for storing bitmap arrays. A bitmap array is + * simply an array of pointers to dynamically allocated Bitmaps. Note + * that the size of a Bitmap structure is determined dynamically at run + * time as the size of the array is only known then. + */ +typedef struct BitmapArray { // subtype of Object + ObjType type; /**< This must have the value OBJ_BITMAP_ARRAY */ + int32 bitzero; /**< The index of the lowest bit each bitmap can + * store */ + int32 bitmax; /**< The index of the highest bit each bitmap can + * store */ + int32 arrayzero; /**< The index of array element zero: the + * index of the lowest numbered bitmap in the + * array */ + int32 arraymax; /**< The index of the lowest numbered bitmap in + * the array */ + Bitmap *bitmap[0]; /** Element zero of the array of Bitmap pointers + * comprising the array. */ +} BitmapArray; + +/** + * Subtype of Object for storing bitmap hashes. A bitmap hash is a hash + * of dynamically allocated bitmaps, keyed by strings. Note that these + * cannot be created as shared variables. + */ +typedef struct BitmapHash { + ObjType type; /**< This must have the value OBJ_BITMAP_HASH */ + int32 bitzero; /**< The index of the lowest bit each bitmap can + * store */ + int32 bitmax; /**< The index of the highest bit each bitmap can + * store */ + HTAB *hash; /**< Pointer to the (Postgresql dynahash) hash + * table */ +} BitmapHash; + + +/** + * Subtype of Object for storing arrays of integers. + */ +typedef struct Int4Array { + ObjType type; /**< This must have the value OBJ_INT4_ARRAY */ + int32 arrayzero; /**< The index of array element zero: the + * index of the lowest numbered bitmap in the + * array */ + int32 arraymax; /**< The index of the lowest numbered bitmap in + * the array */ + int32 array[0]; /** Element zero of the array of integers */ +} Int4Array; + + +/** + * A Veil variable. These may be session or shared variables, and may + * contain any Veil variable type. They are created and accessed by + * vl_lookup_shared_variable() and vl_lookup_variable(), and are stored + * in either the shared hash or one of the session hashes. See + * veil_shmem.c and veil_variables.c for more details. + */ +typedef struct VarEntry { + char key[HASH_KEYLEN]; /**< String containing variable name */ + bool shared; /**< Whether this is a shared variable */ + Object *obj; /**< Pointer to the contents of the variable */ +} VarEntry; + + +/** + * Describes a veil shared or session variable. This matches the SQL + * veil_variable_t which is defined as: +\verbatim +create type veil_variable_t as ( + name text, + type text, + shared bool, +); +\endverbatim + */ +typedef struct veil_variable_t { + char *name; /**< The name of the variable */ + char *type; /**< The type of the variable (eg "Bitmap") */ + bool shared; /**< Whether this is a shared variable (as + opposed to a session variable) */ +} veil_variable_t; + + +#endif + diff --git a/src/veil_utils.c b/src/veil_utils.c new file mode 100644 index 0000000..a1c9c07 --- /dev/null +++ b/src/veil_utils.c @@ -0,0 +1,62 @@ +/** + * @file veil_utils.c + * \code + * Author: Marc Munro + * Copyright (c) 2005 - 2011 Marc Munro + * License: BSD + * + * \endcode + * @brief + * Miscelaneous functions for veil + * + */ + + +#include "postgres.h" +#include "utils/memutils.h" +#include "veil_funcs.h" +#include "veil_datatypes.h" + +/** + * Dynamically allocate memory using palloc in TopMemoryContext. + * + * @param size The size of the chunk of memory being requested. + * + * @return Pointer to the newly allocated chunk of memory + */ +void * +vl_malloc(size_t size) +{ + void *result; + MemoryContext oldcontext = MemoryContextSwitchTo(TopMemoryContext); + result = palloc(size); + (void) MemoryContextSwitchTo(oldcontext); + return result; +} + +/** + * Return a static string describing an ObjType object. + * + * @param obj The ObjType for which we want a description. + * + * @return Pointer to a static string describing obj. + */ +char * +vl_ObjTypeName(ObjType obj) +{ + static char *names[] = { + "Undefined", "ShmemCtl", "Int4", + "Range", "Bitmap", "BitmapArray", + "BitmapHash", "BitmapRef", "Int4Array" + }; + + if ((obj < OBJ_UNDEFINED) || + (obj > OBJ_INT4_ARRAY)) + { + return "Unknown"; + } + else { + return names[obj]; + } +} + diff --git a/src/veil_utils.d b/src/veil_utils.d new file mode 100644 index 0000000..074fe42 --- /dev/null +++ b/src/veil_utils.d @@ -0,0 +1,4 @@ +src/veil_utils.o src/veil_utils.d: \ + src/veil_utils.c \ + src/veil_funcs.h \ + src/veil_datatypes.h diff --git a/src/veil_variables.c b/src/veil_variables.c new file mode 100644 index 0000000..a0dc4b1 --- /dev/null +++ b/src/veil_variables.c @@ -0,0 +1,327 @@ +/** + * @file veil_variables.c + * \code + * Author: Marc Munro + * Copyright (c) 2005 - 2011 Marc Munro + * License: BSD + * \endcode + * @brief + * Functions for dealing with Veil variables. + * + * Variables may be either session or shared, and are used to retain + * state between function calls. Shared variables are available to all + * suitably privileged sessions within a database. Session variables + * hold values that are private to a single session. + * + */ + +#include "postgres.h" +#include "veil_datatypes.h" +#include "utils/hsearch.h" +#include "storage/shmem.h" + +#include "veil_funcs.h" + +/** + * Baselines the number of session variables that can be created in each + * context. + */ +#define SESSION_HASH_ELEMS 32 + +/** + * This identifies the hash table for all session variables. The shared + * variable hash tables are managed in veil_shmem.c. + */ + +static HTAB *session_hash = NULL; + + +/** + * Create, or attach to, a hash for session variables. + * + */ +static HTAB * +create_session_hash() +{ + HASHCTL hashctl; + + /* TODO: Think about creating a specific memory context for this. */ + + hashctl.keysize = HASH_KEYLEN; + hashctl.entrysize = sizeof(VarEntry); + + return hash_create("VEIL_SESSION", + SESSION_HASH_ELEMS, &hashctl, HASH_ELEM); +} + +/** + * Define a new, or attach to an existing, shared variable. Raise an + * ERROR if the variable already exists as a session variable or if we + * cannot create the variable due to resource limitations (out of + * memory, or out of space in the shared hash). + * + * @param name The name of the variable. + * + * @return Pointer to the shared variable. If the variable is newly + * created by this call then it will be unitialised (ie it will have a + * NULL obj reference). + */ +VarEntry * +vl_lookup_shared_variable(char *name) +{ + VarEntry *var; + HTAB *shared_hash = vl_get_shared_hash(); + bool found; + + if (!session_hash) { + session_hash = create_session_hash(); + } + + var = (VarEntry *) hash_search(session_hash, (void *) name, + HASH_FIND, &found); + if (found) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("attempt to redefine session variable %s", name), + errdetail("You are trying to create shared variable %s " + "but it already exists as a session variable.", + name))); + } + + var = (VarEntry *) hash_search(shared_hash, (void *) name, + HASH_ENTER, &found); + + if (!var) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Out of memory for shared variables"))); + } + + if (!found) { + /* Shared variable did not already exist so we must initialise + * it. */ + + var->obj = NULL; + var->shared = true; + } + + return var; +} + +/** + * Lookup a variable by name, creating it as as a session variable if it + * does not already exist. + * + * @param name The name of the variable + * + * @return Pointer to the shared or session variable. If the variable + * is newly created by this call then it will be unitialised (ie it will + * have a NULL obj reference). + */ +VarEntry * +vl_lookup_variable(char *name) +{ + VarEntry *var; + HTAB *shared_hash = vl_get_shared_hash(); + bool found; + + if (!session_hash) { + session_hash = create_session_hash(); + } + + var = (VarEntry *)hash_search(session_hash, (void *) name, + HASH_FIND, &found); + if (!var) { + /* See whether this is a shared variable. */ + var = (VarEntry *)hash_search(shared_hash, (void *) name, + HASH_FIND, NULL); + } + + + if (!var) { + /* Create new session variable */ + var = (VarEntry *) hash_search(session_hash, (void *) name, + HASH_ENTER, &found); + if (!var) { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Out of memory for shared variables"))); + } + var->obj = NULL; + var->shared = false; + } + return var; +} + +/** + * Return the next variable from a scan of the hash of variables. Note + * that this function is not re-entrant. + * + * @param prev The last variable retrieved by a scan, or NULL if + * starting a new scan. + * + * @return The next variable encountered in the scan. NULL if we have + * finished. + */ +veil_variable_t * +vl_next_variable(veil_variable_t *prev) +{ + static bool doing_shared; + static HTAB *hash; + static HASH_SEQ_STATUS status; + static veil_variable_t result; + VarEntry *var; + + if (!session_hash) { + session_hash = create_session_hash(); + } + + if (!prev) { + doing_shared = true; + /* Initialise a scan of the shared hash. */ + hash = vl_get_shared_hash(); + + hash_seq_init(&status, hash); + } + + var = hash_seq_search(&status); + + if (!var) { + /* No more entries from that hash. */ + if (doing_shared) { + /* Switch to, and get var from, the session hash. */ + doing_shared = false; + hash = session_hash; + hash_seq_init(&status, hash); + var = hash_seq_search(&status); + } + } + + if (var) { + /* Yay, we have an entry. */ + result.name = var->key; + result.shared = var->shared; + if (var->obj) { + result.type = vl_ObjTypeName(var->obj->type); + } + else { + result.type = vl_ObjTypeName(OBJ_UNDEFINED);; + } + return &result; + } + else { + /* Thats all. There are no more entries */ + return NULL; + } +} + +/** + * Reset all Int4 entries in an ::Int4Array (to zero). + * + * @param array The array to be reset. + */ +void +vl_ClearInt4Array(Int4Array *array) +{ + int elems = 1 + array->arraymax - array->arrayzero; + int i; + for (i = 0; i < elems; i++) { + array->array[i] = 0; + } +} + +/** + * Return a newly initialised (zeroed) ::Int4Array. It may already + * exist in which case it will be re-used if possible. It may + * be created in either session or shared memory depending on the value + * of shared. + * + * @param current Pointer to an existing Int4Array if one exists. + * @param shared Whether to create the variable in shared or session + * memory. + * @param min Index of the first entry in the array. + * @param max Index of the last entry in the array. + */ +Int4Array * +vl_NewInt4Array(Int4Array *current, bool shared, + int32 min, int32 max) +{ + Int4Array *result = NULL; + int elems = 1 + max - min; + + if (current) { + int cur_elems = 1 + current->arraymax - current->arrayzero; + if (elems <= cur_elems) { + vl_ClearInt4Array(current); + result = current; + } + else { + if (!shared) { + /* Note that we can't free shared memory - no api to do + * so. */ + pfree(current); + } + } + } + if (!result) { + if (shared) { + result = vl_shmalloc(sizeof(Int4Array) + (sizeof(int32) * elems)); + } + else { + result = vl_malloc(sizeof(Int4Array) + (sizeof(int32) * elems)); + } + } + result->type = OBJ_INT4_ARRAY; + result->arrayzero = min; + result->arraymax = max; + + return result; +} + +/** + * Set an entry within an ::Int4Array. If idx is outside of the + * acceptable range, raise an error. + * + * @param array The ::Int4Array within which the entry is to be set. + * @param idx The index of the entry to be set. + * @param value The value to which the entry will be set. + */ +void +vl_Int4ArraySet(Int4Array *array, int32 idx, int32 value) +{ + if ((idx < array->arrayzero) || + (idx > array->arraymax)) + { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Int4ArraySet range error"), + errdetail("Index (%d) not in range %d..%d. ", idx, + array->arrayzero, array->arraymax))); + } + array->array[idx - array->arrayzero] = value; +} + +/** + * Get an entry from an ::Int4Array. If idx is outside of the + * acceptable range, raise an error. + * + * @param array The ::Int4Array within from the entry is to be read. + * @param idx The index of the entry to be retrieved. + + * @return The value of the entry + */ +int32 +vl_Int4ArrayGet(Int4Array *array, int32 idx) +{ + if ((idx < array->arrayzero) || + (idx > array->arraymax)) + { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Int4ArrayGet range error"), + errdetail("Index (%d) not in range %d..%d. ", idx, + array->arrayzero, array->arraymax))); + } + return array->array[idx - array->arrayzero]; +} + diff --git a/src/veil_variables.d b/src/veil_variables.d new file mode 100644 index 0000000..09fb2d8 --- /dev/null +++ b/src/veil_variables.d @@ -0,0 +1,4 @@ +src/veil_variables.o src/veil_variables.d: \ + src/veil_variables.c \ + src/veil_datatypes.h \ + src/veil_funcs.h diff --git a/src/veil_version.h b/src/veil_version.h new file mode 100644 index 0000000..8bad207 --- /dev/null +++ b/src/veil_version.h @@ -0,0 +1,20 @@ +/** + * @file veil_version.h + * \code + * Author: Marc Munro + * Copyright (c) 2005 - 2011 Marc Munro + * License: BSD + * + * \endcode + * @brief + * Provides version information for veil + * + */ + +/** The version number for this version of veil + */ +#define VEIL_VERSION "1.0.0" +/** The version number suffix, indicating stability. + */ +#define VEIL_VERSION_INFO "Stable" + diff --git a/tools/psql_funcs.sh b/tools/psql_funcs.sh new file mode 100644 index 0000000..3aca58d --- /dev/null +++ b/tools/psql_funcs.sh @@ -0,0 +1,216 @@ +#! /bin/sh +# psql_funcs.sh +# +# Functions for use by psql shell scripts. +# +# Copyright (c) 2005 - 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# + +# psql_collate +# Filter test output from psql, cleaning and collating as we go. Unfiltered +# output, and summary output goes to logfile $1. Result is non-zero if +# the tests fail +# +psql_collate() +{ + tee -a $1 | awk ' + function finish_test() + { + if (!testing) return + + if ((condition == "=") || + (condition == "~")) + { + ok = ok && found + } + else { + ok = ok && !found + } + + if (prepfail) { + failed++ + ok = 0 + res = "PREP FAIL\n [" preplines "\n ]" + } + else { + if (ok && lines != "") # If lines is empty, then no result + { # from test, so consider it failed + passed++ + } + else { + failed++ + } + res = ok ? "PASS": "FAIL\n [" lines "\n ]" + } + + printf(" %s\n", res) + print res >>LOGFILE + printf "END TEST %s\n\n", testno >>LOGFILE + + found = 0 + testing = 0 + prepfail = 0 + } + BEGIN { + prepping = 0 # boolean + prepfail = 0 # boolean + testing = 0 # boolean + complete = 0 # boolean + passed = 0 # counter + failed = 0 # counter + tests = 0 # counter + printf("\n") + } + /^-/ { + # Print lines starting with hyphen. Such lines terminate any + # open test + finish_test() + line = $0 + gsub(/^. ?/, "", line) + print line + next + } + /^COMPLETE/ { + # The line identifies completion of all tests. If this + # line is not reached it may be because the pipe from psql + # was broken. + complete = 1 + } + /^PREP/ { + # The line identifies the start of the preparation for a + # test. Lines between here and the TEST statement are + # ignored unless an ERROR is detected which will cause the + # subsequent test to fail + finish_test() + prepping = 1 + prepfail = 0 + ignore = $2 == "IGNORE" + preplines = "" + next + } + /^TEST/ { + # The line defines a test. Format is: + # TEST # + prepping = 0 + finish_test() + testno = $2 + condition = $3 + nf = split($0, fields, /#/) + pattern = fields[2] + if (nf > 2) { + summary = fields[3] + } + else { + summary = "result " condition " " pattern + } + line = sprintf("%8s: %s", testno, summary) + printf("%-65s", line) + found = 0 + lines="" + testing = 1 + ok = 1 + tests++ + print >>LOGFILE # All input lines go to logfile + # This is done explicitly so that the output + # from finish_test is logged first + next # No more to do for this line + } + { print >>LOGFILE } # All input lines go to logfile + (prepping) { + if ($0 ~ /ERROR/) { + if (!ignore) { + prepfail = 1 + } + } + if (prepfail) { + if (preplines == "") { + preplines = " " $0 + } + else { + preplines = preplines "\n " $0 + } + } + next + } + { # may be a result line. Test against condition and pattern, + # and accumlate the line for later output + sub(/^[ ]*/, "") # Remove leading space + sub(/[ ]*$/, "") # Remove trailing space +#printf("\nComparing :%s:%s:%s:\n", $0, condition, pattern) + if ((condition == "=") || + (condition == "!=")) + { + if ($0 == pattern) { +#print "Found" + found = 1 + printf "MATCH = \"%s\"\n", $0 >>LOGFILE + } + } + else if ((condition == "~") || + (condition == "!~")) + { + if ($0 ~ pattern) { + found = 1 + printf "MATCH ~ \"%s\"\n", $0 >>LOGFILE + } + } + if (lines == "") { + lines = " " $0 + } + else { + lines = lines "\n " $0 + } + } + END { + finish_test() + printf("\ntests: %d\npassed: %d\nfailed: %d\n\n", + tests, passed, failed) + printf("\ntests: %d\npassed: %d\nfailed: %d\n\n", + tests, passed, failed) >>LOGFILE + if (!complete) { + print "NOT ALL TESTS COMPLETED! (Check logfile for details)" + print "NOT ALL TESTS COMPLETED!" >>LOGFILE + exit(2) + } + else { + if (tests != passed) { + exit(2) + } + } + } + ' LOGFILE=$1 +} + +# psql_clean +# Filter output from psql to clean it. Send raw output to logfile $1 +# +psql_clean() +{ + tee -a $1 | awk ' + function do_print(str) { + if (continuing) { + printf("\n") + } + printf("%s", str) + continuing = 1 + } + /^-/ { + sub(/^- ?/, "") + do_print($0) + } + /^INSERT/ {printf(".")} + /^CREATE/ {printf(".")} + /^DROP/ {printf(".")} + /^DELETE/ {printf(".")} + /^COMMIT/ {printf(".")} + /[: ]*ERROR[: ]/ {do_print($0); errors=1} + /[: ]*HINT[: ]/ {do_print($0)} + /[: ]*WARN[: ]/ {do_print($0)} + END { + do_print("") + if (errors) exit(2) + } + ' +} diff --git a/veil--1.0.sql b/veil--1.0.sql new file mode 100644 index 0000000..e1a912d --- /dev/null +++ b/veil--1.0.sql @@ -0,0 +1,743 @@ +/* ---------- + * veil_interface.sqs (or a file derived from it) + * + * Source file from which veil_interface_trial.sql and + * veil--.sql is generated using sed. + * + * Copyright (c) 2005 - 2011 Marc Munro + * Author: Marc Munro + * License: BSD + * + * ---------- + */ + +create type veil_range_t as ( + min int4, + max int4 +); +comment on type veil_range_t is +'Veil type used to record ranges. A range is a pair of integers identifying +the minimum and maximum values of the range. Ranges are used to +constrain the size of a bitmap or bitmap array.'; + +create type veil_variable_t as ( + name text, + type text, + shared bool +); +comment on type veil_variable_t is +'Veil type used as the result type of veil_variables(), to describe each +variable known to a veil instance.'; + + +create or replace +function veil_share(name text) returns bool + as '$libdir/veil', 'veil_share' + language C stable strict; + +comment on function veil_share(name text) is +'Create a shared variable named NAME. + +Return TRUE if successful, else raise an error. + +Create a veil variable as a shared variable. Variables are named +containers for various types of values. They are referenced by name, +passing the name as a string to the various veil functions. + +Variables may be shared variables in which case they are available, with +the same value, to all sessions, or session variables in which case each +session will have its own instance. Variables are by default created as +session variables, and are created by assignment, or initialisation. To +create a shared variable, call veil_share() with the name of the +variable you wish to create, then create and use the variable as you +would a session variable. Shared variables should only be created and +initialised from veil_init() in order to prevent race conditions. If a +variable has already been created as a session variable, it cannot be +repurposed as a shared variable.'; + + +create or replace +function veil_variables() returns setof veil_variable_t + as '$libdir/veil', 'veil_variables' + language C stable; + +comment on function veil_variables() is +'List all current veil_variables. +Return a set of veil_variable_t results, detailing each existant +variable + +This is intended for interactive use for debugging purposes.'; + + +create or replace +function veil_init_range(name text, min int4, max int4) returns int4 + as '$libdir/veil', 'veil_init_range' + language C stable strict; + +comment on function veil_init_range(text, int4, int4) is +'Initialise a Range variable called NAME constrained by MIN and MAX. + +Return the number of elements in the range. + +Ranges may be examined using the veil_range() function.'; + + +create or replace +function veil_range(name text) returns veil_range_t + as '$libdir/veil', 'veil_range' + language C stable strict; + +comment on function veil_range(name text) is +'Return the range for range variable NAME.'; + + +create or replace +function veil_init_bitmap(bitmap_name text, range_name text) returns bool + as '$libdir/veil', 'veil_init_bitmap' + language C stable strict; + +comment on function veil_init_bitmap(text, text) is +'Create or re-initialise the Bitmap named BITMAP_NAME, for the range of +bits given by RANGE_NAME. + +Return TRUE on success, raise an error otherwise. + +All bits in the bitmap will be zero (cleared).'; + + +create or replace +function veil_clear_bitmap(name text) returns bool + as '$libdir/veil', 'veil_clear_bitmap' + language C stable strict; + +comment on function veil_clear_bitmap(text) is +'Clear the Bitmap or BitmapRef identified by NAME. + +Return TRUE, or raise an error. + +Clear (set to zero) all bits in the named bitmap.'; + + +create or replace +function veil_bitmap_setbit(name text, bit_number int4) returns bool + as '$libdir/veil', 'veil_bitmap_setbit' + language C stable strict; + +comment on function veil_bitmap_setbit(text, int4) is +'In the Bitmap or BitmapRef identified by NAME, set the bit given by +BIT_NUMBER. + +Return TRUE or raise an error. + +Set to 1, the identified bit.'; + + +create or replace +function veil_bitmap_clearbit(name text, bit_number int4) returns bool + as '$libdir/veil', 'veil_bitmap_clearbit' + language C stable strict; + +comment on function veil_bitmap_clearbit(text, int4) is +'In the Bitmap or BitmapRef identified by NAME, clear the bit given by +BIT_NUMBER. + +Return TRUE or raise an error. + +Set to 0, the identified bit.'; + + +create or replace +function veil_bitmap_testbit(name text, bit_number int4) returns bool + as '$libdir/veil', 'veil_bitmap_testbit' + language C stable strict; + +comment on function veil_bitmap_testbit(text, int4) is +'In the Bitmap or BitmapRef identified by NAME, test the bit given by +BIT_NUMBER. + +Return TRUE if the bit is set, FALSE if it is zero.'; + + +create or replace +function veil_bitmap_union(result_name text, name2 text) returns bool + as '$libdir/veil', 'veil_bitmap_union' + language C stable strict; + +comment on function veil_bitmap_union(text, text) is +'Union two Bitmaps, RESULT_NAME and NAME2, with the result going into +the first. + +Return TRUE, or raise an error.'; + + +create or replace +function veil_bitmap_intersect(result_name text, name2 text) returns bool + as '$libdir/veil', + 'veil_bitmap_intersect' + language C stable strict; + +comment on function veil_bitmap_intersect(text, text) is +'Intersect two Bitmaps, RESULT_NAME and NAME2, with the result going into +the first. + +Return TRUE, or raise an error.'; + + +create or replace +function veil_bitmap_bits(name text) returns setof int4 + as '$libdir/veil', 'veil_bitmap_bits' + language C stable strict; + +comment on function veil_bitmap_bits(text) is +'Return each bit in the bitmap NAME. + +This is primarily intended for interactive use for debugging, etc.'; + + +create or replace +function veil_bitmap_range(name text) returns veil_range_t + as '$libdir/veil', 'veil_bitmap_range' + language C stable strict; + +comment on function veil_bitmap_range(text) is +'Return the range of bitmap NAME. + +It is primarily intended for interactive use.'; + + + +create or replace +function veil_init_bitmap_array(bmarray text, array_range text, + bitmap_range text) returns bool + as '$libdir/veil', 'veil_init_bitmap_array' + language C stable strict; + +comment on function veil_init_bitmap_array(text, text, text) is +'Creates or resets (clears) BMARRAY, to have ARRAY_RANGE bitmaps of +BITMAP_RANGE bits. + +Returns TRUE or raises an error'; + + +create or replace +function veil_clear_bitmap_array(bmarray text) returns bool + as '$libdir/veil', 'veil_clear_bitmap_array' + language C stable strict; + +comment on function veil_clear_bitmap_array(text) is +'Clear all bits in all bitmaps of bitmap array BMARRAY. + +Return TRUE or raise an error'; + + +create or replace +function veil_bitmap_from_array(bmref text, bmarray text, + index int4) returns text + as '$libdir/veil', + 'veil_bitmap_from_array' + language C stable strict; + +comment on function veil_bitmap_from_array(text, text, int4) is +'Set BitmapRef BMREF to the bitmap from BMARRAY indexed by INDEX. + +Return the name of the BitmapRef. + +This is used to isolate a single bitmap from a bitmap array, recording +it in a BitmapRef. That bitmap can then be manipulated by ordinary veil +bitmap functions. Note that BitMapRefs can only be referenced within +the transaction they are defined.'; + + +create or replace +function veil_bitmap_array_testbit( + bmarray text, arr_idx int4, bitno int4) returns bool + as '$libdir/veil', + 'veil_bitmap_array_testbit' + language C stable strict; + +comment on function veil_bitmap_array_testbit(text, int4, int4) is +'Test a bit in BMARRAY, from the bitmap indexed by ARR_IDX, checking the +bit identified by BITNO. + +Return TRUE if the bit is set, else FALSE'; + + +create or replace +function veil_bitmap_array_setbit( + bmarray text, arr_idx int4, bitno int4) returns bool + as '$libdir/veil', + 'veil_bitmap_array_setbit' + language C stable strict; + +comment on function veil_bitmap_array_setbit(text, int4, int4) is +'Set a bit in BMARRAY, from the bitmap indexed by ARR_IDX, setting the +bit identified by BITNO. + +Return TRUE'; + + +create or replace +function veil_bitmap_array_clearbit( + bmarray text, arr_idx int4, bitno int4) returns bool + as '$libdir/veil', + 'veil_bitmap_array_clearbit' + language C stable strict; + +comment on function veil_bitmap_array_clearbit(text, int4, int4) is +'Clear a bit in BMARRAY, from the bitmap indexed by ARR_IDX, clearing the +bit identified by BITNO. + +Return TRUE'; + + +create or replace +function veil_union_from_bitmap_array( + bitmap text, bmarray text, arr_idx int4) returns bool + as '$libdir/veil', + 'veil_union_from_bitmap_array' + language C stable strict; + +comment on function veil_union_from_bitmap_array(text, text, int4) is +'Union BITMAP with BMARRAY[ARR_IDX], with the result going into bitmap. + +Return TRUE'; + + +create or replace +function veil_intersect_from_bitmap_array( + bitmap text, bmarray text, arr_idx int4) returns bool + as '$libdir/veil', + 'veil_intersect_from_bitmap_array' + language C stable strict; + +comment on function veil_intersect_from_bitmap_array(text, text, int4) is +'Intersect BITMAP with BMARRAY[ARR_IDX], with the result going into bitmap. + +Return TRUE'; + + +create or replace +function veil_bitmap_array_bits(bmarray text, arr_idx int4) returns setof int4 + as '$libdir/veil', + 'veil_bitmap_array_bits' + language C stable strict; + +comment on function veil_bitmap_array_bits(text, int4) is +'Return all bits in the bitmap given by BMARRAY[ARR_IDX]. + +This is primarily intended for interactive use: for debugging, etc.'; + + +create or replace +function veil_bitmap_array_arange(bmarray text) returns veil_range_t + as '$libdir/veil', + 'veil_bitmap_array_arange' + language C stable strict; + +comment on function veil_bitmap_array_arange(text) is +'Return the array bounds for BMARRAY.'; + + +create or replace +function veil_bitmap_array_brange(bmarray text) returns veil_range_t + as '$libdir/veil', + 'veil_bitmap_array_brange' + language C stable strict; + +comment on function veil_bitmap_array_brange(text) is +'Return the range of the bitmaps in BMARRAY.'; + + + +create or replace +function veil_init_bitmap_hash(bmhash text, range text) returns bool + as '$libdir/veil', 'veil_init_bitmap_hash' + language C stable strict; + +comment on function veil_init_bitmap_hash(text, text) is +'Initialise a bitmap hash variable called BMHASH to contain bitmaps of +size RANGE. + +Return TRUE.'; + + +create or replace +function veil_clear_bitmap_hash(bmhash text) returns bool + as '$libdir/veil', 'veil_clear_bitmap_hash' + language C stable strict; + +comment on function veil_clear_bitmap_hash(text) is +'Clear all bits in an existing bitmap hash named BMHASH. + +Return TRUE.'; + + +create or replace +function veil_bitmap_hash_key_exists(bmhash text, key text) returns bool + as '$libdir/veil', + 'veil_bitmap_hash_key_exists' + language C stable strict; + +comment on function veil_bitmap_hash_key_exists(text, text) is +'Determine whether in BMHASH the given KEY already exists. + +Return TRUE if the key exists, else FALSE.'; + + +create or replace +function veil_bitmap_from_hash(text, text, text) returns text + as '$libdir/veil', + 'veil_bitmap_from_hash' + language C stable strict; + +comment on function veil_bitmap_from_hash(text, text, text) is +'Set BitmapRef BMREF to the bitmap from BMHASH identfied by KEY. + +Return the name of BMREF.'; + + +create or replace +function veil_bitmap_hash_testbit(text, text, int4) returns bool + as '$libdir/veil', + 'veil_bitmap_hash_testbit' + language C stable strict; + +comment on function veil_bitmap_hash_testbit(text, text, int4) is +'Test the bit, in the bitmap from BMHASH identified by KEY, given by +BITNO. + +Return TRUE if the bit is set, else FALSE.'; + + +create or replace +function veil_bitmap_hash_setbit(text, text, int4) returns bool + as '$libdir/veil', + 'veil_bitmap_hash_setbit' + language C stable strict; + +comment on function veil_bitmap_hash_setbit(text, text, int4) is +'Set the bit, in the bitmap from BMHASH identified by KEY, given by +BITNO to TRUE. + +Return TRUE.'; + + +create or replace +function veil_bitmap_hash_clearbit(text, text, int4) returns bool + as '$libdir/veil', + 'veil_bitmap_hash_clearbit' + language C stable strict; + +comment on function veil_bitmap_hash_clearbit(text, text, int4) is +'Set the bit, in the bitmap from BMHASH identified by KEY, given by +BITNO to FALSE. + +Return TRUE.'; + + +create or replace +function veil_union_into_bitmap_hash(text, text, text) returns bool + as '$libdir/veil', + 'veil_union_into_bitmap_hash' + language C stable strict; + +comment on function veil_union_into_bitmap_hash(text, text, text) is +'Into the bitmap from BMHASH, identified by KEY, and union the bits from +BITMAP (which may be a bitmap or bitmap_ref). + +Return TRUE.'; + + +create or replace +function veil_union_from_bitmap_hash(text, text, text) returns bool + as '$libdir/veil', + 'veil_union_from_bitmap_hash' + language C stable strict; +comment on function veil_union_from_bitmap_hash(text, text, text) is +'Retrieve the bitmap from BMHASH, identified by KEY, and union it into +BITMAP (which may be a bitmap or bitmap_ref). + +Return TRUE.'; + + +create or replace +function veil_intersect_from_bitmap_hash(text, text, text) returns bool + as '$libdir/veil', + 'veil_intersect_from_bitmap_hash' + language C stable strict; +comment on function veil_intersect_from_bitmap_hash(text, text, text) is +'Into BITMAP, intersect the bits from the bitmap in BMHASH identified by +KEY. + +Return TRUE.'; + + +create or replace +function veil_bitmap_hash_bits(text, text) returns setof int4 + as '$libdir/veil', + 'veil_bitmap_hash_bits' + language C stable strict; + +comment on function veil_bitmap_hash_bits(text, text) is +'Return the set of bits in the bitset from BMHASH identfied by KEY.'; + + +create or replace +function veil_bitmap_hash_range(text) returns veil_range_t + as '$libdir/veil', + 'veil_bitmap_hash_range' + language C stable strict; + +comment on function veil_bitmap_hash_range(text) is +'Return the range of all bitmaps in BMHASH.'; + + +create or replace +function veil_bitmap_hash_entries(text) returns setof text + as '$libdir/veil', + 'veil_bitmap_hash_entries' + language C stable strict; + +comment on function veil_bitmap_hash_entries(text) is +'Return the keys of all bitmaps in BMHASH.'; + + +create or replace +function veil_int4_set(text, int4) returns int4 + as '$libdir/veil', + 'veil_int4_set' + language C stable; + +comment on function veil_int4_set(text, int4) is +'Set the int4 variable NAME to VALUE. + +Return the new value'; + + +create or replace +function veil_int4_get(text) returns int4 + as '$libdir/veil', + 'veil_int4_get' + language C stable strict; + +comment on function veil_int4_get(text) is +'Return the value of int4 variable NAME.'; + + +create or replace +function veil_init_int4array(text, text) returns bool + as '$libdir/veil', 'veil_init_int4array' + language C stable strict; + +comment on function veil_init_int4array(text, text) is +'Initialise the int4 array ARRAYNAME, with an index range of RANGE. + +Each entry in the array is set to zero. + +Return TRUE.'; + + +create or replace +function veil_clear_int4array(text) returns bool + as '$libdir/veil', + 'veil_clear_int4array' + language C stable strict; +comment on function veil_clear_int4array(text) is +'Reset each entry in the int4 array ARRAYNAME to zero. + +Return TRUE.'; + + + +create or replace +function veil_int4array_set(text, int4, int4) returns int4 + as '$libdir/veil', + 'veil_int4array_set' + language C stable; + +comment on function veil_int4array_set(text, int4, int4) is +'Set the ARRAY element IDX to VALUE. + +Return the new value.'; + + +create or replace +function veil_int4array_get(text, int4) returns int4 + as '$libdir/veil', + 'veil_int4array_get' + language C stable strict; + +comment on function veil_int4array_get(text, int4) is +'Return the value of ARRAY element IDX.'; + + +create or replace +function veil_init(bool) returns bool + as '$libdir/veil', + 'veil_init' + language C stable strict; + +comment on function veil_init(bool) is +'This is the default version of veil_init, which does nothing except +raise an error.'; + + +create or replace +function veil_perform_reset() returns bool + as '$libdir/veil', 'veil_perform_reset' + language C stable; + +comment on function veil_perform_reset() is +'Allow userspace to force call of veil_init. + +This function may fail due to outstanding transactions possibly holding +shared memory that we wish to re-use. In this case a warning will be +issued. Once any long running transactions have completed, a retry +should succeed. + +Return TRUE if successful, FALSE otherwise.'; + + +create or replace +function veil_force_reset() returns bool + as '$libdir/veil', 'veil_force_reset' + language C stable; + +comment on function veil_force_reset() is +'Allow userspace to force an unconditional reset of veil shared memory. + +This always causes a PANIC, causing the database to fully reset.'; + + +create or replace +function veil_version() returns text + as '$libdir/veil', 'veil_version' + language C stable; + +comment on function veil_version() is +'Return a text string identifying the current veil version.'; + + + +create or replace +function veil_serialise(varname text) returns text + as '$libdir/veil', + 'veil_serialise' + language C stable strict; + +comment on function veil_serialise(varname text) is +'Return a serialised copy of a variable VARNAME in text form. + +This is intended to be used with pgmemcache so that session variables +can be efficiently cached. Serialised values can be concatenated +together as a single string and then deserialised in a single operation. + +The value can be restored by de-serialising it.'; + + + +create or replace +function veil_serialize(text) returns text + as '$libdir/veil', + 'veil_serialise' + language C stable strict; + +comment on function veil_serialize(varname text) is +'Return a serialised copy of a variable VARNAME in text form. + +This is intended to be used with pgmemcache so that session variables +can be efficiently cached. Serialized values can be concatenated +together as a single string and then deserialized in a single operation. + +The value can be restored by de-serializing it.'; + + + +create or replace +function veil_deserialise(text) returns int4 + as '$libdir/veil', + 'veil_deserialise' + language C stable strict; + +comment on function veil_deserialise(text) is +'Reset the contents of a set of serialised variable from STREAM. + +Return the number of items de-serialised.'; + + +-- Ditto for victims of webster. +create or replace +function veil_deserialize(text) returns int4 + as '$libdir/veil', + 'veil_deserialise' + language C stable strict; + +comment on function veil_deserialize(text) is +'Reset the contents of a set of serialized variable from STREAM. + +Return the number of items de-serialized.'; + + +revoke execute on function veil_share(text) from public; +revoke execute on function veil_variables() from public; +revoke execute on function veil_init_range(text, int4,int4) from public; +revoke execute on function veil_range(text) from public; + +revoke execute on function veil_init_bitmap(text, text) from public; +revoke execute on function veil_clear_bitmap(text) from public; +revoke execute on function veil_bitmap_setbit(text, int4) from public; +revoke execute on function veil_bitmap_testbit(text, int4) from public; +revoke execute on function veil_bitmap_bits(text) from public; +revoke execute on function veil_bitmap_range(text) from public; + +revoke execute on function veil_init_bitmap_array(text, text, text) + from public; +revoke execute on function veil_clear_bitmap_array(text) from public; +revoke execute on function veil_bitmap_from_array(text, text, int4) + from public; +revoke execute on function veil_bitmap_array_setbit(text, int4, int4) + from public; +revoke execute on function veil_bitmap_array_testbit(text, int4, int4) + from public; +revoke execute on function veil_union_from_bitmap_array(text, text, int4) + from public; +revoke execute on function veil_intersect_from_bitmap_array(text, text, int4) + from public; +revoke execute on function veil_bitmap_array_bits(text, int4) from public; +revoke execute on function veil_bitmap_array_arange(text) from public; +revoke execute on function veil_bitmap_array_brange(text) from public; + + +revoke execute on function veil_init_bitmap_hash(text, text) from public; +revoke execute on function veil_clear_bitmap_hash(text) from public; +revoke execute on function veil_bitmap_hash_key_exists(text, text) + from public; +revoke execute on function veil_bitmap_from_hash(text, text, text) + from public; +revoke execute on function veil_bitmap_hash_setbit(text, text, int4) + from public; +revoke execute on function veil_bitmap_hash_testbit(text, text, int4) + from public; +revoke execute on function veil_union_into_bitmap_hash(text, text, text) + from public; +revoke execute on function veil_union_from_bitmap_hash(text, text, text) + from public; +revoke execute on function veil_intersect_from_bitmap_hash(text, text, text) + from public; +revoke execute on function veil_bitmap_hash_bits(text, text) from public; +revoke execute on function veil_bitmap_hash_range(text) from public; +revoke execute on function veil_bitmap_hash_entries(text) from public; + +revoke execute on function veil_init_int4array(text, text) from public; +revoke execute on function veil_clear_int4array(text) from public; +revoke execute on function veil_int4array_set(text, int4, int4) from public; +revoke execute on function veil_int4array_get(text, int4) from public; + +revoke execute on function veil_init(bool) from public; + +revoke execute on function veil_serialise(text) from public; +revoke execute on function veil_serialize(text) from public; +revoke execute on function veil_deserialise(text) from public; +revoke execute on function veil_deserialize(text) from public; + + diff --git a/veil.control b/veil.control new file mode 100644 index 0000000..f2350bc --- /dev/null +++ b/veil.control @@ -0,0 +1,16 @@ +# veil.control +# +# Postgres extension control file for Veil +# +# Copyright (c) 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# + +directory = 'extension/veil' +default_version = '1.0' +module_pathname = '$libdir/veil' +superuser = true +relocatable = true + +comment = 'Provides tools for building row-level access controls.' diff --git a/veil_demo--1.0.sql b/veil_demo--1.0.sql new file mode 100644 index 0000000..491cd1d --- /dev/null +++ b/veil_demo--1.0.sql @@ -0,0 +1,1265 @@ +create schema hidden; +revoke all on schema hidden from public; + +create table hidden.privileges ( + privilege_id integer not null, + privilege_name varchar(80) not null +); +alter table hidden.privileges add constraint privilege__pk + primary key(privilege_id); + + +create table hidden.roles ( + role_id integer not null, + role_name varchar(80) not null +); +alter table hidden.roles add constraint role__pk + primary key(role_id); + + +create table hidden.role_privileges ( + role_id integer not null, + privilege_id integer not null +); + +alter table hidden.role_privileges add constraint role_privilege__pk + primary key(role_id, privilege_id); + +alter table hidden.role_privileges add constraint role_privilege__role_fk + foreign key(role_id) + references hidden.roles(role_id); + +alter table hidden.role_privileges add constraint role_privilege__priv_fk + foreign key(privilege_id) + references hidden.privileges(privilege_id); + +create table hidden.role_roles ( + role_id integer not null, + has_role_id integer not null +); + +alter table hidden.role_roles add constraint role_role__pk + primary key(role_id, has_role_id); + +alter table hidden.role_roles add constraint role_role__role_fk + foreign key(role_id) + references hidden.roles(role_id); + +alter table hidden.role_roles add constraint role_role__has_role_fk + foreign key(has_role_id) + references hidden.roles(role_id); + + +create sequence person_id_seq; +create table hidden.persons ( + person_id integer not null, + person_name varchar(80) not null +); +alter table hidden.persons add constraint person__pk + primary key(person_id); + + +create sequence project_id_seq; +create table hidden.projects ( + project_id integer not null, + project_name varchar(80) not null +); +alter table hidden.projects add constraint project__pk + primary key(project_id); + + +create table hidden.detail_types ( + detail_type_id integer not null, + required_privilege_id integer not null, + detail_type_name varchar(80) not null +); +alter table hidden.detail_types add constraint detail_type__pk + primary key(detail_type_id); + +alter table hidden.detail_types add constraint detail_type__priv_fk + foreign key(required_privilege_id) + references hidden.privileges(privilege_id); + + +create table hidden.assignments ( + project_id integer not null, + person_id integer not null, + role_id integer not null +); +alter table hidden.assignments add constraint assignment__pk + primary key(project_id, person_id); + +alter table hidden.assignments add constraint assignment__project_fk + foreign key(project_id) + references hidden.projects(project_id); + +alter table hidden.assignments add constraint assignment__person_fk + foreign key(person_id) + references hidden.persons(person_id); + +alter table hidden.assignments add constraint assignment__role_fk + foreign key(role_id) + references hidden.roles(role_id); + + +create table hidden.person_roles ( + person_id integer not null, + role_id integer not null +); +alter table hidden.person_roles add constraint person_role__pk + primary key(person_id, role_id); + +alter table hidden.person_roles add constraint person_role__person_fk + foreign key(person_id) + references hidden.persons(person_id); + +alter table hidden.person_roles add constraint person_role__role_fk + foreign key(role_id) + references hidden.roles(role_id); + + +create table hidden.project_details ( + project_id integer not null, + detail_type_id integer not null, + value text not null +); +alter table hidden.project_details add constraint project_detail__pk + primary key(project_id, detail_type_id); + +alter table hidden.project_details add constraint project_detail__project_fk + foreign key(project_id) + references hidden.projects(project_id); + +alter table hidden.project_details add constraint project_detail__detail_fk + foreign key(detail_type_id) + references hidden.detail_types(detail_type_id); + + +create table hidden.person_details ( + person_id integer not null, + detail_type_id integer not null, + value text not null +); +alter table hidden.person_details add constraint person_detail__pk + primary key(person_id, detail_type_id); + +alter table hidden.person_details add constraint person_detail__person_fk + foreign key(person_id) + references hidden.persons(person_id); + +alter table hidden.person_details add constraint person_detail__detail_fk + foreign key(detail_type_id) + references hidden.detail_types(detail_type_id); + + +insert into hidden.privileges (privilege_id, privilege_name) values +(10001, 'select_privileges'), +(10002, 'insert_privileges'), +(10003, 'update_privileges'), +(10004, 'delete_privileges'), +(10005, 'select_roles'), +(10006, 'insert_roles'), +(10007, 'update_roles'), +(10008, 'delete_roles'), +(10009, 'select_role_privileges'), +(10010, 'insert_role_privileges'), +(10011, 'update_role_privileges'), +(10012, 'delete_role_privileges'), +(10013, 'select_persons'), +(10014, 'insert_persons'), +(10015, 'update_persons'), +(10016, 'delete_persons'), +(10017, 'select_projects'), +(10018, 'insert_projects'), +(10019, 'update_projects'), +(10020, 'delete_projects'), +(10021, 'select_detail_types'), +(10022, 'insert_detail_types'), +(10023, 'update_detail_types'), +(10024, 'delete_detail_types'), +(10025, 'select_assignments'), +(10026, 'insert_assignments'), +(10027, 'update_assignments'), +(10028, 'delete_assignments'), +(10029, 'select_person_roles'), +(10030, 'insert_person_roles'), +(10031, 'update_person_roles'), +(10032, 'delete_person_roles'), +(10033, 'select_project_details'), +(10034, 'insert_project_details'), +(10035, 'update_project_details'), +(10036, 'delete_project_details'), +(10037, 'select_person_details'), +(10038, 'insert_person_details'), +(10039, 'update_person_details'), +(10040, 'delete_person_details'), +(10041, 'select_role_roles'), +(10042, 'insert_role_roles'), +(10043, 'update_role_roles'), +(10044, 'delete_role_roles'), +(10100, 'can_connect'), +(10150, 'view_basic'), +(10151, 'view_personal'), +(10152, 'view_personal_secure'), +(10153, 'view_project_confidential'); + +insert into hidden.roles (role_id, role_name) values +(11001, 'DBA'), +(11002, 'Personal Context'), +(11003, 'Employee'), +(11004, 'Worker'), +(11005, 'Project Manager'), +(11006, 'Director'), +(11007, 'Manager'); + +-- DBA can do anything (but is not automatically an employee) +insert into hidden.role_privileges (role_id, privilege_id) +select 11001, privilege_id +from hidden.privileges +where privilege_id != 10100; + +-- Personal Context allows update of personal details +insert into hidden.role_privileges (role_id, privilege_id) values +(11002, 10013), +(11002, 10015), +(11002, 10025), +(11002, 10029), +(11002, 10037), +(11002, 10038), +(11002, 10039), +(11002, 10040), +(11002, 10150), +(11002, 10151), +(11002, 10152); + +-- Basic Access can see lookup data +insert into hidden.role_privileges (role_id, privilege_id) +select 11003, privilege_id +from hidden.privileges +where privilege_name in ('select_privileges', 'select_roles', + 'select_role_privileges', 'select_detail_types'); + +insert into hidden.role_privileges (role_id, privilege_id) +select 11003, 10100; + +-- Workers can modify project info +insert into hidden.role_privileges (role_id, privilege_id) +select 11004, privilege_id +from hidden.privileges +where privilege_name like '%project%' +and privilege_name not like 'delete%' +and privilege_name not like '%confidential'; + +insert into hidden.role_privileges (role_id, privilege_id) +select 11004, 10025; +insert into hidden.role_privileges (role_id, privilege_id) +select 11004, 10150; + +-- Project Manager can do anything to project info and can see personal info +insert into hidden.role_privileges (role_id, privilege_id) +select 11005, privilege_id +from hidden.privileges +where privilege_name like '%project%' +or privilege_name like '%assignment%'; + +insert into hidden.role_privileges (role_id, privilege_id) +select 11005, privilege_id +from hidden.privileges +where privilege_name like 'select_person%'; + +insert into hidden.role_privileges (role_id, privilege_id) +select 11005, 10150; +insert into hidden.role_privileges (role_id, privilege_id) +select 11005, 10151; + +-- Director can do anything except modify personal details +insert into hidden.role_privileges (role_id, privilege_id) +select 11006, privilege_id +from hidden.privileges +where privilege_name not like '%person%'; + +insert into hidden.role_privileges (role_id, privilege_id) +select 11006, privilege_id +from hidden.privileges +where privilege_name like 'select_person%'; + +insert into hidden.role_privileges (role_id, privilege_id) +select 11006, 10014; + +insert into hidden.role_privileges (role_id, privilege_id) +select 11006, 10151; + +insert into hidden.role_privileges (role_id, privilege_id) +select 11006, 10152; + +-- Manager can see personal info +insert into hidden.role_privileges (role_id, privilege_id) +select 11007, privilege_id +from hidden.privileges +where privilege_name like 'select_person%'; + +insert into hidden.role_privileges (role_id, privilege_id) +select 11007, 10150; +insert into hidden.role_privileges (role_id, privilege_id) +select 11007, 10151; + + +insert into hidden.persons (person_id, person_name) values +(1, 'Deb (the DBA)'), +(2, 'Pat (the PM)'), +(3, 'Derick (the director)'), +(4, 'Will (the worker)'), +(5, 'Wilma (the worker)'), +(6, 'Fred (the fired DBA)'); + +insert into hidden.person_roles (person_id, role_id) values +(1, 11001), +(1, 11003), +(2, 11003), +(2, 11007), +(3, 11003), +(3, 11006), +(4, 11003), +(5, 11003), +(6, 11001); + +insert into hidden.projects (project_id, project_name) values +(101, 'Secret Project'), +(102, 'Public project'); + +insert into hidden.assignments (project_id, person_id, role_id) values +(101, 3, 11005), +(101, 5, 11004), +(102, 2, 11005), +(102, 4, 11004), +(102, 5, 11004); + +insert into hidden.detail_types (detail_type_id, required_privilege_id, + detail_type_name) values +(1001, 10150, 'start_date'), +(1002, 10150, 'status'), +(1003, 10150, 'join_date'), +(1004, 10152, 'salary'), +(1005, 10151, 'date of birth'), +(1006, 10152, 'sin'), +(1007, 10150, 'skills'), +(1008, 10153, 'contract value'); + +insert into hidden.person_details (person_id, detail_type_id, value) values +(1, 1003, '20050102'), +(2, 1003, '20050103'), +(3, 1003, '20050104'), +(4, 1003, '20050105'), +(5, 1003, '20050106'), +(6, 1003, '20050107'), +(1, 1002, 'Employee'), +(2, 1002, 'Employee'), +(3, 1002, 'Employee'), +(4, 1002, 'Employee'), +(5, 1002, 'Employee'), +(6, 1002, 'Terminated'), +(1, 1004, '50,000'), +(2, 1004, '50,000'), +(3, 1004, '80,000'), +(4, 1004, '30,000'), +(5, 1004, '30,000'), +(6, 1004, '40,000'), +(1, 1005, '19610102'), +(2, 1005, '19600102'), +(3, 1005, '19650102'), +(4, 1005, '19660102'), +(5, 1005, '19670102'), +(1, 1006, '123456789'), +(2, 1006, '123456789'), +(3, 1006, '123456789'), +(4, 1006, '123456789'), +(5, 1006, '123456789'), +(1, 1007, 'Oracle, C, SQL'), +(2, 1007, 'Soft peoply-stuff'), +(3, 1007, 'None at all'), +(4, 1007, 'Subservience'), +(5, 1007, 'Subservience'); + + +insert into hidden.project_details (project_id, detail_type_id, value) values +(101, 1001, '20050101'), +(101, 1002, 'Secretly ongoing'), +(101, 1008, '$800,000'), +(102, 1001, '20050101'), +(102, 1002, 'Ongoing'), +(102, 1008, '$100,000'); + + +-- Veil Variables: +-- Shared: +-- privs_range Range The range of privilege_id in the privileges table +-- roles_range Range The range of role_id in the roles table +-- role_privs Bitmap Array Array of privilege bitmaps indexed by role_id +-- det_types_range Range The range of detail_type_id in detail_types +-- det_types_privs Int4Array Mapping of detail_type_id to the privilege +-- required to access details of that type +-- +-- Session: +-- person_id Int4 The id of the currently connected person +-- global_context Bitmap The global privileges of the connected person +-- project_context Bitmap Hash Hash of privilege bitmaps, indexed by +-- project_id, giving the connected person's project privileges. +-- +-- Security Contexts: +-- Global +-- Privileges are identified in variable global_context +-- Personal +-- Privileges are identified in role_privs[11002]. +-- Project +-- A relational context. Privileges are identified in +-- project_context[project_id]. + +-- Replacement for default version of veil_init. +-- Initialise and load all shared variables. +-- +create or replace +function veil_init(bool) returns bool as ' +declare + doing_reset alias for $1; + exists_privs_range bool; + exists_roles_range bool; + exists_role_privs bool; + exists_det_types_range bool; + exists_det_types_privs bool; + init_reqd bool; + dummy bool; + dummy2 bool; + dummyint int4; + _count int4; +begin + -- Declare all shared variables. + + select into exists_privs_range, exists_roles_range, + exists_role_privs, exists_det_types_range, + exists_det_types_privs + veil_share(''privs_range''), veil_share(''roles_range''), + veil_share(''role_privs''), veil_share(''det_types_range''), + veil_share(''det_types_privs''); + + init_reqd = not (exists_privs_range and exists_role_privs and + exists_role_privs and exists_det_types_range and + exists_det_types_privs); + + if init_reqd or doing_reset then + -- Load ranges for privs and roles. + select into dummyint + veil_init_range(''roles_range'', min(role_id), max(role_id)) + from hidden.roles; + + select into dummyint + veil_init_range(''privs_range'', min(privilege_id), + max(privilege_id)) + from hidden.privileges; + + -- Load range for detail_types + select into dummyint + veil_init_range(''det_types_range'', + min(detail_type_id), max(detail_type_id)) + from hidden.detail_types; + + -- Initialise array of required privs for detail_types + select into dummy + veil_init_int4array(''det_types_privs'', ''det_types_range''); + select into _count + count(veil_int4array_set(''det_types_privs'', + detail_type_id, required_privilege_id)) + from hidden.detail_types; + + -- Initialise role_privs bitmap_array + select into dummy + veil_init_bitmap_array(''role_privs'', ''roles_range'', + ''privs_range''); + + -- Populate role_privs bitmap_array + select into _count + count(veil_bitmap_array_setbit(''role_privs'', + role_id, privilege_id)) + from hidden.role_privileges; + + end if; + + -- Declare important session variables, so that we do not + -- get odd, undefined variable, error messages. + select into dummyint, dummy, dummy2 + veil_int4_set(''person_id'', null), + veil_init_bitmap(''global_context'', ''privs_range''), + veil_init_bitmap_hash(''project_context'', ''privs_range''); + + return true; +end; +' language plpgsql +stable +security definer; + +revoke execute on function veil_init(bool) from public; + + +create or replace +function connect_person(int4) returns bool as ' +declare + _person_id alias for $1; + dummy int4; + _connect bool; + proj_roles record; + last_proj int4; + first_rec bool; +begin + -- In reality this function would require some authentication token such + -- as a password. This is just a dumb demo version. + + select into _connect disconnect_person(); + + -- Test whether provided person exists. This is where we would, in a + -- real version, do proper authentication. + select into dummy 1 + from hidden.persons + where person_id = _person_id; + + if found then + -- The person exists and passes authentication + + -- From the persons roles set the global_context bitmap. + select into dummy + count(veil_union_from_bitmap_array(''global_context'', + ''role_privs'', role_id)) + from hidden.person_roles + where person_id = _person_id; + + -- Check that user has can_connect privilege + select into _connect + veil_bitmap_testbit(''global_context'', 10100); + + if not _connect then + select into _connect disconnect_person(); + return false; + end if; + + + -- From the persons assignments set the project_context bitmap hash. + select into dummy + count(veil_union_into_bitmap_hash(''project_context'', + project_id::text, + veil_bitmap_from_array(''scratch_bitmap'', + ''role_privs'', role_id))) + from hidden.assignments + where person_id = _person_id; + + -- Finally, record the person_id for the connection. + select into dummy veil_int4_set(''person_id'', _person_id); + + return true; + else + return false; + end if; + +end; +' language plpgsql +stable +security definer; + +create or replace +function disconnect_person() returns bool as ' +declare + dummy int4; + dummy2 bool; + dummy3 bool; +begin + -- Clear session bitmaps, and reset the person_id + select into dummy, dummy2, dummy3 + veil_int4_set(''person_id'', null), + veil_init_bitmap(''global_context'', ''privs_range''), + veil_init_bitmap_hash(''project_context'', ''privs_range''); + return false; +end; +' language plpgsql +stable +security definer; + + +create or replace +function i_have_global_priv(int4) returns bool as ' +declare + priv_id alias for $1; + connection_id int4; + result bool; +begin + select into connection_id, result + veil_int4_get(''person_id''), + veil_bitmap_testbit(''global_context'', priv_id); + if connection_id is null then + return false; + else + return result; + end if; +end; +' language plpgsql +stable +security definer; + + +create or replace +function i_have_personal_priv(int4, int4) returns bool as ' +declare + priv_id alias for $1; + person_id alias for $2; + connection_id int4; + result bool; +begin + select into connection_id, result + veil_int4_get(''person_id''), + veil_bitmap_testbit(''global_context'', priv_id); + if connection_id is null then + -- No-one is connected + return false; + else + if result then + -- We have the required privilege in global context. No need + -- to check any further + return true; + else + if person_id = connection_id then + -- We are connected as the owner of this record. Check + -- whether we have the required privilege in personal + -- context. + select into result + veil_bitmap_array_testbit(''role_privs'', + 11002, priv_id); + return result; + else + -- We have no personal context rights to this record + return false; + end if; + end if; + end if; +end; +' language plpgsql +stable +security definer; + + +create or replace +function i_have_project_priv(int4, int4) returns bool as ' +declare + priv_id alias for $1; + project_id alias for $2; + connection_id int4; + result bool; +begin + select into connection_id, result + veil_int4_get(''person_id''), + veil_bitmap_testbit(''global_context'', priv_id); + if connection_id is null then + -- No-one is connected + return false; + else + if result then + -- We have the required privilege in global context. No need + -- to check any further + return true; + else + select into result + veil_bitmap_hash_testbit(''project_context'', + project_id::text, priv_id); + return result; + end if; + end if; +end; +' language plpgsql +stable +security definer; + + +create or replace +function i_have_proj_or_pers_priv(int4, int4, int4) returns bool as ' +declare + priv_id alias for $1; + project_id alias for $2; + person_id alias for $3; + connection_id int4; + result bool; +begin + select into connection_id, result + veil_int4_get(''person_id''), + veil_bitmap_testbit(''global_context'', priv_id); + if connection_id is null then + -- No-one is connected + return false; + else + if result then + -- We have the required privilege in global context. No need + -- to check any further + return true; + else + if person_id = connection_id then + -- We are connected as the owner of this record. Check + -- whether we have the required privilege in personal + -- context. + select into result + veil_bitmap_array_testbit(''role_privs'', + 11002, priv_id); + return result; + end if; + select into result + veil_bitmap_hash_testbit(''project_context'', + project_id::text, priv_id); + return result; + -- We have no personal context rights to this record + -- so check project context + return false; + end if; + end if; +end; +' language plpgsql +stable +security definer; + +create or replace +function i_have_project_detail_priv(int4, int4) returns bool as ' +declare + result bool; + detail_id alias for $1; + proj_id alias for $2; +begin + select into result + i_have_project_priv(veil_int4array_get(''det_types_privs'', detail_id), + proj_id); + return result; +end; +' language plpgsql +stable +security definer; + +create or replace +function i_have_person_detail_priv(int4, int4) returns bool as ' +declare + result bool; + detail_id alias for $1; + person_id alias for $2; +begin + select into result + i_have_personal_priv(veil_int4array_get(''det_types_privs'', detail_id), + person_id); + return result; +end; +' language plpgsql +stable +security definer; + +create view privileges( + privilege_id, + privilege_name) as +select privilege_id, + privilege_name +from hidden.privileges +where i_have_global_priv(10001); + +create rule ii_privileges as +on insert to privileges +do instead + insert into hidden.privileges + (privilege_id, privilege_name) + select new.privilege_id, new.privilege_name + where i_have_global_priv(10002); + +create rule iu_privileges as +on update to privileges +do instead + update hidden.privileges + set privilege_name = new.privilege_name, + privilege_id = new.privilege_id + where privilege_id = old.privilege_id + and i_have_global_priv(10003); + +create rule id_privileges as +on delete to privileges +do instead + delete from hidden.privileges + where privilege_id = old.privilege_id + and i_have_global_priv(10004); + +grant select, insert, update, delete on privileges to public; + +create view roles( + role_id, + role_name) as +select role_id, + role_name +from hidden.roles +where i_have_global_priv(10005); + +create rule ii_roles as +on insert to roles +do instead + insert into hidden.roles + (role_id, role_name) + select new.role_id, new.role_name + where i_have_global_priv(10006); + +create rule iu_roles as +on update to roles +do instead + update hidden.roles + set role_name = new.role_name, + role_id = new.role_id + where role_id = old.role_id + and i_have_global_priv(10007); + +create rule id_roles as +on delete to roles +do instead + delete from hidden.roles + where role_id = old.role_id + and i_have_global_priv(10008); + +grant select, insert, update, delete on roles to public; + +create view role_privileges( + role_id, + privilege_id) as +select role_id, + privilege_id +from hidden.role_privileges +where i_have_global_priv(10009); + +create rule ii_role_privileges as +on insert to role_privileges +do instead + insert into hidden.role_privileges + (role_id, privilege_id) + select new.role_id, new.privilege_id + where i_have_global_priv(10010); + +create rule iu_role_privileges as +on update to role_privileges +do instead + update hidden.role_privileges + set role_id = new.role_id, + privilege_id = new.privilege_id + where role_id = old.role_id + and privilege_id = old.privilege_id + and i_have_global_priv(10011); + +create rule id_role_privileges as +on delete to role_privileges +do instead + delete from hidden.role_privileges + where role_id = old.role_id + and i_have_global_priv(10012); + +grant select, insert, update, delete on role_privileges to public; + + +create view role_roles( + role_id, + has_role_id) as +select role_id, + has_role_id +from hidden.role_roles +where i_have_global_priv(10041); + +create rule ii_role_roles as +on insert to role_roles +do instead + insert into hidden.role_roles + (role_id, has_role_id) + select new.role_id, new.has_role_id + where i_have_global_priv(10042); + +create rule iu_role_roles as +on update to role_roles +do instead + update hidden.role_roles + set role_id = new.role_id, + has_role_id = new.has_role_id + where role_id = old.role_id + and i_have_global_priv(10043); + +create rule id_role_roles as +on delete to role_roles +do instead + delete from hidden.role_roles + where role_id = old.role_id + and i_have_global_priv(10044); + +grant select, insert, update, delete on role_roles to public; + + +create view persons( + person_id, + person_name) as +select person_id, + person_name +from hidden.persons +where i_have_personal_priv(10013, person_id); + +create rule ii_persons as +on insert to persons +do instead + insert into hidden.persons + (person_id, person_name) + select new.person_id, new.person_name + where i_have_personal_priv(10014, new.person_id); + +create rule iu_persons as +on update to persons +do instead + update hidden.persons + set person_id = new.person_id, + person_name = new.person_name + where person_id = old.person_id + and i_have_personal_priv(10015, old.person_id); + +create rule id_persons as +on delete to persons +do instead + delete from hidden.persons + where person_id = old.person_id + and i_have_personal_priv(10016, old.person_id); + +grant select, insert, update, delete on persons to public; + + +create view projects( + project_id, + project_name) as +select project_id, + project_name +from hidden.projects +where i_have_project_priv(10017, project_id); + +create rule ii_projects as +on insert to projects +do instead + insert into hidden.projects + (project_id, project_name) + select new.project_id, new.project_name + where i_have_project_priv(10018, new.project_id); + +create rule iu_projects as +on update to projects +do instead + update hidden.projects + set project_id = new.project_id, + project_name = new.project_name + where project_id = old.project_id + and i_have_project_priv(10019, old.project_id); + +create rule id_projects as +on delete to projects +do instead + delete from hidden.projects + where project_id = old.project_id + and i_have_project_priv(10020, old.project_id); + +grant select, insert, update, delete on projects to public; + + +create view detail_types ( + detail_type_id, + required_privilege_id, + detail_type_name) as +select detail_type_id, + required_privilege_id, + detail_type_name +from hidden.detail_types +where i_have_global_priv(10021); + +create rule ii_detail_types as +on insert to detail_types +do instead + insert into hidden.detail_types + (detail_type_id, detail_type_name) + select new.detail_type_id, new.detail_type_name + where i_have_global_priv(10022); + +create rule iu_detail_types as +on update to detail_types +do instead + update hidden.detail_types + set detail_type_id = new.detail_type_id, + detail_type_name = new.detail_type_name + where detail_type_id = old.detail_type_id + and i_have_global_priv(10023); + +create rule id_detail_types as +on delete to detail_types +do instead + delete from hidden.detail_types + where detail_type_id = old.detail_type_id + and i_have_global_priv(10024); + +grant select, insert, update, delete on detail_types to public; + + +create view assignments ( + project_id, + person_id, + role_id) as +select project_id, + person_id, + role_id +from hidden.assignments +where i_have_proj_or_pers_priv(10025, project_id, person_id); + +create rule ii_assignments as +on insert to assignments +do instead + insert into hidden.assignments + (project_id, person_id, role_id) + select new.project_id, new.person_id, new.role_id + where i_have_proj_or_pers_priv(10026, new.project_id, new.person_id); + +create rule iu_assignments as +on update to assignments +do instead + update hidden.assignments + set project_id = new.project_id, + person_id = new.person_id, + role_id = new.person_id + where project_id = old.project_id + and person_id = old.person_id + and i_have_proj_or_pers_priv(10027, old.project_id, old.person_id); + +create rule id_assignments as +on delete to assignments +do instead + delete from hidden.assignments + where project_id = old.project_id + and person_id = old.person_id + and i_have_proj_or_pers_priv(10028, old.project_id, old.person_id); + +grant select, insert, update, delete on assignments to public; + + +create view person_roles ( + person_id, + role_id) as +select person_id, + role_id +from hidden.person_roles +where i_have_personal_priv(10029, person_id); + +create rule ii_person_roles as +on insert to person_roles +do instead + insert into hidden.person_roles + (person_id, role_id) + select new.person_id, new.role_id + where i_have_personal_priv(10030, new.person_id); + +create rule iu_person_roles as +on update to person_roles +do instead + update hidden.person_roles + set person_id = new.person_id, + role_id = new.role_id + where person_id = old.person_id + and role_id = old.role_id + and i_have_personal_priv(10031, old.person_id); + +create rule id_person_roles as +on delete to person_roles +do instead + delete from hidden.person_roles + where person_id = old.person_id + and role_id = old.role_id + and i_have_personal_priv(10032, old.person_id); + +grant select, insert, update, delete on person_roles to public; + + +create view project_details ( + project_id, + detail_type_id, + value) as +select project_id, + detail_type_id, + value +from hidden.project_details +where i_have_project_priv(10033, project_id) +and i_have_project_detail_priv(detail_type_id, project_id); + +create rule ii_project_details as +on insert to project_details +do instead + insert into hidden.project_details + (project_id, detail_type_id, value) + select new.project_id, new.detail_type_id, new.value + where i_have_project_priv(10034, new.project_id) + and i_have_project_detail_priv(new.detail_type_id, new.project_id); + +create rule iu_project_details as +on update to project_details +do instead + update hidden.project_details + set project_id = new.project_id, + detail_type_id = new.detail_type_id, + value = new.value + where project_id = old.project_id + and detail_type_id = old.detail_type_id + and i_have_project_priv(10035, old.project_id) + and i_have_project_detail_priv(old.detail_type_id, old.project_id); + +create rule id_project_details as +on delete to project_details +do instead + delete from hidden.project_details + where project_id = old.project_id + and detail_type_id = old.detail_type_id + and i_have_project_priv(10036, old.project_id) + and i_have_project_detail_priv(old.detail_type_id, old.project_id); + +grant select, insert, update, delete on project_details to public; + + +create view person_details ( + person_id, + detail_type_id, + value) as +select person_id, + detail_type_id, + value +from hidden.person_details +where i_have_personal_priv(10037, person_id) +and i_have_person_detail_priv(detail_type_id, person_id); + +create rule ii_person_details as +on insert to person_details +do instead + insert into hidden.person_details + (person_id, detail_type_id, value) + select new.person_id, new.detail_type_id, new.value + where i_have_personal_priv(10038, new.person_id) + and i_have_person_detail_priv(new.detail_type_id, new.person_id); + +create rule iu_person_details as +on update to person_details +do instead + update hidden.person_details + set person_id = new.person_id, + detail_type_id = new.detail_type_id, + value = new.value + where person_id = old.person_id + and detail_type_id = old.detail_type_id + and i_have_personal_priv(10039, old.person_id) + and i_have_person_detail_priv(old.detail_type_id, old.person_id); + +create rule id_person_details as +on delete to person_details +do instead + delete from hidden.person_details + where person_id = old.person_id + and detail_type_id = old.detail_type_id + and i_have_personal_priv(10040, old.person_id) + and i_have_person_detail_priv(old.detail_type_id, old.person_id); + + +grant select, insert, update, delete on person_details to public; + + +create or replace +function global_privs_qry() returns setof int4 as ' +select * from veil_bitmap_bits(''global_context''); +' language sql +stable security definer; + +create or replace view my_global_privs ( + privilege_id) as +select * from global_privs_qry(); + +grant select on my_global_privs to public; + + +create or replace +function personal_privs_qry() returns setof int4 as ' +select * from veil_bitmap_array_bits(''role_privs'', 11002); +' language sql +stable security definer; + +create or replace view my_personal_privs ( + privilege_id) as +select * from personal_privs_qry(); + +grant select on my_personal_privs to public; + + +create or replace +function projects_qry() returns setof varchar as ' +select * from veil_bitmap_hash_entries(''project_context''); +' language sql +stable security definer; + +create or replace view my_projects ( + project_id) as +select * from projects_qry(); + +grant select on my_projects to public; + + +create type hidden.proj_privs as ( + project_id int4, + privilege_id int4 +); + +create or replace +function project_privs() returns setof hidden.proj_privs as ' +declare + _project record; + _priv record; + _result hidden.proj_privs; +begin + for _project in + select * from veil_bitmap_hash_entries(''project_context'') + loop + _result.project_id = _project.veil_bitmap_hash_entries; + for _priv in + select * from veil_bitmap_hash_bits(''project_context'', + _result.project_id) + loop + _result.privilege_id = _priv.veil_bitmap_hash_bits; + return next _result; + end loop; + end loop; + return; +end; +' language plpgsql +stable security definer; + +grant execute on function project_privs() to public; + +create or replace view my_project_privs ( + project_id, + privilege_id) as +select * from project_privs(); + +grant select on my_project_privs to public; + + +create or replace view my_privs ( + context, + project, + privilege_id, + privilege_name) +as +select a.context, a.project, a.id, p.privilege_name +from ( + select 'Project' as context, project_id as project, + privilege_id as id, 3 as seq + from my_project_privs + union all + select 'Global', null, privilege_id, 1 + from my_global_privs + union all + select 'Personal', null, privilege_id, 2 + from my_personal_privs + ) a, + privileges p +where p.privilege_id = a.id +order by a.seq, a.context, a.project, a.id; + +grant select on my_privs to public; diff --git a/veil_demo.control b/veil_demo.control new file mode 100644 index 0000000..94ddf30 --- /dev/null +++ b/veil_demo.control @@ -0,0 +1,16 @@ +# veil-demo.control +# +# Postgres extension control file for veil_demo +# +# Copyright (c) 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# + +directory = 'extension' +default_version = '1.0' +superuser = true +relocatable = false +requires = veil + +comment = 'A demonstration application for veil' diff --git a/veil_demo.mk b/veil_demo.mk new file mode 100644 index 0000000..f384445 --- /dev/null +++ b/veil_demo.mk @@ -0,0 +1,16 @@ +# GNUmakefile +# +# PGXS-based makefile for veil_demo +# +# Copyright (c) 2005 - 2011 Marc Munro +# Author: Marc Munro +# License: BSD +# +# For a list of targets use make help. +# + +EXTENSION=veil_demo +DATA = $(wildcard veil_demo--*.sql) +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) -- 2.39.5