diff options
author | Marc G. Fournier | 1996-07-09 06:22:35 +0000 |
---|---|---|
committer | Marc G. Fournier | 1996-07-09 06:22:35 +0000 |
commit | d31084e9d1118b25fd16580d9d8c2924b5740dff (patch) | |
tree | 3179e66307d54df9c7b966543550e601eb55e668 |
Postgres95 1.01 Distribution - Virgin SourcesPG95-1_01
868 files changed, 242656 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 00000000000..7e047c0cce8 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,48 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Build and install postgres. +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/Makefile,v 1.1.1.1 1996/07/09 06:21:07 scrappy Exp $ +# +# NOTES +# objdir - location of the objects and generated files (eg. obj) +# +#------------------------------------------------------------------------- + +SUBDIR= backend libpq bin + +FIND = find +# assuming gnu tar and split here +TAR = tar +SPLIT = split + +ETAGS = etags +XARGS = xargs + +ifeq ($(USE_TCL), true) +SUBDIR += libpgtcl +endif + +include mk/postgres.subdir.mk + +TAGS: + rm -f TAGS; \ + for i in backend libpq bin; do \ + $(FIND) $$i -name '*.[chyl]' -print | $(XARGS) $(ETAGS) -a ; \ + done + +# target to generate a backup tar file and split files that can be +# saved to 1.44M floppy +BACKUP: + rm -f BACKUP.filelist BACKUP.tgz; \ + $(FIND) . -not -path '*obj/*' -not -path '*data/*' -type f -print > BACKUP.filelist; \ + $(TAR) --files-from BACKUP.filelist -c -z -v -f BACKUP.tgz + $(SPLIT) --bytes=1400k BACKUP.tgz pgBACKUP. + +.PHONY: TAGS +.PHONY: BACKUP diff --git a/src/Makefile.global b/src/Makefile.global new file mode 100644 index 00000000000..1ecd62accef --- /dev/null +++ b/src/Makefile.global @@ -0,0 +1,306 @@ +#------------------------------------------------------------------------- +# +# Makefile.global-- +# global configuration for the Makefiles +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/Attic/Makefile.global,v 1.1.1.1 1996/07/09 06:21:07 scrappy Exp $ +# +# NOTES +# This is seen by any Makefiles that include mk/postgres.mk. To +# override the default setting, create a Makefile.custom in this +# directory and put your defines there. (Makefile.custom is included +# at the end of this file.) +# +# If you change any of these defines you probably have to +# gmake clean; gmake +# since no dependecies are created for these. (of course you can +# be crafty and check what files really depend on them and just remake +# those). +# +#------------------------------------------------------------------------- + + +############################################################################## +# +# CONFIGURATION SECTION +# +# Following are settings pertaining to the postgres build and +# installation. The most important one is obviously the name +# of the port. + +# The name of the port. Valid choices are: +# alpha - DEC Alpha AXP on OSF/1 2.0 +# hpux - HP PA-RISC on HP-UX 9.0 +# sparc_solaris - SUN SPARC on Solaris 2.4 +# sparc - SUN SPARC on SunOS 4.1.3 +# ultrix4 - DEC MIPS on Ultrix 4.4 +# linux - Intel x86 on Linux 1.2 and Linux ELF +# (For non-ELF Linux, you need to comment out +# "LINUX_ELF=1" in src/mk/port/postgres.mk.linux) +# BSD44_derived - OSs derived from 4.4-lite BSD (NetBSD, FreeBSD) +# bsdi - BSD/OS 2.0 and 2.01 +# aix - IBM on AIX 3.2.5 +# irix5 - SGI MIPS on IRIX 5.3 +# Some hooks are provided for +# svr4 - Intel x86 on Intel SVR4 +# next - Motorola MC68K or Intel x86 on NeXTSTEP 3.2 +# but these are guaranteed not to work as of yet. +# +# XXX Note that you MUST set PORTNAME here (or on the command line) so +# that port-dependent variables are correctly set within this file. +# Makefile.custom does not take effect (for ifeq purposes) +# until after this file is processed! +# make sure that you have no whitespaces after the PORTNAME setting +# or the makefiles can get confused +PORTNAME= alpha + +# POSTGRESLOGIN is the login name of the user who gets special +# privileges within the database. By default it is "postgres", but +# you can change it to any existing login name (such as your own +# login if you are compiling a private version or don't have root +# access). +POSTGRESLOGIN= postgres + +# For convenience, POSTGRESDIR is where DATADIR, BINDIR, and LIBDIR +# and other target destinations are rooted. Of course, each of these is +# changable separately. +POSTGRESDIR= /private/postgres95 + +# SRCDIR specifies where the source files are. +SRCDIR= $(POSTGRESDIR)/src + +# DATADIR specifies where the postmaster expects to find its database. +# This may be overridden by command line options or the PGDATA environment +# variable. +DATADIR= $(POSTGRESDIR)/data + +# Where the postgres executables live (changeable by just putting them +# somewhere else and putting that directory in your shell PATH) +BINDIR= $(POSTGRESDIR)/bin + +# Where libpq.a gets installed. You must put it where your loader will +# look for it if you wish to use the -lpq convention. Otherwise you +# can just put the absolute pathname to the library at the end of your +# command line. +LIBDIR= $(POSTGRESDIR)/lib + +# This is the directory where IPC utilities ipcs and ipcrm are located +# +IPCSDIR= /usr/bin + +# Where the man pages (suitable for use with "man") get installed. +POSTMANDIR= $(POSTGRESDIR)/man + +# Where the formatted documents (e.g., the reference manual) get installed. +POSTDOCDIR= $(POSTGRESDIR)/doc + +# Where the header files necessary to build frontend programs get installed. +HEADERDIR= $(POSTGRESDIR)/include + +# NAMEDATALEN is the max length for system identifiers (e.g. table names, +# attribute names, function names, etc.) +# +# These MUST be set here. DO NOT COMMENT THESE OUT +# Setting these too high will result in excess space usage for system catalogs +# Setting them too low will make the system unusable. +# values between 16 and 64 that are multiples of four are recommended. +# +# NOTE also that databases with different NAMEDATALEN's cannot interoperate! +# +NAMEDATALEN = 32 +# OIDNAMELEN should be set to NAMEDATALEN + sizeof(Oid) +OIDNAMELEN = 36 + +CFLAGS+= -DNAMEDATALEN=$(NAMEDATALEN) -DOIDNAMELEN=$(OIDNAMELEN) + +############################################################################## +# +# FEATURES +# +# To disable a feature, comment out the entire definition +# (that is, prepend '#', don't set it to "0" or "no"). + +# Comment out ENFORCE_ALIGNMENT if you do NOT want unaligned access to +# multi-byte types to generate a bus error. +ENFORCE_ALIGNMENT= true + +# Comment out CDEBUG to turn off debugging and sanity-checking. +# +# XXX on MIPS, use -g3 if you want to compile with -O +CDEBUG= -g + +# turn this on if you prefer European style dates instead of American +# style dates +# EUROPEAN_DATES = 1 + +# Comment out PROFILE to disable profiling. +# +# XXX define on MIPS if you want to be able to use pixie. +# note that this disables dynamic loading! +#PROFILE= -p -non_shared + +# About the use of readline in psql: +# psql does not require the GNU readline and history libraries. Hence, we +# do not compile with them by default. However, there are hooks in the +# program which supports the use of GNU readline and history. Should you +# decide to use them, change USE_READLINE to true and change READLINE_INCDIR +# and READLINE_LIBDIR to reflect the location of the readline and histroy +# headers and libraries. +# +#USE_READLINE= true + +# directories for the readline and history libraries. +READLINE_INCDIR= /usr/local/include +HISTORY_INCDIR= /usr/local/include +READLINE_LIBDIR= /usr/local/lib +HISTORY_LIBDIR= /usr/local/lib + +# If you do not plan to use Host based authentication, +# comment out the following line +HBA = 1 + +ifdef HBA +HBAFLAGS= -DHBA +endif + + + +# If you plan to use Kerberos for authentication... +# +# Comment out KRBVERS if you do not use Kerberos. +# Set KRBVERS to "4" for Kerberos v4, "5" for Kerberos v5. +# XXX Edit the default Kerberos variables below! +# +#KRBVERS= 5 + + +# Globally pass Kerberos file locations. +# these are used in the postmaster and all libpq applications. +# +# Adjust KRBINCS and KRBLIBS to reflect where you have Kerberos +# include files and libraries installed. +# PG_KRB_SRVNAM is the name under which POSTGRES is registered in +# the Kerberos database (KDC). +# PG_KRB_SRVTAB is the location of the server's keytab file. +# +ifdef KRBVERS +KRBINCS= -I/usr/athena/include +KRBLIBS= -L/usr/athena/lib +KRBFLAGS+= $(KRBINCS) -DPG_KRB_SRVNAM='"postgres_dbms"' + ifeq ($(KRBVERS), 4) +KRBFLAGS+= -DKRB4 +KRBFLAGS+= -DPG_KRB_SRVTAB='"/etc/srvtab"' +KRBLIBS+= -lkrb -ldes + else + ifeq ($(KRBVERS), 5) +KRBFLAGS+= -DKRB5 +KRBFLAGS+= -DPG_KRB_SRVTAB='"FILE:/krb5/srvtab.postgres"' +KRBLIBS+= -lkrb5 -lcrypto -lcom_err -lisode + endif + endif +endif + +# +# location of Tcl/Tk headers and libraries +# +# Uncomment this to build the tcl utilities. +USE_TCL= true +# customize these to your site's needs +# +TCL_INCDIR= /usr/local/devel/tcl7.4/include +TCL_LIBDIR= /usr/local/devel/tcl7.4/lib +TCL_LIB = -ltcl7.4 +TK_INCDIR= /usr/local/devel/tk4.0/include +TK_LIBDIR= /usr/local/devel/tk4.0/lib +TK_LIB = -ltk4.0 + +# +# include port specific rules and variables. For instance: +# +# signal(2) handling - this is here because it affects some of +# the frontend commands as well as the backend server. +# +# Ultrix and SunOS provide BSD signal(2) semantics by default. +# +# SVID2 and POSIX signal(2) semantics differ from BSD signal(2) +# semantics. We can use the POSIX sigaction(2) on systems that +# allow us to request restartable signals (SA_RESTART). +# +# Some systems don't allow restartable signals at all unless we +# link to a special BSD library. +# +# We devoutly hope that there aren't any systems that provide +# neither POSIX signals nor BSD signals. The alternative +# is to do signal-handler reinstallation, which doesn't work well +# at all. +# +-include $(MKDIR)/port/postgres.mk.$(PORTNAME) + +############################################################################## +# +# Flags for CC and LD. (depend on CDEBUG and PROFILE) +# + +# Globally pass debugging/optimization/profiling flags based +# on the options selected above. +ifdef CDEBUG + CFLAGS+= $(CDEBUG) + LDFLAGS+= $(CDEBUG) +else + ifndef CFLAGS_OPT + CFLAGS_OPT= -O + endif + CFLAGS+= $(CFLAGS_OPT) +# +# Uncommenting this will make things go a LOT faster, but you will +# also lose a lot of useful error-checking. +# + CFLAGS+= -DNO_ASSERT_CHECKING +endif + +ifdef PROFILE +CFLAGS+= $(PROFILE) +LDFLAGS+= $(PROFILE) +endif + +# Globally pass PORTNAME +CFLAGS+= -DPORTNAME_$(PORTNAME) + +# Globally pass the default TCP port for postmaster(1). +CFLAGS+= -DPOSTPORT='"5432"' + +# include flags from mk/port/postgres.mk.$(PORTNAME) +CFLAGS+= $(CFLAGS_BE) +LDADD+= $(LDADD_BE) +LDFLAGS+= $(LDFLAGS_BE) + + +############################################################################## +# +# Miscellaneous configuration +# + +# This is the time, in seconds, at which a given backend server +# will wait on a lock before deciding to abort the transaction +# (this is what we do in lieu of deadlock detection). +# +# Low numbers are not recommended as they will tend to cause +# false aborts if many transactions are long-lived. +CFLAGS+= -DDEADLOCK_TIMEOUT=60 + +srcdir= $(SRCDIR) +includedir= $(HEADERDIR) +objdir= obj + + +############################################################################## +# +# Customization. +# +-include $(MKDIR)/../Makefile.custom + + diff --git a/src/backend/Makefile b/src/backend/Makefile new file mode 100644 index 00000000000..4cdc7adaf43 --- /dev/null +++ b/src/backend/Makefile @@ -0,0 +1,289 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for the postgres backend (and the postmaster) +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/Makefile,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ +# +#------------------------------------------------------------------------- + +# +# The following turns on intermediate linking of partial objects to speed +# the link cycle during development. (To turn this off, put "BIGOBJS=false" +# in your custom makefile, ../Makefile.custom.) +BIGOBJS= true + + +PROG= postgres + +MKDIR= ../mk +include $(MKDIR)/postgres.mk + + +include $(CURDIR)/access/Makefile.inc +include $(CURDIR)/bootstrap/Makefile.inc +include $(CURDIR)/catalog/Makefile.inc +include $(CURDIR)/commands/Makefile.inc +include $(CURDIR)/executor/Makefile.inc +include $(CURDIR)/include/Makefile.inc +include $(CURDIR)/lib/Makefile.inc +include $(CURDIR)/libpq/Makefile.inc +include $(CURDIR)/main/Makefile.inc +include $(CURDIR)/nodes/Makefile.inc +include $(CURDIR)/optimizer/Makefile.inc +include $(CURDIR)/parser/Makefile.inc +include $(CURDIR)/port/Makefile.inc +include $(CURDIR)/postmaster/Makefile.inc +include $(CURDIR)/regex/Makefile.inc +include $(CURDIR)/rewrite/Makefile.inc +include $(CURDIR)/storage/Makefile.inc +include $(CURDIR)/tcop/Makefile.inc +include $(CURDIR)/tioga/Makefile.inc +include $(CURDIR)/utils/Makefile.inc + +SRCS:= ${SRCS_ACCESS} ${SRCS_BOOTSTRAP} $(SRCS_CATALOG) ${SRCS_COMMANDS} \ + ${SRCS_EXECUTOR} $(SRCS_LIB) $(SRCS_LIBPQ) ${SRCS_MAIN} \ + ${SRCS_NODES} ${SRCS_OPTIMIZER} ${SRCS_PARSER} ${SRCS_PORT} \ + $(SRCS_POSTMASTER) ${SRCS_REGEX} ${SRCS_REWRITE} ${SRCS_STORAGE} \ + ${SRCS_TCOP} ${SRCS_UTILS} + +ifeq ($(BIGOBJS), true) +OBJS= ACCESS.o BOOTSTRAP.o COMMANDS.o EXECUTOR.o MAIN.o MISC.o NODES.o \ + PARSER.o OPTIMIZER.o REGEX.o REWRITE.o STORAGE.o TCOP.o UTILS.o +CLEANFILES+= $(subst .s,.o,$(SRCS:.c=.o)) $(OBJS) +else +OBJS:= $(subst .s,.o,$(SRCS:%.c=$(objdir)/%.o)) +CLEANFILES+= $(notdir $(OBJS)) +endif + +############################################################################# +# +# TIOGA stuff +# +ifdef TIOGA +SRCS+= $(SRCS_TIOGA) + ifeq ($(BIGOBJS), true) +TIOGA.o: $(SRCS_TIOGA:%.c=$(objdir)/%.o) + $(make_partial) +OBJS+= TIOGA.o +CLEANFILES+= $(SRCS_TIOGA:%.c=%.o) TIOGA.o + else +OBJS+= $(SRCS_TIOGA:%.c=$(objdir)/%.o) + endif +endif + + +############################################################################# +# +# Compiling the postgres backend. +# +CFLAGS+= -DPOSTGRESDIR='"$(POSTGRESDIR)"' \ + -DPGDATADIR='"$(DATADIR)"' \ + -I$(CURDIR)/. -I$(CURDIR)/$(objdir) \ + -I$(CURDIR)/include \ + -I$(CURDIR)/port/$(PORTNAME) + +# turn this on if you prefer European style dates instead of American +# style dates +ifdef EUROPEAN_DATES +CFLAGS += -DEUROPEAN_STYLE +endif + +# kerberos flags +ifdef KRBVERS +CFLAGS+= $(KRBFLAGS) +LDADD+= $(KRBLIBS) +endif + +# host based access flags +ifdef HBA +CFLAGS+= $(HBAFLAGS) +endif + + + +# +# All systems except NEXTSTEP require the math library. +# Loader flags for system-dependent libraries are appended in +# src/backend/port/$(PORTNAME)/Makefile.inc +# +ifneq ($(PORTNAME), next) +LDADD+= -lm +endif + +# statically link in libc for linux +ifeq ($(PORTNAME), linux) +LDADD+= -lc +endif + +postgres: $(POSTGRES_DEPEND) $(OBJS) $(EXPORTS) + $(CC) $(LDFLAGS) -o $(objdir)/$(@F) $(addprefix $(objdir)/,$(notdir $(OBJS))) $(LDADD) + +# Make this target first if you are doing a parallel make. +# The targets in 'first' need to be made sequentially because of dependencies. +# Then, you can make 'all' with parallelism turned on. +first: $(POSTGRES_DEPEND) + + +############################################################################# +# +# Partial objects for platforms with slow linkers. +# +ifeq ($(BIGOBJS), true) + +OBJS_ACCESS:= $(SRCS_ACCESS:%.c=$(objdir)/%.o) +OBJS_BOOTSTRAP:= $(SRCS_BOOTSTRAP:%.c=$(objdir)/%.o) +OBJS_CATALOG:= $(SRCS_CATALOG:%.c=$(objdir)/%.o) +OBJS_COMMANDS:= $(SRCS_COMMANDS:%.c=$(objdir)/%.o) +OBJS_EXECUTOR:= $(SRCS_EXECUTOR:%.c=$(objdir)/%.o) +OBJS_MAIN:= $(SRCS_MAIN:%.c=$(objdir)/%.o) +OBJS_POSTMASTER:= $(SRCS_POSTMASTER:%.c=$(objdir)/%.o) +OBJS_LIB:= $(SRCS_LIB:%.c=$(objdir)/%.o) +OBJS_LIBPQ:= $(SRCS_LIBPQ:%.c=$(objdir)/%.o) +OBJS_PORT:= $(addprefix $(objdir)/,$(subst .s,.o,$(SRCS_PORT:.c=.o))) +OBJS_NODES:= $(SRCS_NODES:%.c=$(objdir)/%.o) +OBJS_PARSER:= $(SRCS_PARSER:%.c=$(objdir)/%.o) +OBJS_OPTIMIZER:= $(SRCS_OPTIMIZER:%.c=$(objdir)/%.o) +OBJS_REGEX:= $(SRCS_REGEX:%.c=$(objdir)/%.o) +OBJS_REWRITE:= $(SRCS_REWRITE:%.c=$(objdir)/%.o) +OBJS_STORAGE:= $(SRCS_STORAGE:%.c=$(objdir)/%.o) +OBJS_TCOP:= $(SRCS_TCOP:%.c=$(objdir)/%.o) +OBJS_UTILS:= $(SRCS_UTILS:%.c=$(objdir)/%.o) + +ACCESS.o: $(OBJS_ACCESS) + $(make_partial) +BOOTSTRAP.o: $(OBJS_BOOTSTRAP) + $(make_partial) +COMMANDS.o: $(OBJS_COMMANDS) + $(make_partial) +EXECUTOR.o: $(OBJS_EXECUTOR) + $(make_partial) +MAIN.o: $(OBJS_MAIN) $(OBJS_POSTMASTER) + $(make_partial) +MISC.o: $(OBJS_CATALOG) $(OBJS_LIB) $(OBJS_LIBPQ) $(OBJS_PORT) + $(make_partial) +NODES.o: $(OBJS_NODES) + $(make_partial) +PARSER.o: $(OBJS_PARSER) + $(make_partial) +OPTIMIZER.o: $(OBJS_OPTIMIZER) + $(make_partial) +REGEX.o: $(OBJS_REGEX) + $(make_partial) +REWRITE.o: $(OBJS_REWRITE) + $(make_partial) +STORAGE.o: $(OBJS_STORAGE) + $(make_partial) +TCOP.o: $(OBJS_TCOP) + $(make_partial) +UTILS.o: $(OBJS_UTILS) + $(make_partial) +endif + +############################################################################# +# +# Installation. +# +# Install the bki files to the data directory. We also copy a version +# of them that has "PGUID" intact, so one can change the value of the +# postgres userid before running initdb in the case of customizing the +# binary release (i.e., fixing up PGUID w/o recompiling the system). +# Those files are copied out as foo.source. The program newbki(1) can +# be run later to reset the postgres login id (but it must be run before +# initdb is run, or after clearing the data directory with +# cleardbdir(1)). [newbki distributed with v4r2 but not with Postgres95.] +# + +# NAMEDATALEN=`egrep "^#define NAMEDATALEN" $(CURDIR)/include/postgres.h | awk '{print $$3}'`; \ +# OIDNAMELEN=`egrep "^#define OIDNAMELEN" $(CURDIR)/include/postgres.h | awk '{print $$3}'`; \ + +install: beforeinstall pg_id $(BKIFILES) postgres + $(INSTALL) $(INSTL_EXE_OPTS) $(objdir)/postgres $(DESTDIR)$(BINDIR)/postgres + @rm -f $(DESTDIR)$(BINDIR)/postmaster + cd $(DESTDIR)$(BINDIR); ln -s postgres postmaster + @cd $(objdir); \ + PG_UID=`./pg_id $(POSTGRESLOGIN)`; \ + POSTGRESLOGIN=$(POSTGRESLOGIN);\ + echo "NAMEDATALEN = $(NAMEDATALEN)"; \ + echo "OIDNAMELEN = $(OIDNAMELEN)"; \ + case $$PG_UID in "NOUSER") \ + echo "Warning: no account named $(POSTGRESLOGIN), using yours";\ + POSTGRESLOGIN=`whoami`; \ + PG_UID=`./pg_id`;; \ + esac ;\ + for bki in $(BKIFILES); do \ + sed \ + -e "s/postgres PGUID/$$POSTGRESLOGIN $$PG_UID/" \ + -e "s/NAMEDATALEN/$(NAMEDATALEN)/g" \ + -e "s/OIDNAMELEN/$(OIDNAMELEN)/g" \ + -e "s/PGUID/$$PG_UID/" \ + < $$bki > $$bki.sed ; \ + echo "Installing $(DESTDIR)$(DATADIR)/files/$$bki."; \ + $(INSTALL) $(INSTLOPTS) \ + $$bki.sed $(DESTDIR)$(DATADIR)/files/$$bki; \ + rm -f $$bki.sed; \ + echo "Installing $(DESTDIR)$(DATADIR)/files/$$bki.source."; \ + $(INSTALL) $(INSTLOPTS) \ + $$bki $(DESTDIR)$(DATADIR)/files/$$bki.source; \ + done; + @echo "Installing $(DATADIR)/pg_hba"; + @cp $(srcdir)/libpq/pg_hba $(DATADIR) + @chmod 644 $(DATADIR)/pg_hba + + +# so we can get the UID of the postgres owner (w/o moving pg_id to +# src/tools). We just want the vanilla LDFLAGS for pg_id +IDLDFLAGS:= $(LDFLAGS) +ifeq ($(PORTNAME), hpux) +ifeq ($(CC), cc) +IDLDFLAGS+= -Aa -D_HPUX_SOURCE +endif +endif +pg_id: $(srcdir)/bin/pg_id/pg_id.c + $(CC) $(IDLDFLAGS) -o $(objdir)/$(@F) $< + +CLEANFILES+= pg_id postgres + + +############################################################################# +# +# Support for code development. +# + +# +# Build the file, "./ID", used by the "gid" (grep-for-identifier) tool +# +IDFILE= ID +.PHONY: $(IDFILE) +$(IDFILE): + $(CURDIR)/makeID $(PORTNAME) + +# +# Special rule to generate cpp'd version of a .c file. This is +# especially useful given all the hellish macro processing going on. +# The cpp'd version has a .C suffix. To create foo.C from foo.c, just +# type +# bmake foo.C +# +%.cpp: %.c + $(CC) -E $(CFLAGS) $(<:.C=.c) | cat -s | cb | tr -s '\012*' '\012' > $(objdir)/$(@F) + +cppall: $(SRCS:.c=.cpp) + +# +# To use Purify (SunOS only), define PURIFY to be the path (and +# options) with which to invoke the Purify loader. Only the executable +# needs to be loaded with Purify. +# +# PURIFY = /usr/sww/bin/purify -cache-dir=/usr/local/postgres/src/backend/purify-cache +#.if defined(PURIFY) +#${PROG}: $(POSTGRES_DEPEND) $(OBJS) $(EXPORTS) +# ${PURIFY} ${CC} ${LDFLAGS} -o $(objdir)/$(@F) $(addprefix $(objdir)/,$(notdir $(OBJS))) $(LDADD) +# +#CLEANFILES+= .purify* .pure .lock.*.o *_pure_*.o *.pure_*link* +#.endif + diff --git a/src/backend/access/Makefile.inc b/src/backend/access/Makefile.inc new file mode 100644 index 00000000000..6adc2c692b5 --- /dev/null +++ b/src/backend/access/Makefile.inc @@ -0,0 +1,35 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for the access methods module +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/access/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ +# +#------------------------------------------------------------------------- + +accdir=$(CURDIR)/access +VPATH:=$(VPATH):$(accdir):\ + $(accdir)/common:$(accdir)/hash:$(accdir)/heap:$(accdir)/index:\ + $(accdir)/rtree:$(accdir)/nbtree:$(accdir)/transam + + +SUBSRCS= +include $(accdir)/common/Makefile.inc +include $(accdir)/hash/Makefile.inc +include $(accdir)/heap/Makefile.inc +include $(accdir)/index/Makefile.inc +include $(accdir)/rtree/Makefile.inc +include $(accdir)/nbtree/Makefile.inc +include $(accdir)/transam/Makefile.inc +SRCS_ACCESS:= $(SUBSRCS) + +HEADERS+= attnum.h funcindex.h genam.h hash.h \ + heapam.h hio.h htup.h ibit.h iqual.h istrat.h \ + itup.h nbtree.h printtup.h relscan.h rtree.h \ + sdir.h skey.h strat.h transam.h tupdesc.h tupmacs.h \ + valid.h xact.h + diff --git a/src/backend/access/attnum.h b/src/backend/access/attnum.h new file mode 100644 index 00000000000..7c999e58e9d --- /dev/null +++ b/src/backend/access/attnum.h @@ -0,0 +1,61 @@ +/*------------------------------------------------------------------------- + * + * attnum.h-- + * POSTGRES attribute number definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: attnum.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef ATTNUM_H +#define ATTNUM_H + +#include "c.h" + +/* + * user defined attribute numbers start at 1. -ay 2/95 + */ +typedef int16 AttrNumber; + +#define InvalidAttrNumber 0 + +/* ---------------- + * support macros + * ---------------- + */ +/* + * AttributeNumberIsValid -- + * True iff the attribute number is valid. + */ +#define AttributeNumberIsValid(attributeNumber) \ + ((bool) ((attributeNumber) != InvalidAttrNumber)) + +/* + * AttrNumberIsForUserDefinedAttr -- + * True iff the attribute number corresponds to an user defined attribute. + */ +#define AttrNumberIsForUserDefinedAttr(attributeNumber) \ + ((bool) ((attributeNumber) > 0)) + +/* + * AttrNumberGetAttrOffset -- + * Returns the attribute offset for an attribute number. + * + * Note: + * Assumes the attribute number is for an user defined attribute. + */ +#define AttrNumberGetAttrOffset(attNum) \ + (AssertMacro(AttrNumberIsForUserDefinedAttr(attNum)) ? \ + ((attNum - 1)) : 0) + +/* + * AttributeOffsetGetAttributeNumber -- + * Returns the attribute number for an attribute offset. + */ +#define AttrOffsetGetAttrNumber(attributeOffset) \ + ((AttrNumber) (1 + attributeOffset)) + +#endif /* ATTNUM_H */ diff --git a/src/backend/access/common/Makefile.inc b/src/backend/access/common/Makefile.inc new file mode 100644 index 00000000000..5d5dd476274 --- /dev/null +++ b/src/backend/access/common/Makefile.inc @@ -0,0 +1,16 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for access/common +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/access/common/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ +# +#------------------------------------------------------------------------- + +SUBSRCS+= heaptuple.c heapvalid.c indextuple.c indexvalid.c printtup.c \ + scankey.c tupdesc.c + diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c new file mode 100644 index 00000000000..c3e72fb97e8 --- /dev/null +++ b/src/backend/access/common/heaptuple.c @@ -0,0 +1,1011 @@ +/*------------------------------------------------------------------------- + * + * heaptuple.c-- + * This file contains heap tuple accessor and mutator routines, as well + * as a few various tuple utilities. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/common/heaptuple.c,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + * NOTES + * The old interface functions have been converted to macros + * and moved to heapam.h + * + *------------------------------------------------------------------------- + */ +#include <string.h> + +#include "postgres.h" + +#include "access/htup.h" +#include "access/itup.h" +#include "access/tupmacs.h" +#include "access/skey.h" +#include "storage/ipc.h" +#include "storage/buf.h" +#include "storage/bufmgr.h" +#include "access/transam.h" +#include "storage/bufpage.h" /* for MAXTUPLEN */ +#include "storage/itemptr.h" +#include "utils/memutils.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/nabstime.h" + +/* this is so the sparcstation debugger works */ + +#ifndef NO_ASSERT_CHECKING +#ifdef sparc +#define register +#endif /* sparc */ +#endif /* NO_ASSERT_CHECKING */ + +/* ---------------------------------------------------------------- + * misc support routines + * ---------------------------------------------------------------- + */ + +/* ---------------- + * ComputeDataSize + * ---------------- + */ +Size +ComputeDataSize(TupleDesc tupleDesc, + Datum value[], + char nulls[]) +{ + uint32 length; + int i; + int numberOfAttributes = tupleDesc->natts; + AttributeTupleForm *att = tupleDesc->attrs; + + for (length = 0, i = 0; i < numberOfAttributes; i++) { + if (nulls[i] != ' ') continue; + + switch (att[i]->attlen) { + case -1: + /* + * This is the size of the disk representation and so + * must include the additional sizeof long. + */ + if (att[i]->attalign == 'd') { + length = DOUBLEALIGN(length) + + VARSIZE(DatumGetPointer(value[i])); + } else { + length = INTALIGN(length) + + VARSIZE(DatumGetPointer(value[i])); + } + break; + case sizeof(char): + length++; + break; + case sizeof(short): + length = SHORTALIGN(length + sizeof(short)); + break; + case sizeof(int32): + length = INTALIGN(length + sizeof(int32)); + break; + default: + if (att[i]->attlen < sizeof(int32)) + elog(WARN, "ComputeDataSize: attribute %d has len %d", + i, att[i]->attlen); + if (att[i]->attalign == 'd') + length = DOUBLEALIGN(length) + att[i]->attlen; + else + length = LONGALIGN(length) + att[i]->attlen; + break; + } + } + + return length; +} + +/* ---------------- + * DataFill + * ---------------- + */ +void +DataFill(char *data, + TupleDesc tupleDesc, + Datum value[], + char nulls[], + char *infomask, + bits8 bit[]) +{ + bits8 *bitP; + int bitmask; + uint32 length; + int i; + int numberOfAttributes = tupleDesc->natts; + AttributeTupleForm* att = tupleDesc->attrs; + + if (bit != NULL) { + bitP = &bit[-1]; + bitmask = CSIGNBIT; + } + + *infomask = 0; + + for (i = 0; i < numberOfAttributes; i++) { + if (bit != NULL) { + if (bitmask != CSIGNBIT) { + bitmask <<= 1; + } else { + bitP += 1; + *bitP = 0x0; + bitmask = 1; + } + + if (nulls[i] == 'n') { + *infomask |= HEAP_HASNULL; + continue; + } + + *bitP |= bitmask; + } + + switch (att[i]->attlen) { + case -1: + *infomask |= HEAP_HASVARLENA; + if (att[i]->attalign=='d') { + data = (char *) DOUBLEALIGN(data); + } else { + data = (char *) INTALIGN(data); + } + length = VARSIZE(DatumGetPointer(value[i])); + memmove(data, DatumGetPointer(value[i]),length); + data += length; + break; + case sizeof(char): + *data = att[i]->attbyval ? + DatumGetChar(value[i]) : *((char *) value[i]); + data += sizeof(char); + break; + case sizeof(int16): + data = (char *) SHORTALIGN(data); + * (short *) data = (att[i]->attbyval ? + DatumGetInt16(value[i]) : + *((short *) value[i])); + data += sizeof(short); + break; + case sizeof(int32): + data = (char *) INTALIGN(data); + * (int32 *) data = (att[i]->attbyval ? + DatumGetInt32(value[i]) : + *((int32 *) value[i])); + data += sizeof(int32); + break; + default: + if (att[i]->attlen < sizeof(int32)) + elog(WARN, "DataFill: attribute %d has len %d", + i, att[i]->attlen); + if (att[i]->attalign == 'd') { + data = (char *) DOUBLEALIGN(data); + memmove(data, DatumGetPointer(value[i]), + att[i]->attlen); + data += att[i]->attlen; + } else { + data = (char *) LONGALIGN(data); + memmove(data, DatumGetPointer(value[i]), + att[i]->attlen); + data += att[i]->attlen; + } + + } + } +} + +/* ---------------------------------------------------------------- + * heap tuple interface + * ---------------------------------------------------------------- + */ + +/* ---------------- + * heap_attisnull - returns 1 iff tuple attribute is not present + * ---------------- + */ +int +heap_attisnull(HeapTuple tup, int attnum) +{ + if (attnum > (int)tup->t_natts) + return (1); + + if (HeapTupleNoNulls(tup)) return(0); + + if (attnum > 0) { + return(att_isnull(attnum - 1, tup->t_bits)); + } else + switch (attnum) { + case SelfItemPointerAttributeNumber: + case ObjectIdAttributeNumber: + case MinTransactionIdAttributeNumber: + case MinCommandIdAttributeNumber: + case MaxTransactionIdAttributeNumber: + case MaxCommandIdAttributeNumber: + case ChainItemPointerAttributeNumber: + case AnchorItemPointerAttributeNumber: + case MinAbsoluteTimeAttributeNumber: + case MaxAbsoluteTimeAttributeNumber: + case VersionTypeAttributeNumber: + break; + + case 0: + elog(WARN, "heap_attisnull: zero attnum disallowed"); + + default: + elog(WARN, "heap_attisnull: undefined negative attnum"); + } + + return (0); +} + +/* ---------------------------------------------------------------- + * system attribute heap tuple support + * ---------------------------------------------------------------- + */ + +/* ---------------- + * heap_sysattrlen + * + * This routine returns the length of a system attribute. + * ---------------- + */ +int +heap_sysattrlen(AttrNumber attno) +{ + HeapTupleData *f = NULL; + int len; + + switch (attno) { + case SelfItemPointerAttributeNumber: + len = sizeof f->t_ctid; + break; + case ObjectIdAttributeNumber: + len = sizeof f->t_oid; + break; + case MinTransactionIdAttributeNumber: + len = sizeof f->t_xmin; + break; + case MinCommandIdAttributeNumber: + len = sizeof f->t_cmin; + break; + case MaxTransactionIdAttributeNumber: + len = sizeof f->t_xmax; + break; + case MaxCommandIdAttributeNumber: + len = sizeof f->t_cmax; + break; + case ChainItemPointerAttributeNumber: + len = sizeof f->t_chain; + break; + case AnchorItemPointerAttributeNumber: + elog(WARN, "heap_sysattrlen: field t_anchor does not exist!"); + break; + case MinAbsoluteTimeAttributeNumber: + len = sizeof f->t_tmin; + break; + case MaxAbsoluteTimeAttributeNumber: + len = sizeof f->t_tmax; + break; + case VersionTypeAttributeNumber: + len = sizeof f->t_vtype; + break; + default: + elog(WARN, "sysattrlen: System attribute number %d unknown.", + attno); + len = 0; + break; + } + return (len); +} + +/* ---------------- + * heap_sysattrbyval + * + * This routine returns the "by-value" property of a system attribute. + * ---------------- + */ +bool +heap_sysattrbyval(AttrNumber attno) +{ + bool byval; + + switch (attno) { + case SelfItemPointerAttributeNumber: + byval = false; + break; + case ObjectIdAttributeNumber: + byval = true; + break; + case MinTransactionIdAttributeNumber: + byval = true; + break; + case MinCommandIdAttributeNumber: + byval = true; + break; + case MaxTransactionIdAttributeNumber: + byval = true; + break; + case MaxCommandIdAttributeNumber: + byval = true; + break; + case ChainItemPointerAttributeNumber: + byval = false; + break; + case AnchorItemPointerAttributeNumber: + byval = false; + break; + case MinAbsoluteTimeAttributeNumber: + byval = true; + break; + case MaxAbsoluteTimeAttributeNumber: + byval = true; + break; + case VersionTypeAttributeNumber: + byval = true; + break; + default: + byval = true; + elog(WARN, "sysattrbyval: System attribute number %d unknown.", + attno); + break; + } + + return byval; +} + +/* ---------------- + * heap_getsysattr + * ---------------- + */ +char * +heap_getsysattr(HeapTuple tup, Buffer b, int attnum) +{ + switch (attnum) { + case SelfItemPointerAttributeNumber: + return ((char *)&tup->t_ctid); + case ObjectIdAttributeNumber: + return ((char *) (long) tup->t_oid); + case MinTransactionIdAttributeNumber: + return ((char *) (long) tup->t_xmin); + case MinCommandIdAttributeNumber: + return ((char *) (long) tup->t_cmin); + case MaxTransactionIdAttributeNumber: + return ((char *) (long) tup->t_xmax); + case MaxCommandIdAttributeNumber: + return ((char *) (long) tup->t_cmax); + case ChainItemPointerAttributeNumber: + return ((char *) &tup->t_chain); + case AnchorItemPointerAttributeNumber: + elog(WARN, "heap_getsysattr: t_anchor does not exist!"); + break; + + /* + * For tmin and tmax, we need to do some extra work. These don't + * get filled in until the vacuum cleaner runs (or we manage to flush + * a page after setting the value correctly below). If the vacuum + * cleaner hasn't run yet, then the times stored in the tuple are + * wrong, and we need to look up the commit time of the transaction. + * We cache this value in the tuple to avoid doing the work more than + * once. + */ + + case MinAbsoluteTimeAttributeNumber: + if (!AbsoluteTimeIsBackwardCompatiblyValid(tup->t_tmin) && + TransactionIdDidCommit(tup->t_xmin)) + tup->t_tmin = TransactionIdGetCommitTime(tup->t_xmin); + return ((char *) (long) tup->t_tmin); + case MaxAbsoluteTimeAttributeNumber: + if (!AbsoluteTimeIsBackwardCompatiblyReal(tup->t_tmax)) { + if (TransactionIdDidCommit(tup->t_xmax)) + tup->t_tmax = TransactionIdGetCommitTime(tup->t_xmax); + else + tup->t_tmax = CURRENT_ABSTIME; + } + return ((char *) (long) tup->t_tmax); + case VersionTypeAttributeNumber: + return ((char *) (long) tup->t_vtype); + default: + elog(WARN, "heap_getsysattr: undefined attnum %d", attnum); + } + return(NULL); +} + +/* ---------------- + * fastgetattr + * + * This is a newer version of fastgetattr which attempts to be + * faster by caching attribute offsets in the attribute descriptor. + * + * an alternate way to speed things up would be to cache offsets + * with the tuple, but that seems more difficult unless you take + * the storage hit of actually putting those offsets into the + * tuple you send to disk. Yuck. + * + * This scheme will be slightly slower than that, but should + * preform well for queries which hit large #'s of tuples. After + * you cache the offsets once, examining all the other tuples using + * the same attribute descriptor will go much quicker. -cim 5/4/91 + * ---------------- + */ +char * +fastgetattr(HeapTuple tup, + int attnum, + TupleDesc tupleDesc, + bool *isnull) +{ + char *tp; /* ptr to att in tuple */ + bits8 *bp; /* ptr to att in tuple */ + int slow; /* do we have to walk nulls? */ + AttributeTupleForm *att = tupleDesc->attrs; + + /* ---------------- + * sanity checks + * ---------------- + */ + + Assert(PointerIsValid(isnull)); + Assert(attnum > 0); + + /* ---------------- + * Three cases: + * + * 1: No nulls and no variable length attributes. + * 2: Has a null or a varlena AFTER att. + * 3: Has nulls or varlenas BEFORE att. + * ---------------- + */ + + *isnull = false; + + if (HeapTupleNoNulls(tup)) { + attnum--; + if (att[attnum]->attcacheoff > 0) { + return (char *) + fetchatt( &(att[attnum]), + (char *)tup + tup->t_hoff + att[attnum]->attcacheoff); + } else if (attnum == 0) { + /* + * first attribute is always at position zero + */ + return((char *) fetchatt(&(att[0]), (char *) tup + tup->t_hoff)); + } + + tp = (char *) tup + tup->t_hoff; + + slow = 0; + } else { + /* + * there's a null somewhere in the tuple + */ + + bp = tup->t_bits; + tp = (char *) tup + tup->t_hoff; + slow = 0; + attnum--; + + /* ---------------- + * check to see if desired att is null + * ---------------- + */ + + if (att_isnull(attnum, bp)) { + *isnull = true; + return NULL; + } + + /* ---------------- + * Now check to see if any preceeding bits are null... + * ---------------- + */ + + { + register int i = 0; /* current offset in bp */ + + for (i = 0; i < attnum && !slow; i++) { + if (att_isnull(i, bp)) slow = 1; + } + } + } + + /* + * now check for any non-fixed length attrs before our attribute + */ + if (!slow) { + if (att[attnum]->attcacheoff > 0) { + return (char *) + fetchatt(&(att[attnum]), + tp + att[attnum]->attcacheoff); + } else if (attnum == 0) { + return (char *) + fetchatt(&(att[0]), (char *) tup + tup->t_hoff); + } else if (!HeapTupleAllFixed(tup)) { + register int j = 0; + + for (j = 0; j < attnum && !slow; j++) + if (att[j]->attlen < 1) slow = 1; + } + } + + /* + * if slow is zero, and we got here, we know that we have a tuple with + * no nulls. We also have to initialize the remainder of + * the attribute cached offset values. + */ + if (!slow) { + register int j = 1; + register long off; + + /* + * need to set cache for some atts + */ + + att[0]->attcacheoff = 0; + + while (att[j]->attcacheoff > 0) j++; + + off = att[j-1]->attcacheoff + att[j-1]->attlen; + + for (; j < attnum + 1; j++) { + switch(att[j]->attlen) { + case -1: + off = (att[j]->attalign=='d') ? + DOUBLEALIGN(off) : INTALIGN(off); + break; + case sizeof(char): + break; + case sizeof(short): + off = SHORTALIGN(off); + break; + case sizeof(int32): + off = INTALIGN(off); + break; + default: + if (att[j]->attlen < sizeof(int32)) { + elog(WARN, + "fastgetattr: attribute %d has len %d", + j, att[j]->attlen); + } + if (att[j]->attalign == 'd') + off = DOUBLEALIGN(off); + else + off = LONGALIGN(off); + break; + } + + att[j]->attcacheoff = off; + off += att[j]->attlen; + } + + return + (char *)fetchatt(&(att[attnum]), tp + att[attnum]->attcacheoff); + } else { + register bool usecache = true; + register int off = 0; + register int i; + + /* + * Now we know that we have to walk the tuple CAREFULLY. + * + * Note - This loop is a little tricky. On iteration i we + * first set the offset for attribute i and figure out how much + * the offset should be incremented. Finally, we need to align the + * offset based on the size of attribute i+1 (for which the offset + * has been computed). -mer 12 Dec 1991 + */ + + for (i = 0; i < attnum; i++) { + if (!HeapTupleNoNulls(tup)) { + if (att_isnull(i, bp)) { + usecache = false; + continue; + } + } + switch (att[i]->attlen) { + case -1: + off = (att[i]->attalign=='d') ? + DOUBLEALIGN(off) : INTALIGN(off); + break; + case sizeof(char): + break; + case sizeof(short): + off = SHORTALIGN(off); + break; + case sizeof(int32): + off = INTALIGN(off); + break; + default: + if (att[i]->attlen < sizeof(int32)) + elog(WARN, + "fastgetattr2: attribute %d has len %d", + i, att[i]->attlen); + if (att[i]->attalign == 'd') + off = DOUBLEALIGN(off); + else + off = LONGALIGN(off); + break; + } + if (usecache && att[i]->attcacheoff > 0) { + off = att[i]->attcacheoff; + if (att[i]->attlen == -1) { + usecache = false; + } + } else { + if (usecache) att[i]->attcacheoff = off; + } + + switch(att[i]->attlen) { + case sizeof(char): + off++; + break; + case sizeof(int16): + off += sizeof(int16); + break; + case sizeof(int32): + off += sizeof(int32); + break; + case -1: + usecache = false; + off += VARSIZE(tp + off); + break; + default: + off += att[i]->attlen; + break; + } + } + switch (att[attnum]->attlen) { + case -1: + off = (att[attnum]->attalign=='d')? + DOUBLEALIGN(off) : INTALIGN(off); + break; + case sizeof(char): + break; + case sizeof(short): + off = SHORTALIGN(off); + break; + case sizeof(int32): + off = INTALIGN(off); + break; + default: + if (att[attnum]->attlen < sizeof(int32)) + elog(WARN, "fastgetattr3: attribute %d has len %d", + attnum, att[attnum]->attlen); + if (att[attnum]->attalign == 'd') + off = DOUBLEALIGN(off); + else + off = LONGALIGN(off); + break; + } + return((char *) fetchatt(&(att[attnum]), tp + off)); + } +} + +/* ---------------- + * heap_getattr + * + * returns an attribute from a heap tuple. uses + * ---------------- + */ +char * +heap_getattr(HeapTuple tup, + Buffer b, + int attnum, + TupleDesc tupleDesc, + bool *isnull) +{ + bool localIsNull; + + /* ---------------- + * sanity checks + * ---------------- + */ + Assert(tup != NULL); + + if (! PointerIsValid(isnull)) + isnull = &localIsNull; + + if (attnum > (int) tup->t_natts) { + *isnull = true; + return ((char *) NULL); + } + + /* ---------------- + * take care of user defined attributes + * ---------------- + */ + if (attnum > 0) { + char *datum; + datum = fastgetattr(tup, attnum, tupleDesc, isnull); + + return (datum); + } + + /* ---------------- + * take care of system attributes + * ---------------- + */ + *isnull = false; + return + heap_getsysattr(tup, b, attnum); +} + +/* ---------------- + * heap_copytuple + * + * returns a copy of an entire tuple + * ---------------- + */ +HeapTuple +heap_copytuple(HeapTuple tuple) +{ + HeapTuple newTuple; + + if (! HeapTupleIsValid(tuple)) + return (NULL); + + /* XXX For now, just prevent an undetectable executor related error */ + if (tuple->t_len > MAXTUPLEN) { + elog(WARN, "palloctup: cannot handle length %d tuples", + tuple->t_len); + } + + newTuple = (HeapTuple) palloc(tuple->t_len); + memmove((char *) newTuple, (char *) tuple, (int) tuple->t_len); + return(newTuple); +} + +/* ---------------- + * heap_deformtuple + * + * the inverse of heap_formtuple (see below) + * ---------------- + */ +void +heap_deformtuple(HeapTuple tuple, + TupleDesc tdesc, + Datum values[], + char nulls[]) +{ + int i; + int natts; + + Assert(HeapTupleIsValid(tuple)); + + natts = tuple->t_natts; + for (i = 0; i<natts; i++) { + bool isnull; + + values[i] = (Datum)heap_getattr(tuple, + InvalidBuffer, + i+1, + tdesc, + &isnull); + if (isnull) + nulls[i] = 'n'; + else + nulls[i] = ' '; + } +} + +/* ---------------- + * heap_formtuple + * + * constructs a tuple from the given value[] and null[] arrays + * + * old comments + * Handles alignment by aligning 2 byte attributes on short boundries + * and 3 or 4 byte attributes on long word boundries on a vax; and + * aligning non-byte attributes on short boundries on a sun. Does + * not properly align fixed length arrays of 1 or 2 byte types (yet). + * + * Null attributes are indicated by a 'n' in the appropriate byte + * of the null[]. Non-null attributes are indicated by a ' ' (space). + * + * Fix me. (Figure that must keep context if debug--allow give oid.) + * Assumes in order. + * ---------------- + */ +HeapTuple +heap_formtuple(TupleDesc tupleDescriptor, + Datum value[], + char nulls[]) +{ + char *tp; /* tuple pointer */ + HeapTuple tuple; /* return tuple */ + int bitmaplen; + long len; + int hoff; + bool hasnull = false; + int i; + int numberOfAttributes = tupleDescriptor->natts; + + len = sizeof *tuple - sizeof tuple->t_bits; + + for (i = 0; i < numberOfAttributes && !hasnull; i++) { + if (nulls[i] != ' ') hasnull = true; + } + + if (numberOfAttributes > MaxHeapAttributeNumber) + elog(WARN, "heap_formtuple: numberOfAttributes of %d > %d", + numberOfAttributes, MaxHeapAttributeNumber); + + if (hasnull) { + bitmaplen = BITMAPLEN(numberOfAttributes); + len += bitmaplen; + } + + hoff = len = DOUBLEALIGN(len); /* be conservative here */ + + len += ComputeDataSize(tupleDescriptor, value, nulls); + + tp = (char *) palloc(len); + tuple = (HeapTuple) tp; + + memset(tp, 0, (int)len); + + tuple->t_len = len; + tuple->t_natts = numberOfAttributes; + tuple->t_hoff = hoff; + tuple->t_tmin = INVALID_ABSTIME; + tuple->t_tmax = CURRENT_ABSTIME; + + DataFill((char *)tuple + tuple->t_hoff, + tupleDescriptor, + value, + nulls, + &tuple->t_infomask, + (hasnull ? tuple->t_bits : NULL)); + + return (tuple); +} + +/* ---------------- + * heap_modifytuple + * + * forms a new tuple from an old tuple and a set of replacement values. + * ---------------- + */ +HeapTuple +heap_modifytuple(HeapTuple tuple, + Buffer buffer, + Relation relation, + Datum replValue[], + char replNull[], + char repl[]) +{ + int attoff; + int numberOfAttributes; + Datum *value; + char *nulls; + bool isNull; + HeapTuple newTuple; + int madecopy; + uint8 infomask; + + /* ---------------- + * sanity checks + * ---------------- + */ + Assert(HeapTupleIsValid(tuple)); + Assert(BufferIsValid(buffer) || RelationIsValid(relation)); + Assert(HeapTupleIsValid(tuple)); + Assert(PointerIsValid(replValue)); + Assert(PointerIsValid(replNull)); + Assert(PointerIsValid(repl)); + + /* ---------------- + * if we're pointing to a disk page, then first + * make a copy of our tuple so that all the attributes + * are available. XXX this is inefficient -cim + * ---------------- + */ + madecopy = 0; + if (BufferIsValid(buffer) == true) { + relation = (Relation) BufferGetRelation(buffer); + tuple = heap_copytuple(tuple); + madecopy = 1; + } + + numberOfAttributes = RelationGetRelationTupleForm(relation)->relnatts; + + /* ---------------- + * allocate and fill value[] and nulls[] arrays from either + * the tuple or the repl information, as appropriate. + * ---------------- + */ + value = (Datum *) palloc(numberOfAttributes * sizeof *value); + nulls = (char *) palloc(numberOfAttributes * sizeof *nulls); + + for (attoff = 0; + attoff < numberOfAttributes; + attoff += 1) { + + if (repl[attoff] == ' ') { + char *attr; + + attr = + heap_getattr(tuple, + InvalidBuffer, + AttrOffsetGetAttrNumber(attoff), + RelationGetTupleDescriptor(relation), + &isNull) ; + value[attoff] = PointerGetDatum(attr); + nulls[attoff] = (isNull) ? 'n' : ' '; + + } else if (repl[attoff] != 'r') { + elog(WARN, "heap_modifytuple: repl is \\%3d", repl[attoff]); + + } else { /* == 'r' */ + value[attoff] = replValue[attoff]; + nulls[attoff] = replNull[attoff]; + } + } + + /* ---------------- + * create a new tuple from the values[] and nulls[] arrays + * ---------------- + */ + newTuple = heap_formtuple(RelationGetTupleDescriptor(relation), + value, + nulls); + + /* ---------------- + * copy the header except for t_len, t_natts, t_hoff, t_bits, t_infomask + * ---------------- + */ + infomask = newTuple->t_infomask; + memmove((char *) &newTuple->t_ctid, /*XXX*/ + (char *) &tuple->t_ctid, + ((char *) &tuple->t_hoff - (char *) &tuple->t_ctid)); /*XXX*/ + newTuple->t_infomask = infomask; + newTuple->t_natts = numberOfAttributes; /* fix t_natts just in case */ + + /* ---------------- + * if we made a copy of the tuple, then free it. + * ---------------- + */ + if (madecopy) + pfree(tuple); + + return + newTuple; +} + +/* ---------------------------------------------------------------- + * other misc functions + * ---------------------------------------------------------------- + */ + +HeapTuple +heap_addheader(uint32 natts, /* max domain index */ + int structlen, /* its length */ + char *structure) /* pointer to the struct */ +{ + register char *tp; /* tuple data pointer */ + HeapTuple tup; + long len; + int hoff; + + AssertArg(natts > 0); + + len = sizeof (HeapTupleData) - sizeof (tup->t_bits); + + hoff = len = DOUBLEALIGN(len); /* be conservative */ + len += structlen; + tp = (char *) palloc(len); + tup = (HeapTuple) tp; + memset((char*)tup, 0, len); + + tup->t_len = (short) len; /* XXX */ + tp += tup->t_hoff = hoff; + tup->t_natts = natts; + tup->t_infomask = 0; + + memmove(tp, structure, structlen); + + return (tup); +} diff --git a/src/backend/access/common/heapvalid.c b/src/backend/access/common/heapvalid.c new file mode 100644 index 00000000000..b80c5dd9eb0 --- /dev/null +++ b/src/backend/access/common/heapvalid.c @@ -0,0 +1,134 @@ +/*------------------------------------------------------------------------- + * + * heapvalid.c-- + * heap tuple qualification validity checking code + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/common/Attic/heapvalid.c,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "c.h" + +#include "access/htup.h" +#include "access/skey.h" +#include "access/heapam.h" +#include "utils/tqual.h" +#include "access/valid.h" /* where the declarations go */ +#include "access/xact.h" + +#include "storage/buf.h" +#include "storage/bufmgr.h" +#include "storage/bufpage.h" +#include "storage/itemid.h" +#include "fmgr.h" +#include "utils/elog.h" +#include "utils/rel.h" + +/* ---------------- + * heap_keytest + * + * Test a heap tuple with respect to a scan key. + * ---------------- + */ +bool +heap_keytest(HeapTuple t, + TupleDesc tupdesc, + int nkeys, + ScanKey keys) +{ + bool isnull; + Datum atp; + int test; + + for (; nkeys--; keys++) { + atp = (Datum)heap_getattr(t, InvalidBuffer, + keys->sk_attno, + tupdesc, + &isnull); + + if (isnull) + /* XXX eventually should check if SK_ISNULL */ + return false; + + if (keys->sk_flags & SK_COMMUTE) + test = (long) FMGR_PTR2(keys->sk_func, keys->sk_procedure, + keys->sk_argument, atp); + else + test = (long) FMGR_PTR2(keys->sk_func, keys->sk_procedure, + atp, keys->sk_argument); + + if (!test == !(keys->sk_flags & SK_NEGATE)) + return false; + } + + return true; +} + +/* ---------------- + * heap_tuple_satisfies + * + * Returns a valid HeapTuple if it satisfies the timequal and keytest. + * Returns NULL otherwise. Used to be heap_satisifies (sic) which + * returned a boolean. It now returns a tuple so that we can avoid doing two + * PageGetItem's per tuple. + * + * Complete check of validity including LP_CTUP and keytest. + * This should perhaps be combined with valid somehow in the + * future. (Also, additional rule tests/time range tests.) + * + * on 8/21/92 mao says: i rearranged the tests here to do keytest before + * SatisfiesTimeQual. profiling indicated that even for vacuumed relations, + * time qual checking was more expensive than key testing. time qual is + * least likely to fail, too. we should really add the time qual test to + * the restriction and optimize it in the normal way. this has interactions + * with joey's expensive function work. + * ---------------- + */ +HeapTuple +heap_tuple_satisfies(ItemId itemId, + Relation relation, + PageHeader disk_page, + TimeQual qual, + int nKeys, + ScanKey key) +{ + HeapTuple tuple; + bool res; + + if (! ItemIdIsUsed(itemId)) + return NULL; + + tuple = (HeapTuple) PageGetItem((Page) disk_page, itemId); + + if (key != NULL) + res = heap_keytest(tuple, RelationGetTupleDescriptor(relation), + nKeys, key); + else + res = TRUE; + + if (res && (relation->rd_rel->relkind == RELKIND_UNCATALOGED + || HeapTupleSatisfiesTimeQual(tuple,qual))) + return tuple; + + return (HeapTuple) NULL; +} + +/* + * TupleUpdatedByCurXactAndCmd() -- Returns true if this tuple has + * already been updated once by the current transaction/command + * pair. + */ +bool +TupleUpdatedByCurXactAndCmd(HeapTuple t) +{ + if (TransactionIdEquals(t->t_xmax, + GetCurrentTransactionId()) && + t->t_cmax == GetCurrentCommandId()) + return true; + + return false; +} diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c new file mode 100644 index 00000000000..be5d2ccbd96 --- /dev/null +++ b/src/backend/access/common/indextuple.c @@ -0,0 +1,427 @@ +/*------------------------------------------------------------------------- + * + * indextuple.c-- + * This file contains index tuple accessor and mutator routines, + * as well as a few various tuple utilities. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/common/indextuple.c,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <string.h> + +#include "c.h" +#include "access/ibit.h" +#include "access/itup.h" /* where the declarations go */ +#include "access/heapam.h" +#include "access/genam.h" +#include "access/tupdesc.h" +#include "access/tupmacs.h" + +#include "storage/itemptr.h" +#include "utils/elog.h" +#include "utils/palloc.h" + +static Size IndexInfoFindDataOffset(unsigned short t_info); + +/* ---------------------------------------------------------------- + * index_ tuple interface routines + * ---------------------------------------------------------------- + */ + +/* ---------------- + * index_formtuple + * ---------------- + */ +IndexTuple +index_formtuple(TupleDesc tupleDescriptor, + Datum value[], + char null[]) +{ + register char *tp; /* tuple pointer */ + IndexTuple tuple; /* return tuple */ + Size size, hoff; + int i; + unsigned short infomask = 0; + bool hasnull = false; + char tupmask = 0; + int numberOfAttributes = tupleDescriptor->natts; + + if (numberOfAttributes > MaxIndexAttributeNumber) + elog(WARN, "index_formtuple: numberOfAttributes of %d > %d", + numberOfAttributes, MaxIndexAttributeNumber); + + + for (i = 0; i < numberOfAttributes && !hasnull; i++) { + if (null[i] != ' ') hasnull = true; + } + + if (hasnull) infomask |= INDEX_NULL_MASK; + + hoff = IndexInfoFindDataOffset(infomask); + size = hoff + + ComputeDataSize(tupleDescriptor, + value, null); + size = DOUBLEALIGN(size); /* be conservative */ + + tp = (char *) palloc(size); + tuple = (IndexTuple) tp; + memset(tp,0,(int)size); + + DataFill((char *)tp + hoff, + tupleDescriptor, + value, + null, + &tupmask, + (hasnull ? (bits8*)tp + sizeof(*tuple) : NULL)); + + /* + * We do this because DataFill wants to initialize a "tupmask" which + * is used for HeapTuples, but we want an indextuple infomask. The only + * "relevent" info is the "has variable attributes" field, which is in + * mask position 0x02. We have already set the null mask above. + */ + + if (tupmask & 0x02) infomask |= INDEX_VAR_MASK; + + /* + * Here we make sure that we can actually hold the size. We also want + * to make sure that size is not aligned oddly. This actually is a + * rather odd way to make sure the size is not too large overall. + */ + + if (size & 0xE000) + elog(WARN, "index_formtuple: data takes %d bytes: too big", size); + + + infomask |= size; + + /* ---------------- + * initialize metadata + * ---------------- + */ + tuple->t_info = infomask; + return (tuple); +} + +/* ---------------- + * fastgetiattr + * + * This is a newer version of fastgetiattr which attempts to be + * faster by caching attribute offsets in the attribute descriptor. + * + * an alternate way to speed things up would be to cache offsets + * with the tuple, but that seems more difficult unless you take + * the storage hit of actually putting those offsets into the + * tuple you send to disk. Yuck. + * + * This scheme will be slightly slower than that, but should + * preform well for queries which hit large #'s of tuples. After + * you cache the offsets once, examining all the other tuples using + * the same attribute descriptor will go much quicker. -cim 5/4/91 + * ---------------- + */ +char * +fastgetiattr(IndexTuple tup, + int attnum, + TupleDesc tupleDesc, + bool *isnull) +{ + register char *tp; /* ptr to att in tuple */ + register char *bp; /* ptr to att in tuple */ + int slow; /* do we have to walk nulls? */ + register int data_off; /* tuple data offset */ + + /* ---------------- + * sanity checks + * ---------------- + */ + + Assert(PointerIsValid(isnull)); + Assert(attnum > 0); + + /* ---------------- + * Three cases: + * + * 1: No nulls and no variable length attributes. + * 2: Has a null or a varlena AFTER att. + * 3: Has nulls or varlenas BEFORE att. + * ---------------- + */ + + *isnull = false; + data_off = IndexTupleHasMinHeader(tup) ? sizeof *tup : + IndexInfoFindDataOffset(tup->t_info); + + if (IndexTupleNoNulls(tup)) { + + /* first attribute is always at position zero */ + + if (attnum == 1) { + return(fetchatt(&(tupleDesc->attrs[0]), (char *) tup + data_off)); + } + attnum--; + + if (tupleDesc->attrs[attnum]->attcacheoff > 0) { + return(fetchatt(&(tupleDesc->attrs[attnum]), + (char *) tup + data_off + + tupleDesc->attrs[attnum]->attcacheoff)); + } + + tp = (char *) tup + data_off; + + slow = 0; + }else { /* there's a null somewhere in the tuple */ + + bp = (char *) tup + sizeof(*tup); /* "knows" t_bits are here! */ + slow = 0; + /* ---------------- + * check to see if desired att is null + * ---------------- + */ + + attnum--; + { + if (att_isnull(attnum, bp)) { + *isnull = true; + return NULL; + } + } + /* ---------------- + * Now check to see if any preceeding bits are null... + * ---------------- + */ + { + register int i = 0; /* current offset in bp */ + register int mask; /* bit in byte we're looking at */ + register char n; /* current byte in bp */ + register int byte, finalbit; + + byte = attnum >> 3; + finalbit = attnum & 0x07; + + for (; i <= byte; i++) { + n = bp[i]; + if (i < byte) { + /* check for nulls in any "earlier" bytes */ + if ((~n) != 0) { + slow++; + break; + } + } else { + /* check for nulls "before" final bit of last byte*/ + mask = (finalbit << 1) - 1; + if ((~n) & mask) + slow++; + } + } + } + tp = (char *) tup + data_off; + } + + /* now check for any non-fixed length attrs before our attribute */ + + if (!slow) { + if (tupleDesc->attrs[attnum]->attcacheoff > 0) { + return(fetchatt(&(tupleDesc->attrs[attnum]), + tp + tupleDesc->attrs[attnum]->attcacheoff)); + }else if (!IndexTupleAllFixed(tup)) { + register int j = 0; + + for (j = 0; j < attnum && !slow; j++) + if (tupleDesc->attrs[j]->attlen < 1) slow = 1; + } + } + + /* + * if slow is zero, and we got here, we know that we have a tuple with + * no nulls. We also know that we have to initialize the remainder of + * the attribute cached offset values. + */ + + if (!slow) { + register int j = 1; + register long off; + + /* + * need to set cache for some atts + */ + + tupleDesc->attrs[0]->attcacheoff = 0; + + while (tupleDesc->attrs[j]->attcacheoff > 0) j++; + + off = tupleDesc->attrs[j-1]->attcacheoff + + tupleDesc->attrs[j-1]->attlen; + + for (; j < attnum + 1; j++) { + /* + * Fix me when going to a machine with more than a four-byte + * word! + */ + + switch(tupleDesc->attrs[j]->attlen) + { + case -1: + off = (tupleDesc->attrs[j]->attalign=='d')? + DOUBLEALIGN(off):INTALIGN(off); + break; + case sizeof(char): + break; + case sizeof(short): + off = SHORTALIGN(off); + break; + case sizeof(int32): + off = INTALIGN(off); + break; + default: + if (tupleDesc->attrs[j]->attlen > sizeof(int32)) + off = (tupleDesc->attrs[j]->attalign=='d')? + DOUBLEALIGN(off) : LONGALIGN(off); + else + elog(WARN, "fastgetiattr: attribute %d has len %d", + j, tupleDesc->attrs[j]->attlen); + break; + + } + + tupleDesc->attrs[j]->attcacheoff = off; + off += tupleDesc->attrs[j]->attlen; + } + + return(fetchatt( &(tupleDesc->attrs[attnum]), + tp + tupleDesc->attrs[attnum]->attcacheoff)); + }else { + register bool usecache = true; + register int off = 0; + register int i; + + /* + * Now we know that we have to walk the tuple CAREFULLY. + */ + + for (i = 0; i < attnum; i++) { + if (!IndexTupleNoNulls(tup)) { + if (att_isnull(i, bp)) { + usecache = false; + continue; + } + } + + if (usecache && tupleDesc->attrs[i]->attcacheoff > 0) { + off = tupleDesc->attrs[i]->attcacheoff; + if (tupleDesc->attrs[i]->attlen == -1) + usecache = false; + else + continue; + } + + if (usecache) tupleDesc->attrs[i]->attcacheoff = off; + switch(tupleDesc->attrs[i]->attlen) + { + case sizeof(char): + off++; + break; + case sizeof(short): + off = SHORTALIGN(off) + sizeof(short); + break; + case -1: + usecache = false; + off = (tupleDesc->attrs[i]->attalign=='d')? + DOUBLEALIGN(off):INTALIGN(off); + off += VARSIZE(tp + off); + break; + default: + if (tupleDesc->attrs[i]->attlen > sizeof(int32)) + off = (tupleDesc->attrs[i]->attalign=='d') ? + DOUBLEALIGN(off) + tupleDesc->attrs[i]->attlen : + LONGALIGN(off) + tupleDesc->attrs[i]->attlen; + else + elog(WARN, "fastgetiattr2: attribute %d has len %d", + i, tupleDesc->attrs[i]->attlen); + + break; + } + } + + return(fetchatt(&tupleDesc->attrs[attnum], tp + off)); + } +} + +/* ---------------- + * index_getattr + * ---------------- + */ +Datum +index_getattr(IndexTuple tuple, + AttrNumber attNum, + TupleDesc tupDesc, + bool *isNullOutP) +{ + Assert (attNum > 0); + + return (Datum) + fastgetiattr(tuple, attNum, tupDesc, isNullOutP); +} + +RetrieveIndexResult +FormRetrieveIndexResult(ItemPointer indexItemPointer, + ItemPointer heapItemPointer) +{ + RetrieveIndexResult result; + + Assert(ItemPointerIsValid(indexItemPointer)); + Assert(ItemPointerIsValid(heapItemPointer)); + + result = (RetrieveIndexResult) palloc(sizeof *result); + + result->index_iptr = *indexItemPointer; + result->heap_iptr = *heapItemPointer; + + return (result); +} + +/* + * Takes an infomask as argument (primarily because this needs to be usable + * at index_formtuple time so enough space is allocated). + * + * Change me if adding an attribute to IndexTuples!!!!!!!!!!! + */ +static Size +IndexInfoFindDataOffset(unsigned short t_info) +{ + if (!(t_info & INDEX_NULL_MASK)) + return((Size) sizeof(IndexTupleData)); + else { + Size size = sizeof(IndexTupleData); + + if (t_info & INDEX_NULL_MASK) { + size += sizeof(IndexAttributeBitMapData); + } + return DOUBLEALIGN(size); /* be conservative */ + } +} + +/* + * Copies source into target. If *target == NULL, we palloc space; otherwise + * we assume we have space that is already palloc'ed. + */ +void +CopyIndexTuple(IndexTuple source, IndexTuple *target) +{ + Size size; + IndexTuple ret; + + size = IndexTupleSize(source); + if (*target == NULL) { + *target = (IndexTuple) palloc(size); + } + + ret = *target; + memmove((char*)ret, (char*)source, size); +} + diff --git a/src/backend/access/common/indexvalid.c b/src/backend/access/common/indexvalid.c new file mode 100644 index 00000000000..b437718cecc --- /dev/null +++ b/src/backend/access/common/indexvalid.c @@ -0,0 +1,84 @@ +/*------------------------------------------------------------------------- + * + * indexvalid.c-- + * index tuple qualification validity checking code + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/common/Attic/indexvalid.c,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "executor/execdebug.h" +#include "access/genam.h" +#include "access/iqual.h" /* where the declarations go */ +#include "access/itup.h" +#include "access/skey.h" + +#include "storage/buf.h" +#include "storage/bufpage.h" +#include "storage/itemid.h" +#include "utils/rel.h" + +/* ---------------------------------------------------------------- + * index scan key qualification code + * ---------------------------------------------------------------- + */ +int NIndexTupleProcessed; + +/* ---------------- + * index_keytest + * + * old comments + * May eventually combine with other tests (like timeranges)? + * Should have Buffer buffer; as an argument and pass it to amgetattr. + * ---------------- + */ +bool +index_keytest(IndexTuple tuple, + TupleDesc tupdesc, + int scanKeySize, + ScanKey key) +{ + bool isNull; + Datum datum; + int test; + + IncrIndexProcessed(); + + while (scanKeySize > 0) { + datum = index_getattr(tuple, + 1, + tupdesc, + &isNull); + + if (isNull) { + /* XXX eventually should check if SK_ISNULL */ + return (false); + } + + if (key[0].sk_flags & SK_COMMUTE) { + test = (int) (*(key[0].sk_func)) + (DatumGetPointer(key[0].sk_argument), + datum); + } else { + test = (int) (*(key[0].sk_func)) + (datum, + DatumGetPointer(key[0].sk_argument)); + } + + if (!test == !(key[0].sk_flags & SK_NEGATE)) { + return (false); + } + + scanKeySize -= 1; + key++; + } + + return (true); +} + diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c new file mode 100644 index 00000000000..556b73b9dfd --- /dev/null +++ b/src/backend/access/common/printtup.c @@ -0,0 +1,306 @@ +/*------------------------------------------------------------------------- + * + * printtup.c-- + * Routines to print out tuples to the destination (binary or non-binary + * portals, frontend/interactive backend, etc.). + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.1.1.1 1996/07/09 06:21:10 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <sys/file.h> +#include <stdio.h> +#include <string.h> + +#include "postgres.h" + +#include "access/heapam.h" +#include "access/htup.h" +#include "access/skey.h" +#include "access/printtup.h" +#include "access/tupdesc.h" +#include "storage/buf.h" +#include "utils/memutils.h" +#include "utils/palloc.h" +#include "fmgr.h" +#include "utils/elog.h" + +#include "utils/syscache.h" +#include "catalog/pg_type.h" + +#include "libpq/libpq.h" + +/* ---------------------------------------------------------------- + * printtup / debugtup support + * ---------------------------------------------------------------- + */ + +/* ---------------- + * typtoout - used by printtup and debugtup + * ---------------- + */ +Oid +typtoout(Oid type) +{ + HeapTuple typeTuple; + + typeTuple = SearchSysCacheTuple(TYPOID, + ObjectIdGetDatum(type), + 0, 0, 0); + + if (HeapTupleIsValid(typeTuple)) + return((Oid) + ((TypeTupleForm) GETSTRUCT(typeTuple))->typoutput); + + elog(WARN, "typtoout: Cache lookup of type %d failed", type); + return(InvalidOid); +} + +Oid +gettypelem(Oid type) +{ + HeapTuple typeTuple; + + typeTuple = SearchSysCacheTuple(TYPOID, + ObjectIdGetDatum(type), + 0,0,0); + + if (HeapTupleIsValid(typeTuple)) + return((Oid) + ((TypeTupleForm) GETSTRUCT(typeTuple))->typelem); + + elog(WARN, "typtoout: Cache lookup of type %d failed", type); + return(InvalidOid); +} + +/* ---------------- + * printtup + * ---------------- + */ +void +printtup(HeapTuple tuple, TupleDesc typeinfo) +{ + int i, j, k; + char *outputstr, *attr; + bool isnull; + Oid typoutput; + + /* ---------------- + * tell the frontend to expect new tuple data + * ---------------- + */ + pq_putnchar("D", 1); + + /* ---------------- + * send a bitmap of which attributes are null + * ---------------- + */ + j = 0; + k = 1 << 7; + for (i = 0; i < tuple->t_natts; ) { + attr = heap_getattr(tuple, InvalidBuffer, ++i, typeinfo, &isnull); + if (!isnull) + j |= k; + k >>= 1; + if (!(i & 7)) { + pq_putint(j, 1); + j = 0; + k = 1 << 7; + } + } + if (i & 7) + pq_putint(j, 1); + + /* ---------------- + * send the attributes of this tuple + * ---------------- + */ + for (i = 0; i < tuple->t_natts; ++i) { + attr = heap_getattr(tuple, InvalidBuffer, i+1, typeinfo, &isnull); + typoutput = typtoout((Oid) typeinfo->attrs[i]->atttypid); + + if (!isnull && OidIsValid(typoutput)) { + outputstr = fmgr(typoutput, attr, + gettypelem(typeinfo->attrs[i]->atttypid)); + pq_putint(strlen(outputstr)+4, 4); + pq_putnchar(outputstr, strlen(outputstr)); + pfree(outputstr); + } + } +} + +/* ---------------- + * printatt + * ---------------- + */ +static void +printatt(unsigned attributeId, + AttributeTupleForm attributeP, + char *value) +{ + printf("\t%2d: %.*s%s%s%s\t(typeid = %u, len = %d, byval = %c)\n", + attributeId, + NAMEDATALEN, /* attname is a char16 */ + attributeP->attname.data, + value != NULL ? " = \"" : "", + value != NULL ? value : "", + value != NULL ? "\"" : "", + (unsigned int) (attributeP->atttypid), + attributeP->attlen, + attributeP->attbyval ? 't' : 'f'); +} + +/* ---------------- + * showatts + * ---------------- + */ +void +showatts(char *name, TupleDesc tupleDesc) +{ + int i; + int natts = tupleDesc->natts; + AttributeTupleForm *attinfo = tupleDesc->attrs; + + puts(name); + for (i = 0; i < natts; ++i) + printatt((unsigned) i+1, attinfo[i], (char *) NULL); + printf("\t----\n"); +} + +/* ---------------- + * debugtup + * ---------------- + */ +void +debugtup(HeapTuple tuple, TupleDesc typeinfo) +{ + register int i; + char *attr, *value; + bool isnull; + Oid typoutput; + + for (i = 0; i < tuple->t_natts; ++i) { + attr = heap_getattr(tuple, InvalidBuffer, i+1, typeinfo, &isnull); + typoutput = typtoout((Oid) typeinfo->attrs[i]->atttypid); + + if (!isnull && OidIsValid(typoutput)) { + value = fmgr(typoutput, attr, + gettypelem(typeinfo->attrs[i]->atttypid)); + printatt((unsigned) i+1, typeinfo->attrs[i], value); + pfree(value); + } + } + printf("\t----\n"); +} + +/*#define IPORTAL_DEBUG*/ + +/* ---------------- + * printtup_internal + * Protocol expects either T, D, C, E, or N. + * We use a different data prefix, e.g. 'B' instead of 'D' to + * indicate a tuple in internal (binary) form. + * + * This is same as printtup, except we don't use the typout func. + * ---------------- + */ +void +printtup_internal(HeapTuple tuple, TupleDesc typeinfo) +{ + int i, j, k; + char *attr; + bool isnull; + + /* ---------------- + * tell the frontend to expect new tuple data + * ---------------- + */ + pq_putnchar("B", 1); + + /* ---------------- + * send a bitmap of which attributes are null + * ---------------- + */ + j = 0; + k = 1 << 7; + for (i = 0; i < tuple->t_natts; ) { + attr = heap_getattr(tuple, InvalidBuffer, ++i, typeinfo, &isnull); + if (!isnull) + j |= k; + k >>= 1; + if (!(i & 7)) { + pq_putint(j, 1); + j = 0; + k = 1 << 7; + } + } + if (i & 7) + pq_putint(j, 1); + + /* ---------------- + * send the attributes of this tuple + * ---------------- + */ +#ifdef IPORTAL_DEBUG + fprintf(stderr, "sending tuple with %d atts\n", tuple->t_natts); +#endif + for (i = 0; i < tuple->t_natts; ++i) { + int32 len = typeinfo->attrs[i]->attlen; + + attr = heap_getattr(tuple, InvalidBuffer, i+1, typeinfo, &isnull); + if (!isnull) { + /* # of bytes, and opaque data */ + if (len == -1) { + /* variable length, assume a varlena structure */ + len = VARSIZE(attr) - VARHDRSZ; + + pq_putint(len, sizeof(int32)); + pq_putnchar(VARDATA(attr), len); +#ifdef IPORTAL_DEBUG + { + char *d = VARDATA(attr); + + fprintf(stderr, "length %d data %x%x%x%x\n", + len, *d, *(d+1), *(d+2), *(d+3)); + } +#endif + } else { + /* fixed size */ + if (typeinfo->attrs[i]->attbyval) { + int8 i8; + int16 i16; + int32 i32; + + pq_putint(len, sizeof(int32)); + switch (len) { + case sizeof(int8): + i8 = DatumGetChar(attr); + pq_putnchar((char *) &i8, len); + break; + case sizeof(int16): + i16 = DatumGetInt16(attr); + pq_putnchar((char *) &i16, len); + break; + case sizeof(int32): + i32 = DatumGetInt32(attr); + pq_putnchar((char *) &i32, len); + break; + } +#ifdef IPORTAL_DEBUG + fprintf(stderr, "byval length %d data %d\n", len, attr); +#endif + } else { + pq_putint(len, sizeof(int32)); + pq_putnchar(attr, len); +#ifdef IPORTAL_DEBUG + fprintf(stderr, "byref length %d data %x\n", len, attr); +#endif + } + } + } + } +} diff --git a/src/backend/access/common/scankey.c b/src/backend/access/common/scankey.c new file mode 100644 index 00000000000..7a47219a73c --- /dev/null +++ b/src/backend/access/common/scankey.c @@ -0,0 +1,68 @@ +/*------------------------------------------------------------------------- + * + * scan.c-- + * scan direction and key code + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/common/scankey.c,v 1.1.1.1 1996/07/09 06:21:10 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "c.h" +#include "access/sdir.h" +#include "access/attnum.h" +#include "access/skey.h" + +#include "fmgr.h" + +/* + * ScanKeyEntryIsLegal -- + * True iff the scan key entry is legal. + */ +#define ScanKeyEntryIsLegal(entry) \ + ((bool) (AssertMacro(PointerIsValid(entry)) && \ + AttributeNumberIsValid(entry->sk_attno))) + +/* + * ScanKeyEntrySetIllegal -- + * Marks a scan key entry as illegal. + */ +void +ScanKeyEntrySetIllegal(ScanKey entry) +{ + + Assert(PointerIsValid(entry)); + + entry->sk_flags = 0; /* just in case... */ + entry->sk_attno = InvalidAttrNumber; + entry->sk_procedure = 0; /* should be InvalidRegProcedure */ +} + +/* + * ScanKeyEntryInitialize -- + * Initializes an scan key entry. + * + * Note: + * Assumes the scan key entry is valid. + * Assumes the intialized scan key entry will be legal. + */ +void +ScanKeyEntryInitialize(ScanKey entry, + bits16 flags, + AttrNumber attributeNumber, + RegProcedure procedure, + Datum argument) +{ + Assert(PointerIsValid(entry)); + + entry->sk_flags = flags; + entry->sk_attno = attributeNumber; + entry->sk_procedure = procedure; + entry->sk_argument = argument; + fmgr_info(procedure, &entry->sk_func, &entry->sk_nargs); + + Assert(ScanKeyEntryIsLegal(entry)); +} diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c new file mode 100644 index 00000000000..527eb5113df --- /dev/null +++ b/src/backend/access/common/tupdesc.c @@ -0,0 +1,398 @@ +/*------------------------------------------------------------------------- + * + * tupdesc.c-- + * POSTGRES tuple descriptor support code + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/common/tupdesc.c,v 1.1.1.1 1996/07/09 06:21:10 scrappy Exp $ + * + * NOTES + * some of the executor utility code such as "ExecTypeFromTL" should be + * moved here. + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> /* for sprintf() */ +#include <ctype.h> +#include <string.h> + +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "nodes/parsenodes.h" + +#include "access/attnum.h" +#include "access/htup.h" +#include "access/tupdesc.h" + +#include "utils/builtins.h" +#include "utils/elog.h" /* XXX generate exceptions instead */ +#include "utils/palloc.h" + +#include "utils/syscache.h" +#include "catalog/pg_type.h" + +#include "nodes/primnodes.h" + +#include "parser/catalog_utils.h" + +/* ---------------------------------------------------------------- + * CreateTemplateTupleDesc + * + * This function allocates and zeros a tuple descriptor structure. + * ---------------------------------------------------------------- + */ +TupleDesc +CreateTemplateTupleDesc(int natts) +{ + uint32 size; + TupleDesc desc; + + /* ---------------- + * sanity checks + * ---------------- + */ + AssertArg(natts >= 1); + + /* ---------------- + * allocate enough memory for the tuple descriptor and + * zero it as TupleDescInitEntry assumes that the descriptor + * is filled with NULL pointers. + * ---------------- + */ + size = natts * sizeof (AttributeTupleForm); + desc = (TupleDesc) palloc(sizeof(struct tupleDesc)); + desc->attrs = (AttributeTupleForm*) palloc(size); + memset(desc->attrs, 0, size); + + desc->natts = natts; + + return (desc); +} + +/* ---------------------------------------------------------------- + * CreateTupleDesc + * + * This function allocates a new TupleDesc from AttributeTupleForm array + * ---------------------------------------------------------------- + */ +TupleDesc +CreateTupleDesc(int natts, AttributeTupleForm* attrs) +{ + TupleDesc desc; + + /* ---------------- + * sanity checks + * ---------------- + */ + AssertArg(natts >= 1); + + desc = (TupleDesc) palloc(sizeof(struct tupleDesc)); + desc->attrs = attrs; + desc->natts = natts; + + + return (desc); +} + +/* ---------------------------------------------------------------- + * CreateTupleDescCopy + * + * This function creates a new TupleDesc by copying from an existing + * TupleDesc + * + * ---------------------------------------------------------------- + */ +TupleDesc +CreateTupleDescCopy(TupleDesc tupdesc) +{ + TupleDesc desc; + int i, size; + + desc = (TupleDesc) palloc(sizeof(struct tupleDesc)); + desc->natts = tupdesc->natts; + size = desc->natts * sizeof (AttributeTupleForm); + desc->attrs = (AttributeTupleForm*) palloc(size); + for (i=0;i<desc->natts;i++) { + desc->attrs[i] = + (AttributeTupleForm)palloc(ATTRIBUTE_TUPLE_SIZE); + memmove(desc->attrs[i], + tupdesc->attrs[i], + ATTRIBUTE_TUPLE_SIZE); + } + return desc; +} + +/* ---------------------------------------------------------------- + * TupleDescInitEntry + * + * This function initializes a single attribute structure in + * a preallocated tuple descriptor. + * ---------------------------------------------------------------- + */ +bool +TupleDescInitEntry(TupleDesc desc, + AttrNumber attributeNumber, + char *attributeName, + char *typeName, + int attdim, + bool attisset) +{ + HeapTuple tuple; + TypeTupleForm typeForm; + AttributeTupleForm att; + + /* ---------------- + * sanity checks + * ---------------- + */ + AssertArg(PointerIsValid(desc)); + AssertArg(attributeNumber >= 1); + /* attributeName's are sometimes NULL, + from resdom's. I don't know why that is, though -- Jolly */ +/* AssertArg(NameIsValid(attributeName));*/ +/* AssertArg(NameIsValid(typeName));*/ + + AssertArg(!PointerIsValid(desc->attrs[attributeNumber - 1])); + + + /* ---------------- + * allocate storage for this attribute + * ---------------- + */ + + att = (AttributeTupleForm) palloc(ATTRIBUTE_TUPLE_SIZE); + desc->attrs[attributeNumber - 1] = att; + + /* ---------------- + * initialize some of the attribute fields + * ---------------- + */ + att->attrelid = 0; /* dummy value */ + + if (attributeName != NULL) + namestrcpy(&(att->attname), attributeName); + else + memset(att->attname.data,0,NAMEDATALEN); + + + att->attdefrel = 0; /* dummy value */ + att->attnvals = 0; /* dummy value */ + att->atttyparg = 0; /* dummy value */ + att->attbound = 0; /* dummy value */ + att->attcanindex = 0; /* dummy value */ + att->attproc = 0; /* dummy value */ + att->attcacheoff = -1; + + att->attnum = attributeNumber; + att->attnelems = attdim; + att->attisset = attisset; + + /* ---------------- + * search the system cache for the type tuple of the attribute + * we are creating so that we can get the typeid and some other + * stuff. + * + * Note: in the special case of + * + * create EMP (name = char16, manager = EMP) + * + * RelationNameCreateHeapRelation() calls BuildDesc() which + * calls this routine and since EMP does not exist yet, the + * system cache lookup below fails. That's fine, but rather + * then doing a elog(WARN) we just leave that information + * uninitialized, return false, then fix things up later. + * -cim 6/14/90 + * ---------------- + */ + tuple = SearchSysCacheTuple(TYPNAME, PointerGetDatum(typeName), + 0,0,0); + if (! HeapTupleIsValid(tuple)) { + /* ---------------- + * here type info does not exist yet so we just fill + * the attribute with dummy information and return false. + * ---------------- + */ + att->atttypid = InvalidOid; + att->attlen = (int16) 0; + att->attbyval = (bool) 0; + att->attalign = 'i'; + return false; + } + + /* ---------------- + * type info exists so we initialize our attribute + * information from the type tuple we found.. + * ---------------- + */ + typeForm = (TypeTupleForm) GETSTRUCT(tuple); + + att->atttypid = tuple->t_oid; + att->attalign = typeForm->typalign; + + /* ------------------------ + If this attribute is a set, what is really stored in the + attribute is the OID of a tuple in the pg_proc catalog. + The pg_proc tuple contains the query string which defines + this set - i.e., the query to run to get the set. + So the atttypid (just assigned above) refers to the type returned + by this query, but the actual length of this attribute is the + length (size) of an OID. + + Why not just make the atttypid point to the OID type, instead + of the type the query returns? Because the executor uses the atttypid + to tell the front end what type will be returned (in BeginCommand), + and in the end the type returned will be the result of the query, not + an OID. + + Why not wait until the return type of the set is known (i.e., the + recursive call to the executor to execute the set has returned) + before telling the front end what the return type will be? Because + the executor is a delicate thing, and making sure that the correct + order of front-end commands is maintained is messy, especially + considering that target lists may change as inherited attributes + are considered, etc. Ugh. + ----------------------------------------- + */ + if (attisset) { + Type t = type("oid"); + att->attlen = tlen(t); + att->attbyval = tbyval(t); + } else { + att->attlen = typeForm->typlen; + att->attbyval = typeForm->typbyval; + } + + + return true; +} + + +/* ---------------------------------------------------------------- + * TupleDescMakeSelfReference + * + * This function initializes a "self-referential" attribute like + * manager in "create EMP (name=text, manager = EMP)". + * It calls TypeShellMake() which inserts a "shell" type + * tuple into pg_type. A self-reference is one kind of set, so + * its size and byval are the same as for a set. See the comments + * above in TupleDescInitEntry. + * ---------------------------------------------------------------- + */ +static void +TupleDescMakeSelfReference(TupleDesc desc, + AttrNumber attnum, + char *relname) +{ + AttributeTupleForm att; + Type t = type("oid"); + + att = desc->attrs[attnum-1]; + att->atttypid = TypeShellMake(relname); + att->attlen = tlen(t); + att->attbyval = tbyval(t); + att->attnelems = 0; +} + +/* ---------------------------------------------------------------- + * BuildDescForRelation + * + * This is a general purpose function identical to BuildDesc + * but is used by the DefineRelation() code to catch the + * special case where you + * + * create FOO ( ..., x = FOO ) + * + * here, the initial type lookup for "x = FOO" will fail + * because FOO isn't in the catalogs yet. But since we + * are creating FOO, instead of doing an elog() we add + * a shell type tuple to pg_type and fix things later + * in amcreate(). + * ---------------------------------------------------------------- + */ +TupleDesc +BuildDescForRelation(List *schema, char *relname) +{ + int natts; + AttrNumber attnum; + List *p; + TupleDesc desc; + char *attname; + char *typename; + int attdim; + bool attisset; + + /* ---------------- + * allocate a new tuple descriptor + * ---------------- + */ + natts = length(schema); + desc = CreateTemplateTupleDesc(natts); + + attnum = 0; + + typename = palloc(NAMEDATALEN+1); + + foreach(p, schema) { + ColumnDef *entry; + List *arry; + + /* ---------------- + * for each entry in the list, get the name and type + * information from the list and have TupleDescInitEntry + * fill in the attribute information we need. + * ---------------- + */ + attnum++; + + entry = lfirst(p); + attname = entry->colname; + arry = entry->typename->arrayBounds; + attisset = entry->typename->setof; + + if (arry != NIL) { + char buf[20]; + + attdim = length(arry); + + /* array of XXX is _XXX (inherited from release 3) */ + sprintf(buf, "_%.*s", NAMEDATALEN, entry->typename->name); + strcpy(typename, buf); + } else { + strcpy(typename, entry->typename->name); + attdim = 0; + } + + if (! TupleDescInitEntry(desc, attnum, attname, + typename, attdim, attisset)) { + /* ---------------- + * if TupleDescInitEntry() fails, it means there is + * no type in the system catalogs. So now we check if + * the type name equals the relation name. If so we + * have a self reference, otherwise it's an error. + * ---------------- + */ + if (!strcmp(typename, relname)) { + TupleDescMakeSelfReference(desc, attnum, relname); + } else + elog(WARN, "DefineRelation: no such type %.*s", + NAMEDATALEN, typename); + } + + /* + * this is for char() and varchar(). When an entry is of type + * char() or varchar(), typlen is set to the appropriate length, + * which we'll use here instead. (The catalog lookup only returns + * the length of bpchar and varchar which is not what we want!) + * - ay 6/95 + */ + if (entry->typename->typlen > 0) { + desc->attrs[attnum - 1]->attlen = entry->typename->typlen; + } + } + return desc; +} + diff --git a/src/backend/access/funcindex.h b/src/backend/access/funcindex.h new file mode 100644 index 00000000000..4689df19c04 --- /dev/null +++ b/src/backend/access/funcindex.h @@ -0,0 +1,43 @@ +/*------------------------------------------------------------------------- + * + * funcindex.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: funcindex.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef _FUNC_INDEX_INCLUDED_ +#define _FUNC_INDEX_INCLUDED_ + +#include "postgres.h" + +typedef struct { + int nargs; + Oid arglist[8]; + Oid procOid; + NameData funcName; +} FuncIndexInfo; + +typedef FuncIndexInfo *FuncIndexInfoPtr; + +/* + * some marginally useful macro definitions + */ +/* #define FIgetname(FINFO) (&((FINFO)->funcName.data[0]))*/ +#define FIgetname(FINFO) (FINFO)->funcName.data +#define FIgetnArgs(FINFO) (FINFO)->nargs +#define FIgetProcOid(FINFO) (FINFO)->procOid +#define FIgetArg(FINFO, argnum) (FINFO)->arglist[argnum] +#define FIgetArglist(FINFO) (FINFO)->arglist + +#define FIsetnArgs(FINFO, numargs) ((FINFO)->nargs = numargs) +#define FIsetProcOid(FINFO, id) ((FINFO)->procOid = id) +#define FIsetArg(FINFO, argnum, argtype) ((FINFO)->arglist[argnum] = argtype) + +#define FIisFunctionalIndex(FINFO) (FINFO->procOid != InvalidOid) + +#endif /* FUNCINDEX_H */ diff --git a/src/backend/access/genam.h b/src/backend/access/genam.h new file mode 100644 index 00000000000..b2544650de8 --- /dev/null +++ b/src/backend/access/genam.h @@ -0,0 +1,60 @@ +/*------------------------------------------------------------------------- + * + * genam.h-- + * POSTGRES general access method definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: genam.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef GENAM_H +#define GENAM_H + +#include "postgres.h" + +#include "access/attnum.h" +#include "access/htup.h" +#include "access/istrat.h" +#include "access/itup.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "access/sdir.h" +#include "access/funcindex.h" + +/* ---------------- + * generalized index_ interface routines + * ---------------- + */ +extern Relation index_open(Oid relationId); +extern Relation index_openr(char *relationName); +extern void index_close(Relation relation); +extern InsertIndexResult index_insert(Relation relation, + IndexTuple indexTuple); +extern void index_delete(Relation relation, ItemPointer indexItem); +extern IndexScanDesc index_beginscan(Relation relation, bool scanFromEnd, + uint16 numberOfKeys, ScanKey key); +extern void index_rescan(IndexScanDesc scan, bool scanFromEnd, ScanKey key); +extern void index_endscan(IndexScanDesc scan); +extern void index_markpos(IndexScanDesc scan); +extern void index_restrpos(IndexScanDesc scan); +extern RetrieveIndexResult index_getnext(IndexScanDesc scan, + ScanDirection direction); +extern RegProcedure index_getprocid(Relation irel, AttrNumber attnum, + uint16 procnum); +extern Datum GetIndexValue(HeapTuple tuple, TupleDesc hTupDesc, + int attOff, AttrNumber attrNums[], FuncIndexInfo *fInfo, + bool *attNull, Buffer buffer); + +/* in genam.c */ +extern IndexScanDesc RelationGetIndexScan(Relation relation, bool scanFromEnd, + uint16 numberOfKeys, ScanKey key); +extern void IndexScanRestart(IndexScanDesc scan, bool scanFromEnd, + ScanKey key); +extern void IndexScanEnd(IndexScanDesc scan); +extern void IndexScanMarkPosition(IndexScanDesc scan); +extern void IndexScanRestorePosition(IndexScanDesc scan); + +#endif /* GENAM_H */ diff --git a/src/backend/access/hash.h b/src/backend/access/hash.h new file mode 100644 index 00000000000..21407696b44 --- /dev/null +++ b/src/backend/access/hash.h @@ -0,0 +1,336 @@ +/*------------------------------------------------------------------------- + * + * hash.h-- + * header file for postgres hash access method implementation + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: hash.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + * NOTES + * modeled after Margo Seltzer's hash implementation for unix. + * + *------------------------------------------------------------------------- + */ +#ifndef HASH_H +#define HASH_H + +#include "access/itup.h" + +/* + * An overflow page is a spare page allocated for storing data whose + * bucket doesn't have room to store it. We use overflow pages rather + * than just splitting the bucket because there is a linear order in + * the way we split buckets. In other words, if there isn't enough space + * in the bucket itself, put it in an overflow page. + * + * Overflow page addresses are stored in form: (Splitnumber, Page offset). + * + * A splitnumber is the number of the generation where the table doubles + * in size. The ovflpage's offset within the splitnumber; offsets start + * at 1. + * + * We convert the stored bitmap address into a page address with the + * macro OADDR_OF(S, O) where S is the splitnumber and O is the page + * offset. + */ +typedef uint32 Bucket; +typedef bits16 OverflowPageAddress; +typedef uint32 SplitNumber; +typedef uint32 PageOffset; + +/* A valid overflow address will always have a page offset >= 1 */ +#define InvalidOvflAddress 0 + +#define SPLITSHIFT 11 +#define SPLITMASK 0x7FF +#define SPLITNUM(N) ((SplitNumber)(((uint32)(N)) >> SPLITSHIFT)) +#define OPAGENUM(N) ((PageOffset)((N) & SPLITMASK)) +#define OADDR_OF(S,O) ((OverflowPageAddress)((uint32)((uint32)(S) << SPLITSHIFT) + (O))) + +#define BUCKET_TO_BLKNO(B) \ + ((Bucket) ((B) + ((B) ? metap->SPARES[_hash_log2((B)+1)-1] : 0)) + 1) +#define OADDR_TO_BLKNO(B) \ + ((BlockNumber) \ + (BUCKET_TO_BLKNO ( (1 << SPLITNUM((B))) -1 ) + OPAGENUM((B)))); + +/* + * hasho_flag tells us which type of page we're looking at. For + * example, knowing overflow pages from bucket pages is necessary + * information when you're deleting tuples from a page. If all the + * tuples are deleted from an overflow page, the overflow is made + * available to other buckets by calling _hash_freeovflpage(). If all + * the tuples are deleted from a bucket page, no additional action is + * necessary. + */ + +#define LH_UNUSED_PAGE (0) +#define LH_OVERFLOW_PAGE (1 << 0) +#define LH_BUCKET_PAGE (1 << 1) +#define LH_BITMAP_PAGE (1 << 2) +#define LH_META_PAGE (1 << 3) + +typedef struct HashPageOpaqueData { + bits16 hasho_flag; /* is this page a bucket or ovfl */ + Bucket hasho_bucket; /* bucket number this pg belongs to */ + OverflowPageAddress hasho_oaddr; /* ovfl address of this ovfl pg */ + BlockNumber hasho_nextblkno; /* next ovfl blkno */ + BlockNumber hasho_prevblkno; /* previous ovfl (or bucket) blkno */ +} HashPageOpaqueData; + +typedef HashPageOpaqueData *HashPageOpaque; + +/* + * ScanOpaqueData is used to remember which buffers we're currently + * examining in the scan. We keep these buffers locked and pinned and + * recorded in the opaque entry of the scan in order to avoid doing a + * ReadBuffer() for every tuple in the index. This avoids semop() calls, + * which are expensive. + */ + +typedef struct HashScanOpaqueData { + Buffer hashso_curbuf; + Buffer hashso_mrkbuf; +} HashScanOpaqueData; + +typedef HashScanOpaqueData *HashScanOpaque; + +/* + * Definitions for metapage. + */ + +#define HASH_METAPAGE 0 /* metapage is always block 0 */ + +#define HASH_MAGIC 0x6440640 +#define HASH_VERSION 0 + +/* + * NCACHED is used to set the array sizeof spares[] & bitmaps[]. + * + * Spares[] is used to hold the number overflow pages currently + * allocated at a certain splitpoint. For example, if spares[3] = 7 + * then there are a maximum of 7 ovflpages available at splitpoint 3. + * The value in spares[] will change as ovflpages are added within + * a splitpoint. + * + * Within a splitpoint, one can find which ovflpages are available and + * which are used by looking at a bitmaps that are stored on the ovfl + * pages themselves. There is at least one bitmap for every splitpoint's + * ovflpages. Bitmaps[] contains the ovflpage addresses of the ovflpages + * that hold the ovflpage bitmaps. + * + * The reason that the size is restricted to NCACHED (32) is because + * the bitmaps are 16 bits: upper 5 represent the splitpoint, lower 11 + * indicate the page number within the splitpoint. Since there are + * only 5 bits to store the splitpoint, there can only be 32 splitpoints. + * Both spares[] and bitmaps[] use splitpoints as there indices, so there + * can only be 32 of them. + */ + +#define NCACHED 32 + + +typedef struct HashMetaPageData { + PageHeaderData hashm_phdr; /* pad for page header + (do not use) */ + uint32 hashm_magic; /* magic no. for hash tables */ + uint32 hashm_version; /* version ID */ + uint32 hashm_nkeys; /* number of keys stored in + the table */ + uint16 hashm_ffactor; /* fill factor */ + uint16 hashm_bsize; /* bucket size (bytes) - + must be a power of 2 */ + uint16 hashm_bshift; /* bucket shift */ + uint16 hashm_bmsize; /* bitmap array size (bytes) - + must be a power of 2 */ + uint32 hashm_maxbucket; /* ID of maximum bucket + in use */ + uint32 hashm_highmask; /* mask to modulo into + entire table */ + uint32 hashm_lowmask; /* mask to modulo into lower + half of table */ + uint32 hashm_ovflpoint; /* pageno. from which ovflpgs + being allocated */ + uint32 hashm_lastfreed; /* last ovflpage freed */ + uint32 hashm_nmaps; /* Initial number of bitmaps */ + uint32 hashm_spares[NCACHED]; /* spare pages available at + splitpoints */ + BlockNumber hashm_mapp[NCACHED]; /* blknumbers of ovfl page + maps */ + RegProcedure hashm_procid; /* hash procedure id from + pg_proc */ +} HashMetaPageData; + +typedef HashMetaPageData *HashMetaPage; + +/* Short hands for accessing structure */ +#define BSHIFT hashm_bshift +#define OVFL_POINT hashm_ovflpoint +#define LAST_FREED hashm_lastfreed +#define MAX_BUCKET hashm_maxbucket +#define FFACTOR hashm_ffactor +#define HIGH_MASK hashm_highmask +#define LOW_MASK hashm_lowmask +#define NKEYS hashm_nkeys +#define SPARES hashm_spares + +extern bool BuildingHash; + +typedef struct HashItemData { + IndexTupleData hash_itup; +} HashItemData; + +typedef HashItemData *HashItem; + +/* + * Constants + */ +#define DEFAULT_FFACTOR 300 +#define SPLITMAX 8 +#define BYTE_TO_BIT 3 /* 2^3 bits/byte */ +#define INT_TO_BYTE 2 /* 2^2 bytes/int */ +#define INT_TO_BIT 5 /* 2^5 bits/int */ +#define ALL_SET ((uint32) ~0) + +/* + * bitmap pages do not contain tuples. they do contain the standard + * page headers and trailers; however, everything in between is a + * giant bit array. the number of bits that fit on a page obviously + * depends on the page size and the header/trailer overhead. + */ +#define BMPGSZ_BYTE(metap) ((metap)->hashm_bmsize) +#define BMPGSZ_BIT(metap) ((metap)->hashm_bmsize << BYTE_TO_BIT) +#define HashPageGetBitmap(pg) \ + ((uint32 *) (((char *) (pg)) + DOUBLEALIGN(sizeof(PageHeaderData)))) + +/* + * The number of bits in an ovflpage bitmap which + * tells which ovflpages are empty versus in use (NOT the number of + * bits in an overflow page *address* bitmap). + */ +#define BITS_PER_MAP 32 /* Number of bits in ovflpage bitmap */ + +/* Given the address of the beginning of a big map, clear/set the nth bit */ +#define CLRBIT(A, N) ((A)[(N)/BITS_PER_MAP] &= ~(1<<((N)%BITS_PER_MAP))) +#define SETBIT(A, N) ((A)[(N)/BITS_PER_MAP] |= (1<<((N)%BITS_PER_MAP))) +#define ISSET(A, N) ((A)[(N)/BITS_PER_MAP] & (1<<((N)%BITS_PER_MAP))) + +/* + * page locking modes + */ +#define HASH_READ 0 +#define HASH_WRITE 1 + +/* + * In general, the hash code tries to localize its knowledge about page + * layout to a couple of routines. However, we need a special value to + * indicate "no page number" in those places where we expect page numbers. + */ + +#define P_NONE 0 + +/* + * Strategy number. There's only one valid strategy for hashing: equality. + */ + +#define HTEqualStrategyNumber 1 +#define HTMaxStrategyNumber 1 + +/* + * When a new operator class is declared, we require that the user supply + * us with an amproc procudure for hashing a key of the new type. + * Since we only have one such proc in amproc, it's number 1. + */ + +#define HASHPROC 1 + +/* public routines */ + +extern void hashbuild(Relation heap, Relation index, int natts, + AttrNumber *attnum, IndexStrategy istrat, uint16 pcount, + Datum *params, FuncIndexInfo *finfo, PredInfo *predInfo); +extern InsertIndexResult hashinsert(Relation rel, IndexTuple itup); +extern char *hashgettuple(IndexScanDesc scan, ScanDirection dir); +extern char *hashbeginscan(Relation rel, bool fromEnd, uint16 keysz, + ScanKey scankey); +extern void hashrescan(IndexScanDesc scan, bool fromEnd, ScanKey scankey); +extern void hashendscan(IndexScanDesc scan); +extern void hashmarkpos(IndexScanDesc scan); +extern void hashrestrpos(IndexScanDesc scan); +extern void hashdelete(Relation rel, ItemPointer tid); + +/* hashfunc.c */ +extern uint32 hashint2(int16 key); +extern uint32 hashint4(uint32 key); +extern uint32 hashfloat4(float32 keyp); +extern uint32 hashfloat8(float64 keyp); +extern uint32 hashoid(Oid key); +extern uint32 hashchar(char key); +extern uint32 hashchar2(uint16 intkey); +extern uint32 hashchar4(uint32 intkey); +extern uint32 hashchar8(char *key); +extern uint32 hashchar16(char *key); +extern uint32 hashtext(struct varlena *key); + +/* private routines */ + +/* hashinsert.c */ +extern InsertIndexResult _hash_doinsert(Relation rel, HashItem hitem); + + +/* hashovfl.c */ +extern Buffer _hash_addovflpage(Relation rel, Buffer *metabufp, Buffer buf); +extern Buffer _hash_freeovflpage(Relation rel, Buffer ovflbuf); +extern int32 _hash_initbitmap(Relation rel, HashMetaPage metap, int32 pnum, + int32 nbits, int32 ndx); +extern void _hash_squeezebucket(Relation rel, HashMetaPage metap, + Bucket bucket); + + +/* hashpage.c */ +extern void _hash_metapinit(Relation rel); +extern Buffer _hash_getbuf(Relation rel, BlockNumber blkno, int access); +extern void _hash_relbuf(Relation rel, Buffer buf, int access); +extern void _hash_wrtbuf(Relation rel, Buffer buf); +extern void _hash_wrtnorelbuf(Relation rel, Buffer buf); +extern Page _hash_chgbufaccess(Relation rel, Buffer *bufp, int from_access, + int to_access); +extern void _hash_pageinit(Page page, Size size); +extern void _hash_pagedel(Relation rel, ItemPointer tid); +extern void _hash_expandtable(Relation rel, Buffer metabuf); + + +/* hashscan.c */ +extern void _hash_regscan(IndexScanDesc scan); +extern void _hash_dropscan(IndexScanDesc scan); +extern void _hash_adjscans(Relation rel, ItemPointer tid); + + +/* hashsearch.c */ +extern void _hash_search(Relation rel, int keysz, ScanKey scankey, + Buffer *bufP, HashMetaPage metap); +extern RetrieveIndexResult _hash_next(IndexScanDesc scan, ScanDirection dir); +extern RetrieveIndexResult _hash_first(IndexScanDesc scan, ScanDirection dir); +extern bool _hash_step(IndexScanDesc scan, Buffer *bufP, ScanDirection dir, + Buffer metabuf); + + +/* hashstrat.c */ +extern StrategyNumber _hash_getstrat(Relation rel, AttrNumber attno, + RegProcedure proc); +extern bool _hash_invokestrat(Relation rel, AttrNumber attno, + StrategyNumber strat, Datum left, Datum right); + + +/* hashutil.c */ +extern ScanKey _hash_mkscankey(Relation rel, IndexTuple itup, + HashMetaPage metap); +extern void _hash_freeskey(ScanKey skey); +extern bool _hash_checkqual(IndexScanDesc scan, IndexTuple itup); +extern HashItem _hash_formitem(IndexTuple itup); +extern Bucket _hash_call(Relation rel, HashMetaPage metap, Datum key); +extern uint32 _hash_log2(uint32 num); +extern void _hash_checkpage(Page page, int flags); + +#endif /* HASH_H */ diff --git a/src/backend/access/hash/Makefile.inc b/src/backend/access/hash/Makefile.inc new file mode 100644 index 00000000000..8ea221bc264 --- /dev/null +++ b/src/backend/access/hash/Makefile.inc @@ -0,0 +1,18 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for access/hash (hash access method) +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/access/hash/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:10 scrappy Exp $ +# +#------------------------------------------------------------------------- + +SUBSRCS+= hash.c hashfunc.c hashinsert.c hashovfl.c hashpage.c hashscan.c \ + hashsearch.c hashstrat.c hashutil.c + + + diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c new file mode 100644 index 00000000000..a4a4e16e599 --- /dev/null +++ b/src/backend/access/hash/hash.c @@ -0,0 +1,467 @@ +/*------------------------------------------------------------------------- + * + * hash.c-- + * Implementation of Margo Seltzer's Hashing package for postgres. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/hash/hash.c,v 1.1.1.1 1996/07/09 06:21:10 scrappy Exp $ + * + * NOTES + * This file contains only the public interface routines. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/excid.h" +#include "access/heapam.h" +#include "access/genam.h" +#include "access/sdir.h" +#include "access/hash.h" +#include "access/funcindex.h" +#include "nodes/execnodes.h" +#include "nodes/plannodes.h" +#include "executor/executor.h" +#include "executor/tuptable.h" +#include "catalog/index.h" + + +bool BuildingHash = false; + +/* + * hashbuild() -- build a new hash index. + * + * We use a global variable to record the fact that we're creating + * a new index. This is used to avoid high-concurrency locking, + * since the index won't be visible until this transaction commits + * and since building is guaranteed to be single-threaded. + */ +void +hashbuild(Relation heap, + Relation index, + int natts, + AttrNumber *attnum, + IndexStrategy istrat, + uint16 pcount, + Datum *params, + FuncIndexInfo *finfo, + PredInfo *predInfo) +{ + HeapScanDesc hscan; + Buffer buffer; + HeapTuple htup; + IndexTuple itup; + TupleDesc htupdesc, itupdesc; + Datum *attdata; + bool *nulls; + InsertIndexResult res; + int nhtups, nitups; + int i; + HashItem hitem; + ExprContext *econtext; + TupleTable tupleTable; + TupleTableSlot *slot; + Oid hrelid, irelid; + Node *pred, *oldPred; + + /* note that this is a new btree */ + BuildingHash = true; + + pred = predInfo->pred; + oldPred = predInfo->oldPred; + + /* initialize the hash index metadata page (if this is a new index) */ + if (oldPred == NULL) + _hash_metapinit(index); + + /* get tuple descriptors for heap and index relations */ + htupdesc = RelationGetTupleDescriptor(heap); + itupdesc = RelationGetTupleDescriptor(index); + + /* get space for data items that'll appear in the index tuple */ + attdata = (Datum *) palloc(natts * sizeof(Datum)); + nulls = (bool *) palloc(natts * sizeof(bool)); + + /* + * If this is a predicate (partial) index, we will need to evaluate the + * predicate using ExecQual, which requires the current tuple to be in a + * slot of a TupleTable. In addition, ExecQual must have an ExprContext + * referring to that slot. Here, we initialize dummy TupleTable and + * ExprContext objects for this purpose. --Nels, Feb '92 + */ +#ifndef OMIT_PARTIAL_INDEX + if (pred != NULL || oldPred != NULL) { + tupleTable = ExecCreateTupleTable(1); + slot = ExecAllocTableSlot(tupleTable); + econtext = makeNode(ExprContext); + FillDummyExprContext(econtext, slot, htupdesc, buffer); + } +#endif /* OMIT_PARTIAL_INDEX */ + + /* start a heap scan */ + hscan = heap_beginscan(heap, 0, NowTimeQual, 0, (ScanKey) NULL); + htup = heap_getnext(hscan, 0, &buffer); + + /* build the index */ + nhtups = nitups = 0; + + for (; HeapTupleIsValid(htup); htup = heap_getnext(hscan, 0, &buffer)) { + + nhtups++; + + /* + * If oldPred != NULL, this is an EXTEND INDEX command, so skip + * this tuple if it was already in the existing partial index + */ + if (oldPred != NULL) { + /*SetSlotContents(slot, htup); */ +#ifndef OMIT_PARTIAL_INDEX + slot->val = htup; + if (ExecQual((List*)oldPred, econtext) == true) { + nitups++; + continue; + } +#endif /* OMIT_PARTIAL_INDEX */ + } + + /* Skip this tuple if it doesn't satisfy the partial-index predicate */ + if (pred != NULL) { +#ifndef OMIT_PARTIAL_INDEX + /*SetSlotContents(slot, htup); */ + slot->val = htup; + if (ExecQual((List*)pred, econtext) == false) + continue; +#endif /* OMIT_PARTIAL_INDEX */ +} + + nitups++; + + /* + * For the current heap tuple, extract all the attributes + * we use in this index, and note which are null. + */ + for (i = 1; i <= natts; i++) { + int attoff; + bool attnull; + + /* + * Offsets are from the start of the tuple, and are + * zero-based; indices are one-based. The next call + * returns i - 1. That's data hiding for you. + */ + + /* attoff = i - 1 */ + attoff = AttrNumberGetAttrOffset(i); + + /* below, attdata[attoff] set to equal some datum & + * attnull is changed to indicate whether or not the attribute + * is null for this tuple + */ + attdata[attoff] = GetIndexValue(htup, + htupdesc, + attoff, + attnum, + finfo, + &attnull, + buffer); + nulls[attoff] = (attnull ? 'n' : ' '); + } + + /* form an index tuple and point it at the heap tuple */ + itup = index_formtuple(itupdesc, attdata, nulls); + + /* + * If the single index key is null, we don't insert it into + * the index. Hash tables support scans on '='. + * Relational algebra says that A = B + * returns null if either A or B is null. This + * means that no qualification used in an index scan could ever + * return true on a null attribute. It also means that indices + * can't be used by ISNULL or NOTNULL scans, but that's an + * artifact of the strategy map architecture chosen in 1986, not + * of the way nulls are handled here. + */ + + if (itup->t_info & INDEX_NULL_MASK) { + pfree(itup); + continue; + } + + itup->t_tid = htup->t_ctid; + hitem = _hash_formitem(itup); + res = _hash_doinsert(index, hitem); + pfree(hitem); + pfree(itup); + pfree(res); + } + + /* okay, all heap tuples are indexed */ + heap_endscan(hscan); + + if (pred != NULL || oldPred != NULL) { +#ifndef OMIT_PARTIAL_INDEX + ExecDestroyTupleTable(tupleTable, true); + pfree(econtext); +#endif /* OMIT_PARTIAL_INDEX */ + } + + /* + * Since we just counted the tuples in the heap, we update its + * stats in pg_class to guarantee that the planner takes advantage + * of the index we just created. Finally, only update statistics + * during normal index definitions, not for indices on system catalogs + * created during bootstrap processing. We must close the relations + * before updatings statistics to guarantee that the relcache entries + * are flushed when we increment the command counter in UpdateStats(). + */ + if (IsNormalProcessingMode()) + { + hrelid = heap->rd_id; + irelid = index->rd_id; + heap_close(heap); + index_close(index); + UpdateStats(hrelid, nhtups, true); + UpdateStats(irelid, nitups, false); + if (oldPred != NULL) { + if (nitups == nhtups) pred = NULL; + UpdateIndexPredicate(irelid, oldPred, pred); + } + } + + /* be tidy */ + pfree(nulls); + pfree(attdata); + + /* all done */ + BuildingHash = false; +} + +/* + * hashinsert() -- insert an index tuple into a hash table. + * + * Hash on the index tuple's key, find the appropriate location + * for the new tuple, put it there, and return an InsertIndexResult + * to the caller. + */ +InsertIndexResult +hashinsert(Relation rel, IndexTuple itup) +{ + HashItem hitem; + InsertIndexResult res; + + if (itup->t_info & INDEX_NULL_MASK) + return ((InsertIndexResult) NULL); + + hitem = _hash_formitem(itup); + + res = _hash_doinsert(rel, hitem); + + pfree(hitem); + + return (res); +} + + +/* + * hashgettuple() -- Get the next tuple in the scan. + */ +char * +hashgettuple(IndexScanDesc scan, ScanDirection dir) +{ + RetrieveIndexResult res; + + /* + * If we've already initialized this scan, we can just advance it + * in the appropriate direction. If we haven't done so yet, we + * call a routine to get the first item in the scan. + */ + + if (ItemPointerIsValid(&(scan->currentItemData))) + res = _hash_next(scan, dir); + else + res = _hash_first(scan, dir); + + return ((char *) res); +} + + +/* + * hashbeginscan() -- start a scan on a hash index + */ +char * +hashbeginscan(Relation rel, + bool fromEnd, + uint16 keysz, + ScanKey scankey) +{ + IndexScanDesc scan; + HashScanOpaque so; + + scan = RelationGetIndexScan(rel, fromEnd, keysz, scankey); + so = (HashScanOpaque) palloc(sizeof(HashScanOpaqueData)); + so->hashso_curbuf = so->hashso_mrkbuf = InvalidBuffer; + scan->opaque = so; + scan->flags = 0x0; + + /* register scan in case we change pages it's using */ + _hash_regscan(scan); + + return ((char *) scan); +} + +/* + * hashrescan() -- rescan an index relation + */ +void +hashrescan(IndexScanDesc scan, bool fromEnd, ScanKey scankey) +{ + ItemPointer iptr; + HashScanOpaque so; + + so = (HashScanOpaque) scan->opaque; + + /* we hold a read lock on the current page in the scan */ + if (ItemPointerIsValid(iptr = &(scan->currentItemData))) { + _hash_relbuf(scan->relation, so->hashso_curbuf, HASH_READ); + so->hashso_curbuf = InvalidBuffer; + ItemPointerSetInvalid(iptr); + } + if (ItemPointerIsValid(iptr = &(scan->currentMarkData))) { + _hash_relbuf(scan->relation, so->hashso_mrkbuf, HASH_READ); + so->hashso_mrkbuf = InvalidBuffer; + ItemPointerSetInvalid(iptr); + } + + /* reset the scan key */ + if (scan->numberOfKeys > 0) { + memmove(scan->keyData, + scankey, + scan->numberOfKeys * sizeof(ScanKeyData)); + } +} + +/* + * hashendscan() -- close down a scan + */ +void +hashendscan(IndexScanDesc scan) +{ + + ItemPointer iptr; + HashScanOpaque so; + + so = (HashScanOpaque) scan->opaque; + + /* release any locks we still hold */ + if (ItemPointerIsValid(iptr = &(scan->currentItemData))) { + _hash_relbuf(scan->relation, so->hashso_curbuf, HASH_READ); + so->hashso_curbuf = InvalidBuffer; + ItemPointerSetInvalid(iptr); + } + + if (ItemPointerIsValid(iptr = &(scan->currentMarkData))) { + if (BufferIsValid(so->hashso_mrkbuf)) + _hash_relbuf(scan->relation, so->hashso_mrkbuf, HASH_READ); + so->hashso_mrkbuf = InvalidBuffer; + ItemPointerSetInvalid(iptr); + } + + /* don't need scan registered anymore */ + _hash_dropscan(scan); + + /* be tidy */ +#ifdef PERFECT_MMGR + pfree (scan->opaque); +#endif /* PERFECT_MMGR */ +} + +/* + * hashmarkpos() -- save current scan position + * + */ +void +hashmarkpos(IndexScanDesc scan) +{ + ItemPointer iptr; + HashScanOpaque so; + + /* see if we ever call this code. if we do, then so_mrkbuf a + * useful element in the scan->opaque structure. if this procedure + * is never called, so_mrkbuf should be removed from the scan->opaque + * structure. + */ + elog(NOTICE, "Hashmarkpos() called."); + + so = (HashScanOpaque) scan->opaque; + + /* release lock on old marked data, if any */ + if (ItemPointerIsValid(iptr = &(scan->currentMarkData))) { + _hash_relbuf(scan->relation, so->hashso_mrkbuf, HASH_READ); + so->hashso_mrkbuf = InvalidBuffer; + ItemPointerSetInvalid(iptr); + } + + /* bump lock on currentItemData and copy to currentMarkData */ + if (ItemPointerIsValid(&(scan->currentItemData))) { + so->hashso_mrkbuf = _hash_getbuf(scan->relation, + BufferGetBlockNumber(so->hashso_curbuf), + HASH_READ); + scan->currentMarkData = scan->currentItemData; + } +} + +/* + * hashrestrpos() -- restore scan to last saved position + */ +void +hashrestrpos(IndexScanDesc scan) +{ + ItemPointer iptr; + HashScanOpaque so; + + /* see if we ever call this code. if we do, then so_mrkbuf a + * useful element in the scan->opaque structure. if this procedure + * is never called, so_mrkbuf should be removed from the scan->opaque + * structure. + */ + elog(NOTICE, "Hashrestrpos() called."); + + so = (HashScanOpaque) scan->opaque; + + /* release lock on current data, if any */ + if (ItemPointerIsValid(iptr = &(scan->currentItemData))) { + _hash_relbuf(scan->relation, so->hashso_curbuf, HASH_READ); + so->hashso_curbuf = InvalidBuffer; + ItemPointerSetInvalid(iptr); + } + + /* bump lock on currentMarkData and copy to currentItemData */ + if (ItemPointerIsValid(&(scan->currentMarkData))) { + so->hashso_curbuf = + _hash_getbuf(scan->relation, + BufferGetBlockNumber(so->hashso_mrkbuf), + HASH_READ); + + scan->currentItemData = scan->currentMarkData; + } +} + +/* stubs */ +void +hashdelete(Relation rel, ItemPointer tid) +{ + /* adjust any active scans that will be affected by this deletion */ + _hash_adjscans(rel, tid); + + /* delete the data from the page */ + _hash_pagedel(rel, tid); +} + diff --git a/src/backend/access/hash/hashfunc.c b/src/backend/access/hash/hashfunc.c new file mode 100644 index 00000000000..6b37de29911 --- /dev/null +++ b/src/backend/access/hash/hashfunc.c @@ -0,0 +1,276 @@ +/*------------------------------------------------------------------------- + * + * hashfunc.c-- + * Comparison functions for hash access method. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/hash/hashfunc.c,v 1.1.1.1 1996/07/09 06:21:10 scrappy Exp $ + * + * NOTES + * These functions are stored in pg_amproc. For each operator class + * defined on hash tables, they compute the hash value of the argument. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" +#include "utils/nabstime.h" + +uint32 hashint2(int16 key) +{ + return ((uint32) ~key); +} + +uint32 hashint4(uint32 key) +{ + return (~key); +} + +/* Hash function from Chris Torek. */ +uint32 hashfloat4(float32 keyp) +{ + int len; + int loop; + uint32 h; + char *kp = (char *) keyp; + + len = sizeof(float32data); + +#define HASH4a h = (h << 5) - h + *kp++; +#define HASH4b h = (h << 5) + h + *kp++; +#define HASH4 HASH4b + + + h = 0; + if (len > 0) { + loop = (len + 8 - 1) >> 3; + + switch (len & (8 - 1)) { + case 0: + do { /* All fall throughs */ + HASH4; + case 7: + HASH4; + case 6: + HASH4; + case 5: + HASH4; + case 4: + HASH4; + case 3: + HASH4; + case 2: + HASH4; + case 1: + HASH4; + } while (--loop); + } + } + return (h); +} + + +uint32 hashfloat8(float64 keyp) +{ + int len; + int loop; + uint32 h; + char *kp = (char *) keyp; + + len = sizeof(float64data); + +#define HASH4a h = (h << 5) - h + *kp++; +#define HASH4b h = (h << 5) + h + *kp++; +#define HASH4 HASH4b + + + h = 0; + if (len > 0) { + loop = (len + 8 - 1) >> 3; + + switch (len & (8 - 1)) { + case 0: + do { /* All fall throughs */ + HASH4; + case 7: + HASH4; + case 6: + HASH4; + case 5: + HASH4; + case 4: + HASH4; + case 3: + HASH4; + case 2: + HASH4; + case 1: + HASH4; + } while (--loop); + } + } + return (h); +} + + +uint32 hashoid(Oid key) +{ + return ((uint32) ~key); +} + + +uint32 hashchar(char key) +{ + int len; + uint32 h; + + len = sizeof(char); + +#define PRIME1 37 +#define PRIME2 1048583 + + h = 0; + /* Convert char to integer */ + h = h * PRIME1 ^ (key - ' '); + h %= PRIME2; + + return (h); +} + +uint32 hashchar2(uint16 intkey) +{ + uint32 h; + int len; + char *key = (char *) &intkey; + + h = 0; + len = sizeof(uint16); + /* Convert string to integer */ + while (len--) + h = h * PRIME1 ^ (*key++ - ' '); + h %= PRIME2; + + return (h); +} + +uint32 hashchar4(uint32 intkey) +{ + uint32 h; + int len; + char *key = (char *) &intkey; + + h = 0; + len = sizeof(uint32); + /* Convert string to integer */ + while (len--) + h = h * PRIME1 ^ (*key++ - ' '); + h %= PRIME2; + + return (h); +} + +uint32 hashchar8(char *key) +{ + uint32 h; + int len; + + h = 0; + len = sizeof(char8); + /* Convert string to integer */ + while (len--) + h = h * PRIME1 ^ (*key++ - ' '); + h %= PRIME2; + + return (h); +} + +uint32 hashname(NameData *n) +{ + uint32 h; + int len; + char *key; + + key = n->data; + + h = 0; + len = NAMEDATALEN; + /* Convert string to integer */ + while (len--) + h = h * PRIME1 ^ (*key++ - ' '); + h %= PRIME2; + + return (h); +} + + +uint32 hashchar16(char *key) +{ + uint32 h; + int len; + + h = 0; + len = sizeof(char16); + /* Convert string to integer */ + while (len--) + h = h * PRIME1 ^ (*key++ - ' '); + h %= PRIME2; + + return (h); +} + + +/* + * (Comment from the original db3 hashing code: ) + * + * "This is INCREDIBLY ugly, but fast. We break the string up into 8 byte + * units. On the first time through the loop we get the 'leftover bytes' + * (strlen % 8). On every other iteration, we perform 8 HASHC's so we handle + * all 8 bytes. Essentially, this saves us 7 cmp & branch instructions. If + * this routine is heavily used enough, it's worth the ugly coding. + * + * "OZ's original sdbm hash" + */ +uint32 hashtext(struct varlena *key) +{ + int keylen; + char *keydata; + uint32 n; + int loop; + + keydata = VARDATA(key); + keylen = VARSIZE(key); + + /* keylen includes the four bytes in which string keylength is stored */ + keylen -= sizeof(VARSIZE(key)); + +#define HASHC n = *keydata++ + 65599 * n + + n = 0; + if (keylen > 0) { + loop = (keylen + 8 - 1) >> 3; + + switch (keylen & (8 - 1)) { + case 0: + do { /* All fall throughs */ + HASHC; + case 7: + HASHC; + case 6: + HASHC; + case 5: + HASHC; + case 4: + HASHC; + case 3: + HASHC; + case 2: + HASHC; + case 1: + HASHC; + } while (--loop); + } + } + return (n); +} diff --git a/src/backend/access/hash/hashinsert.c b/src/backend/access/hash/hashinsert.c new file mode 100644 index 00000000000..c514cc614d8 --- /dev/null +++ b/src/backend/access/hash/hashinsert.c @@ -0,0 +1,239 @@ +/*------------------------------------------------------------------------- + * + * hashinsert.c-- + * Item insertion in hash tables for Postgres. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/hash/hashinsert.c,v 1.1.1.1 1996/07/09 06:21:10 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/hash.h" + +static InsertIndexResult _hash_insertonpg(Relation rel, Buffer buf, int keysz, ScanKey scankey, HashItem hitem, Buffer metabuf); +static OffsetNumber _hash_pgaddtup(Relation rel, Buffer buf, int keysz, ScanKey itup_scankey, Size itemsize, HashItem hitem); + +/* + * _hash_doinsert() -- Handle insertion of a single HashItem in the table. + * + * This routine is called by the public interface routines, hashbuild + * and hashinsert. By here, hashitem is filled in, and has a unique + * (xid, seqno) pair. The datum to be used as a "key" is in the + * hashitem. + */ +InsertIndexResult +_hash_doinsert(Relation rel, HashItem hitem) +{ + Buffer buf; + Buffer metabuf; + BlockNumber blkno; + HashMetaPage metap; + IndexTuple itup; + InsertIndexResult res; + ScanKey itup_scankey; + int natts; + Page page; + + metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ); + metap = (HashMetaPage) BufferGetPage(metabuf); + _hash_checkpage((Page) metap, LH_META_PAGE); + + /* we need a scan key to do our search, so build one */ + itup = &(hitem->hash_itup); + if ((natts = rel->rd_rel->relnatts) != 1) + elog(WARN, "Hash indices valid for only one index key."); + itup_scankey = _hash_mkscankey(rel, itup, metap); + + /* + * find the first page in the bucket chain containing this key and + * place it in buf. _hash_search obtains a read lock for us. + */ + _hash_search(rel, natts, itup_scankey, &buf, metap); + page = BufferGetPage(buf); + _hash_checkpage(page, LH_BUCKET_PAGE); + + /* + * trade in our read lock for a write lock so that we can do the + * insertion. + */ + blkno = BufferGetBlockNumber(buf); + _hash_relbuf(rel, buf, HASH_READ); + buf = _hash_getbuf(rel, blkno, HASH_WRITE); + + + /* + * XXX btree comment (haven't decided what to do in hash): don't + * think the bucket can be split while we're reading the metapage. + * + * If the page was split between the time that we surrendered our + * read lock and acquired our write lock, then this page may no + * longer be the right place for the key we want to insert. + */ + + /* do the insertion */ + res = _hash_insertonpg(rel, buf, natts, itup_scankey, + hitem, metabuf); + + /* be tidy */ + _hash_freeskey(itup_scankey); + + return (res); +} + +/* + * _hash_insertonpg() -- Insert a tuple on a particular page in the table. + * + * This recursive procedure does the following things: + * + * + if necessary, splits the target page. + * + inserts the tuple. + * + * On entry, we must have the right buffer on which to do the + * insertion, and the buffer must be pinned and locked. On return, + * we will have dropped both the pin and the write lock on the buffer. + * + */ +static InsertIndexResult +_hash_insertonpg(Relation rel, + Buffer buf, + int keysz, + ScanKey scankey, + HashItem hitem, + Buffer metabuf) +{ + InsertIndexResult res; + Page page; + BlockNumber itup_blkno; + OffsetNumber itup_off; + int itemsz; + HashPageOpaque pageopaque; + bool do_expand = false; + Buffer ovflbuf; + HashMetaPage metap; + Bucket bucket; + + metap = (HashMetaPage) BufferGetPage(metabuf); + _hash_checkpage((Page) metap, LH_META_PAGE); + + page = BufferGetPage(buf); + _hash_checkpage(page, LH_BUCKET_PAGE|LH_OVERFLOW_PAGE); + pageopaque = (HashPageOpaque) PageGetSpecialPointer(page); + bucket = pageopaque->hasho_bucket; + + itemsz = IndexTupleDSize(hitem->hash_itup) + + (sizeof(HashItemData) - sizeof(IndexTupleData)); + itemsz = DOUBLEALIGN(itemsz); + + while (PageGetFreeSpace(page) < itemsz) { + /* + * no space on this page; check for an overflow page + */ + if (BlockNumberIsValid(pageopaque->hasho_nextblkno)) { + /* + * ovfl page exists; go get it. if it doesn't have room, + * we'll find out next pass through the loop test above. + */ + ovflbuf = _hash_getbuf(rel, pageopaque->hasho_nextblkno, + HASH_WRITE); + _hash_relbuf(rel, buf, HASH_WRITE); + buf = ovflbuf; + page = BufferGetPage(buf); + } else { + /* + * we're at the end of the bucket chain and we haven't + * found a page with enough room. allocate a new overflow + * page. + */ + do_expand = true; + ovflbuf = _hash_addovflpage(rel, &metabuf, buf); + _hash_relbuf(rel, buf, HASH_WRITE); + buf = ovflbuf; + page = BufferGetPage(buf); + + if (PageGetFreeSpace(page) < itemsz) { + /* it doesn't fit on an empty page -- give up */ + elog(WARN, "hash item too large"); + } + } + _hash_checkpage(page, LH_OVERFLOW_PAGE); + pageopaque = (HashPageOpaque) PageGetSpecialPointer(page); + Assert(pageopaque->hasho_bucket == bucket); + } + + itup_off = _hash_pgaddtup(rel, buf, keysz, scankey, itemsz, hitem); + itup_blkno = BufferGetBlockNumber(buf); + + /* by here, the new tuple is inserted */ + res = (InsertIndexResult) palloc(sizeof(InsertIndexResultData)); + + ItemPointerSet(&(res->pointerData), itup_blkno, itup_off); + + if (res != NULL) { + /* + * Increment the number of keys in the table. + * We switch lock access type just for a moment + * to allow greater accessibility to the metapage. + */ + metap = (HashMetaPage) _hash_chgbufaccess(rel, &metabuf, + HASH_READ, HASH_WRITE); + metap->hashm_nkeys += 1; + metap = (HashMetaPage) _hash_chgbufaccess(rel, &metabuf, + HASH_WRITE, HASH_READ); + + } + + _hash_wrtbuf(rel, buf); + + if (do_expand || + (metap->hashm_nkeys / (metap->hashm_maxbucket + 1)) + > metap->hashm_ffactor) { + _hash_expandtable(rel, metabuf); + } + _hash_relbuf(rel, metabuf, HASH_READ); + return (res); +} + +/* + * _hash_pgaddtup() -- add a tuple to a particular page in the index. + * + * This routine adds the tuple to the page as requested, and keeps the + * write lock and reference associated with the page's buffer. It is + * an error to call pgaddtup() without a write lock and reference. + */ +static OffsetNumber +_hash_pgaddtup(Relation rel, + Buffer buf, + int keysz, + ScanKey itup_scankey, + Size itemsize, + HashItem hitem) +{ + OffsetNumber itup_off; + Page page; + + page = BufferGetPage(buf); + _hash_checkpage(page, LH_BUCKET_PAGE|LH_OVERFLOW_PAGE); + + itup_off = OffsetNumberNext(PageGetMaxOffsetNumber(page)); + (void) PageAddItem(page, (Item) hitem, itemsize, itup_off, LP_USED); + + /* write the buffer, but hold our lock */ + _hash_wrtnorelbuf(rel, buf); + + return (itup_off); +} diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c new file mode 100644 index 00000000000..55ee9e9ce79 --- /dev/null +++ b/src/backend/access/hash/hashovfl.c @@ -0,0 +1,614 @@ +/*------------------------------------------------------------------------- + * + * hashovfl.c-- + * Overflow page management code for the Postgres hash access method + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/hash/hashovfl.c,v 1.1.1.1 1996/07/09 06:21:10 scrappy Exp $ + * + * NOTES + * Overflow pages look like ordinary relation pages. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "access/genam.h" +#include "access/hash.h" + +static OverflowPageAddress _hash_getovfladdr(Relation rel, Buffer *metabufp); +static uint32 _hash_firstfreebit(uint32 map); + +/* + * _hash_addovflpage + * + * Add an overflow page to the page currently pointed to by the buffer + * argument 'buf'. + * + * *Metabufp has a read lock upon entering the function; buf has a + * write lock. + * + */ +Buffer +_hash_addovflpage(Relation rel, Buffer *metabufp, Buffer buf) +{ + + OverflowPageAddress oaddr; + BlockNumber ovflblkno; + Buffer ovflbuf; + HashMetaPage metap; + HashPageOpaque ovflopaque; + HashPageOpaque pageopaque; + Page page; + Page ovflpage; + + /* this had better be the last page in a bucket chain */ + page = BufferGetPage(buf); + _hash_checkpage(page, LH_BUCKET_PAGE|LH_OVERFLOW_PAGE); + pageopaque = (HashPageOpaque) PageGetSpecialPointer(page); + Assert(!BlockNumberIsValid(pageopaque->hasho_nextblkno)); + + metap = (HashMetaPage) BufferGetPage(*metabufp); + _hash_checkpage((Page) metap, LH_META_PAGE); + + /* allocate an empty overflow page */ + oaddr = _hash_getovfladdr(rel, metabufp); + if (oaddr == InvalidOvflAddress) { + elog(WARN, "_hash_addovflpage: problem with _hash_getovfladdr."); + } + ovflblkno = OADDR_TO_BLKNO(OADDR_OF(SPLITNUM(oaddr), OPAGENUM(oaddr))); + Assert(BlockNumberIsValid(ovflblkno)); + ovflbuf = _hash_getbuf(rel, ovflblkno, HASH_WRITE); + Assert(BufferIsValid(ovflbuf)); + ovflpage = BufferGetPage(ovflbuf); + + /* initialize the new overflow page */ + _hash_pageinit(ovflpage, BufferGetPageSize(ovflbuf)); + ovflopaque = (HashPageOpaque) PageGetSpecialPointer(ovflpage); + ovflopaque->hasho_prevblkno = BufferGetBlockNumber(buf); + ovflopaque->hasho_nextblkno = InvalidBlockNumber; + ovflopaque->hasho_flag = LH_OVERFLOW_PAGE; + ovflopaque->hasho_oaddr = oaddr; + ovflopaque->hasho_bucket = pageopaque->hasho_bucket; + _hash_wrtnorelbuf(rel, ovflbuf); + + /* logically chain overflow page to previous page */ + pageopaque->hasho_nextblkno = ovflblkno; + _hash_wrtnorelbuf(rel, buf); + return (ovflbuf); +} + +/* + * _hash_getovfladdr() + * + * Find an available overflow page and return its address. + * + * When we enter this function, we have a read lock on *metabufp which + * we change to a write lock immediately. Before exiting, the write lock + * is exchanged for a read lock. + * + */ +static OverflowPageAddress +_hash_getovfladdr(Relation rel, Buffer *metabufp) +{ + HashMetaPage metap; + Buffer mapbuf; + BlockNumber blkno; + PageOffset offset; + OverflowPageAddress oaddr; + SplitNumber splitnum; + uint32 *freep; + uint32 max_free; + uint32 bit; + uint32 first_page; + uint32 free_bit; + uint32 free_page; + uint32 in_use_bits; + uint32 i, j; + + metap = (HashMetaPage) _hash_chgbufaccess(rel, metabufp, HASH_READ, HASH_WRITE); + + splitnum = metap->OVFL_POINT; + max_free = metap->SPARES[splitnum]; + + free_page = (max_free - 1) >> (metap->BSHIFT + BYTE_TO_BIT); + free_bit = (max_free - 1) & (BMPGSZ_BIT(metap) - 1); + + /* Look through all the free maps to find the first free block */ + first_page = metap->LAST_FREED >> (metap->BSHIFT + BYTE_TO_BIT); + for ( i = first_page; i <= free_page; i++ ) { + Page mappage; + + blkno = metap->hashm_mapp[i]; + mapbuf = _hash_getbuf(rel, blkno, HASH_WRITE); + mappage = BufferGetPage(mapbuf); + _hash_checkpage(mappage, LH_BITMAP_PAGE); + freep = HashPageGetBitmap(mappage); + Assert(freep); + + if (i == free_page) + in_use_bits = free_bit; + else + in_use_bits = BMPGSZ_BIT(metap) - 1; + + if (i == first_page) { + bit = metap->LAST_FREED & (BMPGSZ_BIT(metap) - 1); + j = bit / BITS_PER_MAP; + bit = bit & ~(BITS_PER_MAP - 1); + } else { + bit = 0; + j = 0; + } + for (; bit <= in_use_bits; j++, bit += BITS_PER_MAP) + if (freep[j] != ALL_SET) + goto found; + } + + /* No Free Page Found - have to allocate a new page */ + metap->LAST_FREED = metap->SPARES[splitnum]; + metap->SPARES[splitnum]++; + offset = metap->SPARES[splitnum] - + (splitnum ? metap->SPARES[splitnum - 1] : 0); + +#define OVMSG "HASH: Out of overflow pages. Out of luck.\n" + + if (offset > SPLITMASK) { + if (++splitnum >= NCACHED) { + elog(WARN, OVMSG); + } + metap->OVFL_POINT = splitnum; + metap->SPARES[splitnum] = metap->SPARES[splitnum-1]; + metap->SPARES[splitnum-1]--; + offset = 0; + } + + /* Check if we need to allocate a new bitmap page */ + if (free_bit == BMPGSZ_BIT(metap) - 1) { + /* won't be needing old map page */ + + _hash_relbuf(rel, mapbuf, HASH_WRITE); + + free_page++; + if (free_page >= NCACHED) { + elog(WARN, OVMSG); + } + + /* + * This is tricky. The 1 indicates that you want the new page + * allocated with 1 clear bit. Actually, you are going to + * allocate 2 pages from this map. The first is going to be + * the map page, the second is the overflow page we were + * looking for. The init_bitmap routine automatically, sets + * the first bit of itself to indicate that the bitmap itself + * is in use. We would explicitly set the second bit, but + * don't have to if we tell init_bitmap not to leave it clear + * in the first place. + */ + if (_hash_initbitmap(rel, metap, OADDR_OF(splitnum, offset), + 1, free_page)) { + elog(WARN, "overflow_page: problem with _hash_initbitmap."); + } + metap->SPARES[splitnum]++; + offset++; + if (offset > SPLITMASK) { + if (++splitnum >= NCACHED) { + elog(WARN, OVMSG); + } + metap->OVFL_POINT = splitnum; + metap->SPARES[splitnum] = metap->SPARES[splitnum-1]; + metap->SPARES[splitnum-1]--; + offset = 0; + } + } else { + + /* + * Free_bit addresses the last used bit. Bump it to address + * the first available bit. + */ + free_bit++; + SETBIT(freep, free_bit); + _hash_wrtbuf(rel, mapbuf); + } + + /* Calculate address of the new overflow page */ + oaddr = OADDR_OF(splitnum, offset); + _hash_chgbufaccess(rel, metabufp, HASH_WRITE, HASH_READ); + return (oaddr); + + found: + bit = bit + _hash_firstfreebit(freep[j]); + SETBIT(freep, bit); + _hash_wrtbuf(rel, mapbuf); + + /* + * Bits are addressed starting with 0, but overflow pages are addressed + * beginning at 1. Bit is a bit addressnumber, so we need to increment + * it to convert it to a page number. + */ + + bit = 1 + bit + (i * BMPGSZ_BIT(metap)); + if (bit >= metap->LAST_FREED) { + metap->LAST_FREED = bit - 1; + } + + /* Calculate the split number for this page */ + for (i = 0; (i < splitnum) && (bit > metap->SPARES[i]); i++) + ; + offset = (i ? bit - metap->SPARES[i - 1] : bit); + if (offset >= SPLITMASK) { + elog(WARN, OVMSG); + } + + /* initialize this page */ + oaddr = OADDR_OF(i, offset); + _hash_chgbufaccess(rel, metabufp, HASH_WRITE, HASH_READ); + return (oaddr); +} + +/* + * _hash_firstfreebit() + * + * Return the first bit that is not set in the argument 'map'. This + * function is used to find an available overflow page within a + * splitnumber. + * + */ +static uint32 +_hash_firstfreebit(uint32 map) +{ + uint32 i, mask; + + mask = 0x1; + for (i = 0; i < BITS_PER_MAP; i++) { + if (!(mask & map)) + return (i); + mask = mask << 1; + } + return (i); +} + +/* + * _hash_freeovflpage() - + * + * Mark this overflow page as free and return a buffer with + * the page that follows it (which may be defined as + * InvalidBuffer). + * + */ +Buffer +_hash_freeovflpage(Relation rel, Buffer ovflbuf) +{ + HashMetaPage metap; + Buffer metabuf; + Buffer mapbuf; + BlockNumber prevblkno; + BlockNumber blkno; + BlockNumber nextblkno; + HashPageOpaque ovflopaque; + Page ovflpage; + Page mappage; + OverflowPageAddress addr; + SplitNumber splitnum; + uint32 *freep; + uint32 ovflpgno; + int32 bitmappage, bitmapbit; + Bucket bucket; + + metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_WRITE); + metap = (HashMetaPage) BufferGetPage(metabuf); + _hash_checkpage((Page) metap, LH_META_PAGE); + + ovflpage = BufferGetPage(ovflbuf); + _hash_checkpage(ovflpage, LH_OVERFLOW_PAGE); + ovflopaque = (HashPageOpaque) PageGetSpecialPointer(ovflpage); + addr = ovflopaque->hasho_oaddr; + nextblkno = ovflopaque->hasho_nextblkno; + prevblkno = ovflopaque->hasho_prevblkno; + bucket = ovflopaque->hasho_bucket; + (void) memset(ovflpage, 0, BufferGetPageSize(ovflbuf)); + _hash_wrtbuf(rel, ovflbuf); + + /* + * fix up the bucket chain. this is a doubly-linked list, so we + * must fix up the bucket chain members behind and ahead of the + * overflow page being deleted. + * + * XXX this should look like: + * - lock prev/next + * - modify/write prev/next (how to do write ordering with a + * doubly-linked list???) + * - unlock prev/next + */ + if (BlockNumberIsValid(prevblkno)) { + Buffer prevbuf = _hash_getbuf(rel, prevblkno, HASH_WRITE); + Page prevpage = BufferGetPage(prevbuf); + HashPageOpaque prevopaque = + (HashPageOpaque) PageGetSpecialPointer(prevpage); + + _hash_checkpage(prevpage, LH_BUCKET_PAGE|LH_OVERFLOW_PAGE); + Assert(prevopaque->hasho_bucket == bucket); + prevopaque->hasho_nextblkno = nextblkno; + _hash_wrtbuf(rel, prevbuf); + } + if (BlockNumberIsValid(nextblkno)) { + Buffer nextbuf = _hash_getbuf(rel, nextblkno, HASH_WRITE); + Page nextpage = BufferGetPage(nextbuf); + HashPageOpaque nextopaque = + (HashPageOpaque) PageGetSpecialPointer(nextpage); + + _hash_checkpage(nextpage, LH_OVERFLOW_PAGE); + Assert(nextopaque->hasho_bucket == bucket); + nextopaque->hasho_prevblkno = prevblkno; + _hash_wrtbuf(rel, nextbuf); + } + + /* + * Fix up the overflow page bitmap that tracks this particular + * overflow page. The bitmap can be found in the MetaPageData + * array element hashm_mapp[bitmappage]. + */ + splitnum = (addr >> SPLITSHIFT); + ovflpgno = + (splitnum ? metap->SPARES[splitnum - 1] : 0) + (addr & SPLITMASK) - 1; + + if (ovflpgno < metap->LAST_FREED) { + metap->LAST_FREED = ovflpgno; + } + + bitmappage = (ovflpgno >> (metap->BSHIFT + BYTE_TO_BIT)); + bitmapbit = ovflpgno & (BMPGSZ_BIT(metap) - 1); + + blkno = metap->hashm_mapp[bitmappage]; + mapbuf = _hash_getbuf(rel, blkno, HASH_WRITE); + mappage = BufferGetPage(mapbuf); + _hash_checkpage(mappage, LH_BITMAP_PAGE); + freep = HashPageGetBitmap(mappage); + CLRBIT(freep, bitmapbit); + _hash_wrtbuf(rel, mapbuf); + + _hash_relbuf(rel, metabuf, HASH_WRITE); + + /* + * now instantiate the page that replaced this one, + * if it exists, and return that buffer with a write lock. + */ + if (BlockNumberIsValid(nextblkno)) { + return (_hash_getbuf(rel, nextblkno, HASH_WRITE)); + } else { + return (InvalidBuffer); + } +} + + +/* + * _hash_initbitmap() + * + * Initialize a new bitmap page. The metapage has a write-lock upon + * entering the function. + * + * 'pnum' is the OverflowPageAddress of the new bitmap page. + * 'nbits' is how many bits to clear (i.e., make available) in the new + * bitmap page. the remainder of the bits (as well as the first bit, + * representing the bitmap page itself) will be set. + * 'ndx' is the 0-based offset of the new bitmap page within the + * metapage's array of bitmap page OverflowPageAddresses. + */ + +#define INT_MASK ((1 << INT_TO_BIT) -1) + +int32 +_hash_initbitmap(Relation rel, + HashMetaPage metap, + int32 pnum, + int32 nbits, + int32 ndx) +{ + Buffer buf; + BlockNumber blkno; + Page pg; + HashPageOpaque op; + uint32 *freep; + int clearbytes, clearints; + + blkno = OADDR_TO_BLKNO(pnum); + buf = _hash_getbuf(rel, blkno, HASH_WRITE); + pg = BufferGetPage(buf); + _hash_pageinit(pg, BufferGetPageSize(buf)); + op = (HashPageOpaque) PageGetSpecialPointer(pg); + op->hasho_oaddr = InvalidOvflAddress; + op->hasho_prevblkno = InvalidBlockNumber; + op->hasho_nextblkno = InvalidBlockNumber; + op->hasho_flag = LH_BITMAP_PAGE; + op->hasho_bucket = -1; + + freep = HashPageGetBitmap(pg); + + /* set all of the bits above 'nbits' to 1 */ + clearints = ((nbits - 1) >> INT_TO_BIT) + 1; + clearbytes = clearints << INT_TO_BYTE; + (void) memset((char *) freep, 0, clearbytes); + (void) memset(((char *) freep) + clearbytes, 0xFF, + BMPGSZ_BYTE(metap) - clearbytes); + freep[clearints - 1] = ALL_SET << (nbits & INT_MASK); + + /* bit 0 represents the new bitmap page */ + SETBIT(freep, 0); + + /* metapage already has a write lock */ + metap->hashm_nmaps++; + metap->hashm_mapp[ndx] = blkno; + + /* write out the new bitmap page (releasing its locks) */ + _hash_wrtbuf(rel, buf); + + return (0); +} + + +/* + * _hash_squeezebucket(rel, bucket) + * + * Try to squeeze the tuples onto pages occuring earlier in the + * bucket chain in an attempt to free overflow pages. When we start + * the "squeezing", the page from which we start taking tuples (the + * "read" page) is the last bucket in the bucket chain and the page + * onto which we start squeezing tuples (the "write" page) is the + * first page in the bucket chain. The read page works backward and + * the write page works forward; the procedure terminates when the + * read page and write page are the same page. + */ +void +_hash_squeezebucket(Relation rel, + HashMetaPage metap, + Bucket bucket) +{ + Buffer wbuf; + Buffer rbuf; + BlockNumber wblkno; + BlockNumber rblkno; + Page wpage; + Page rpage; + HashPageOpaque wopaque; + HashPageOpaque ropaque; + OffsetNumber woffnum; + OffsetNumber roffnum; + HashItem hitem; + int itemsz; + +/* elog(DEBUG, "_hash_squeezebucket: squeezing bucket %d", bucket); */ + + /* + * start squeezing into the base bucket page. + */ + wblkno = BUCKET_TO_BLKNO(bucket); + wbuf = _hash_getbuf(rel, wblkno, HASH_WRITE); + wpage = BufferGetPage(wbuf); + _hash_checkpage(wpage, LH_BUCKET_PAGE); + wopaque = (HashPageOpaque) PageGetSpecialPointer(wpage); + + /* + * if there aren't any overflow pages, there's nothing to squeeze. + */ + if (!BlockNumberIsValid(wopaque->hasho_nextblkno)) { + _hash_relbuf(rel, wbuf, HASH_WRITE); + return; + } + + /* + * find the last page in the bucket chain by starting at the base + * bucket page and working forward. + * + * XXX if chains tend to be long, we should probably move forward + * using HASH_READ and then _hash_chgbufaccess to HASH_WRITE when + * we reach the end. if they are short we probably don't care + * very much. if the hash function is working at all, they had + * better be short.. + */ + ropaque = wopaque; + do { + rblkno = ropaque->hasho_nextblkno; + if (ropaque != wopaque) { + _hash_relbuf(rel, rbuf, HASH_WRITE); + } + rbuf = _hash_getbuf(rel, rblkno, HASH_WRITE); + rpage = BufferGetPage(rbuf); + _hash_checkpage(rpage, LH_OVERFLOW_PAGE); + Assert(!PageIsEmpty(rpage)); + ropaque = (HashPageOpaque) PageGetSpecialPointer(rpage); + Assert(ropaque->hasho_bucket == bucket); + } while (BlockNumberIsValid(ropaque->hasho_nextblkno)); + + /* + * squeeze the tuples. + */ + roffnum = FirstOffsetNumber; + for(;;) { + hitem = (HashItem) PageGetItem(rpage, PageGetItemId(rpage, roffnum)); + itemsz = IndexTupleDSize(hitem->hash_itup) + + (sizeof(HashItemData) - sizeof(IndexTupleData)); + itemsz = DOUBLEALIGN(itemsz); + + /* + * walk up the bucket chain, looking for a page big enough for + * this item. + */ + while (PageGetFreeSpace(wpage) < itemsz) { + wblkno = wopaque->hasho_nextblkno; + + _hash_wrtbuf(rel, wbuf); + + if (!BlockNumberIsValid(wblkno) || (rblkno == wblkno)) { + _hash_wrtbuf(rel, rbuf); + /* wbuf is already released */ + return; + } + + wbuf = _hash_getbuf(rel, wblkno, HASH_WRITE); + wpage = BufferGetPage(wbuf); + _hash_checkpage(wpage, LH_OVERFLOW_PAGE); + Assert(!PageIsEmpty(wpage)); + wopaque = (HashPageOpaque) PageGetSpecialPointer(wpage); + Assert(wopaque->hasho_bucket == bucket); + } + + /* + * if we're here, we have found room so insert on the "write" + * page. + */ + woffnum = OffsetNumberNext(PageGetMaxOffsetNumber(wpage)); + (void) PageAddItem(wpage, (Item) hitem, itemsz, woffnum, LP_USED); + + /* + * delete the tuple from the "read" page. + * PageIndexTupleDelete repacks the ItemId array, so 'roffnum' + * will be "advanced" to the "next" ItemId. + */ + PageIndexTupleDelete(rpage, roffnum); + _hash_wrtnorelbuf(rel, rbuf); + + /* + * if the "read" page is now empty because of the deletion, + * free it. + */ + if (PageIsEmpty(rpage) && (ropaque->hasho_flag & LH_OVERFLOW_PAGE)) { + rblkno = ropaque->hasho_prevblkno; + Assert(BlockNumberIsValid(rblkno)); + + /* + * free this overflow page. the extra _hash_relbuf is + * because _hash_freeovflpage gratuitously returns the + * next page (we want the previous page and will get it + * ourselves later). + */ + rbuf = _hash_freeovflpage(rel, rbuf); + if (BufferIsValid(rbuf)) { + _hash_relbuf(rel, rbuf, HASH_WRITE); + } + + if (rblkno == wblkno) { + /* rbuf is already released */ + _hash_wrtbuf(rel, wbuf); + return; + } + + rbuf = _hash_getbuf(rel, rblkno, HASH_WRITE); + rpage = BufferGetPage(rbuf); + _hash_checkpage(rpage, LH_OVERFLOW_PAGE); + Assert(!PageIsEmpty(rpage)); + ropaque = (HashPageOpaque) PageGetSpecialPointer(rpage); + Assert(ropaque->hasho_bucket == bucket); + + roffnum = FirstOffsetNumber; + } + } +} diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c new file mode 100644 index 00000000000..2c6ebed8350 --- /dev/null +++ b/src/backend/access/hash/hashpage.c @@ -0,0 +1,669 @@ +/*------------------------------------------------------------------------- + * + * hashpage.c-- + * Hash table page management code for the Postgres hash access method + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/hash/hashpage.c,v 1.1.1.1 1996/07/09 06:21:10 scrappy Exp $ + * + * NOTES + * Postgres hash pages look like ordinary relation pages. The opaque + * data at high addresses includes information about the page including + * whether a page is an overflow page or a true bucket, the block + * numbers of the preceding and following pages, and the overflow + * address of the page if it is an overflow page. + * + * The first page in a hash relation, page zero, is special -- it stores + * information describing the hash table; it is referred to as teh + * "meta page." Pages one and higher store the actual data. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "access/genam.h" +#include "access/hash.h" + +static void _hash_setpagelock(Relation rel, BlockNumber blkno, int access); +static void _hash_unsetpagelock(Relation rel, BlockNumber blkno, int access); +static void _hash_splitpage(Relation rel, Buffer metabuf, Bucket obucket, Bucket nbucket); + +/* + * We use high-concurrency locking on hash indices. There are two cases in + * which we don't do locking. One is when we're building the index. + * Since the creating transaction has not committed, no one can see + * the index, and there's no reason to share locks. The second case + * is when we're just starting up the database system. We use some + * special-purpose initialization code in the relation cache manager + * (see utils/cache/relcache.c) to allow us to do indexed scans on + * the system catalogs before we'd normally be able to. This happens + * before the lock table is fully initialized, so we can't use it. + * Strictly speaking, this violates 2pl, but we don't do 2pl on the + * system catalogs anyway. + */ + + +#define USELOCKING (!BuildingHash && !IsInitProcessingMode()) + + +/* + * _hash_metapinit() -- Initialize the metadata page of a hash index, + * the two buckets that we begin with and the initial + * bitmap page. + */ +void +_hash_metapinit(Relation rel) +{ + HashMetaPage metap; + HashPageOpaque pageopaque; + Buffer metabuf; + Buffer buf; + Page pg; + int nbuckets; + uint32 nelem; /* number elements */ + uint32 lg2nelem; /* _hash_log2(nelem) */ + uint32 nblocks; + uint16 i; + + /* can't be sharing this with anyone, now... */ + if (USELOCKING) + RelationSetLockForWrite(rel); + + if ((nblocks = RelationGetNumberOfBlocks(rel)) != 0) { + elog(WARN, "Cannot initialize non-empty hash table %s", + RelationGetRelationName(rel)); + } + + metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_WRITE); + pg = BufferGetPage(metabuf); + metap = (HashMetaPage) pg; + _hash_pageinit(pg, BufferGetPageSize(metabuf)); + + metap->hashm_magic = HASH_MAGIC; + metap->hashm_version = HASH_VERSION; + metap->hashm_nkeys = 0; + metap->hashm_nmaps = 0; + metap->hashm_ffactor = DEFAULT_FFACTOR; + metap->hashm_bsize = BufferGetPageSize(metabuf); + metap->hashm_bshift = _hash_log2(metap->hashm_bsize); + for (i = metap->hashm_bshift; i > 0; --i) { + if ((1 << i) < (metap->hashm_bsize - + (DOUBLEALIGN(sizeof(PageHeaderData)) + + DOUBLEALIGN(sizeof(HashPageOpaqueData))))) { + break; + } + } + Assert(i); + metap->hashm_bmsize = 1 << i; + metap->hashm_procid = index_getprocid(rel, 1, HASHPROC); + + /* + * Make nelem = 2 rather than 0 so that we end up allocating space + * for the next greater power of two number of buckets. + */ + nelem = 2; + lg2nelem = 1; /*_hash_log2(MAX(nelem, 2)) */ + nbuckets = 2; /*1 << lg2nelem */ + + memset((char *) metap->hashm_spares, 0, sizeof(metap->hashm_spares)); + memset((char *) metap->hashm_mapp, 0, sizeof(metap->hashm_mapp)); + + metap->hashm_spares[lg2nelem] = 2; /* lg2nelem + 1 */ + metap->hashm_spares[lg2nelem + 1] = 2; /* lg2nelem + 1 */ + metap->hashm_ovflpoint = 1; /* lg2nelem */ + metap->hashm_lastfreed = 2; + + metap->hashm_maxbucket = metap->hashm_lowmask = 1; /* nbuckets - 1 */ + metap->hashm_highmask = 3; /* (nbuckets << 1) - 1 */ + + pageopaque = (HashPageOpaque) PageGetSpecialPointer(pg); + pageopaque->hasho_oaddr = InvalidOvflAddress; + pageopaque->hasho_prevblkno = InvalidBlockNumber; + pageopaque->hasho_nextblkno = InvalidBlockNumber; + pageopaque->hasho_flag = LH_META_PAGE; + pageopaque->hasho_bucket = -1; + + /* + * First bitmap page is at: splitpoint lg2nelem page offset 1 which + * turns out to be page 3. Couldn't initialize page 3 until we created + * the first two buckets above. + */ + if (_hash_initbitmap(rel, metap, OADDR_OF(lg2nelem, 1), lg2nelem + 1, 0)) + elog(WARN, "Problem with _hash_initbitmap."); + + /* all done */ + _hash_wrtnorelbuf(rel, metabuf); + + /* + * initialize the first two buckets + */ + for (i = 0; i <= 1; i++) { + buf = _hash_getbuf(rel, BUCKET_TO_BLKNO(i), HASH_WRITE); + pg = BufferGetPage(buf); + _hash_pageinit(pg, BufferGetPageSize(buf)); + pageopaque = (HashPageOpaque) PageGetSpecialPointer(pg); + pageopaque->hasho_oaddr = InvalidOvflAddress; + pageopaque->hasho_prevblkno = InvalidBlockNumber; + pageopaque->hasho_nextblkno = InvalidBlockNumber; + pageopaque->hasho_flag = LH_BUCKET_PAGE; + pageopaque->hasho_bucket = i; + _hash_wrtbuf(rel, buf); + } + + _hash_relbuf(rel, metabuf, HASH_WRITE); + + if (USELOCKING) + RelationUnsetLockForWrite(rel); +} + +/* + * _hash_getbuf() -- Get a buffer by block number for read or write. + * + * When this routine returns, the appropriate lock is set on the + * requested buffer its reference count is correct. + * + * XXX P_NEW is not used because, unlike the tree structures, we + * need the bucket blocks to be at certain block numbers. we must + * depend on the caller to call _hash_pageinit on the block if it + * knows that this is a new block. + */ +Buffer +_hash_getbuf(Relation rel, BlockNumber blkno, int access) +{ + Buffer buf; + + if (blkno == P_NEW) { + elog(WARN, "_hash_getbuf: internal error: hash AM does not use P_NEW"); + } + switch (access) { + case HASH_WRITE: + case HASH_READ: + _hash_setpagelock(rel, blkno, access); + break; + default: + elog(WARN, "_hash_getbuf: invalid access (%d) on new blk: %.*s", + access, NAMEDATALEN, RelationGetRelationName(rel)); + break; + } + buf = ReadBuffer(rel, blkno); + + /* ref count and lock type are correct */ + return (buf); +} + +/* + * _hash_relbuf() -- release a locked buffer. + */ +void +_hash_relbuf(Relation rel, Buffer buf, int access) +{ + BlockNumber blkno; + + blkno = BufferGetBlockNumber(buf); + + switch (access) { + case HASH_WRITE: + case HASH_READ: + _hash_unsetpagelock(rel, blkno, access); + break; + default: + elog(WARN, "_hash_relbuf: invalid access (%d) on blk %x: %.*s", + access, blkno, NAMEDATALEN, RelationGetRelationName(rel)); + } + + ReleaseBuffer(buf); +} + +/* + * _hash_wrtbuf() -- write a hash page to disk. + * + * This routine releases the lock held on the buffer and our reference + * to it. It is an error to call _hash_wrtbuf() without a write lock + * or a reference to the buffer. + */ +void +_hash_wrtbuf(Relation rel, Buffer buf) +{ + BlockNumber blkno; + + blkno = BufferGetBlockNumber(buf); + WriteBuffer(buf); + _hash_unsetpagelock(rel, blkno, HASH_WRITE); +} + +/* + * _hash_wrtnorelbuf() -- write a hash page to disk, but do not release + * our reference or lock. + * + * It is an error to call _hash_wrtnorelbuf() without a write lock + * or a reference to the buffer. + */ +void +_hash_wrtnorelbuf(Relation rel, Buffer buf) +{ + BlockNumber blkno; + + blkno = BufferGetBlockNumber(buf); + WriteNoReleaseBuffer(buf); +} + +Page +_hash_chgbufaccess(Relation rel, + Buffer *bufp, + int from_access, + int to_access) +{ + BlockNumber blkno; + + blkno = BufferGetBlockNumber(*bufp); + + switch (from_access) { + case HASH_WRITE: + _hash_wrtbuf(rel, *bufp); + break; + case HASH_READ: + _hash_relbuf(rel, *bufp, from_access); + break; + default: + elog(WARN, "_hash_chgbufaccess: invalid access (%d) on blk %x: %.*s", + from_access, blkno, NAMEDATALEN, RelationGetRelationName(rel)); + break; + } + *bufp = _hash_getbuf(rel, blkno, to_access); + return (BufferGetPage(*bufp)); +} + +/* + * _hash_pageinit() -- Initialize a new page. + */ +void +_hash_pageinit(Page page, Size size) +{ + Assert(((PageHeader) page)->pd_lower == 0); + Assert(((PageHeader) page)->pd_upper == 0); + Assert(((PageHeader) page)->pd_special == 0); + + /* + * Cargo-cult programming -- don't really need this to be zero, but + * creating new pages is an infrequent occurrence and it makes me feel + * good when I know they're empty. + */ + memset(page, 0, size); + + PageInit(page, size, sizeof(HashPageOpaqueData)); +} + +static void +_hash_setpagelock(Relation rel, + BlockNumber blkno, + int access) +{ + ItemPointerData iptr; + + if (USELOCKING) { + ItemPointerSet(&iptr, blkno, 1); + + switch (access) { + case HASH_WRITE: + RelationSetSingleWLockPage(rel, &iptr); + break; + case HASH_READ: + RelationSetSingleRLockPage(rel, &iptr); + break; + default: + elog(WARN, "_hash_setpagelock: invalid access (%d) on blk %x: %.*s", + access, blkno, NAMEDATALEN, RelationGetRelationName(rel)); + break; + } + } +} + +static void +_hash_unsetpagelock(Relation rel, + BlockNumber blkno, + int access) +{ + ItemPointerData iptr; + + if (USELOCKING) { + ItemPointerSet(&iptr, blkno, 1); + + switch (access) { + case HASH_WRITE: + RelationUnsetSingleWLockPage(rel, &iptr); + break; + case HASH_READ: + RelationUnsetSingleRLockPage(rel, &iptr); + break; + default: + elog(WARN, "_hash_unsetpagelock: invalid access (%d) on blk %x: %.*s", + access, blkno, NAMEDATALEN, RelationGetRelationName(rel)); + break; + } + } +} + +void +_hash_pagedel(Relation rel, ItemPointer tid) +{ + Buffer buf; + Buffer metabuf; + Page page; + BlockNumber blkno; + OffsetNumber offno; + HashMetaPage metap; + HashPageOpaque opaque; + + blkno = ItemPointerGetBlockNumber(tid); + offno = ItemPointerGetOffsetNumber(tid); + + buf = _hash_getbuf(rel, blkno, HASH_WRITE); + page = BufferGetPage(buf); + _hash_checkpage(page, LH_BUCKET_PAGE|LH_OVERFLOW_PAGE); + opaque = (HashPageOpaque) PageGetSpecialPointer(page); + + PageIndexTupleDelete(page, offno); + _hash_wrtnorelbuf(rel, buf); + + if (PageIsEmpty(page) && (opaque->hasho_flag & LH_OVERFLOW_PAGE)) { + buf = _hash_freeovflpage(rel, buf); + if (BufferIsValid(buf)) { + _hash_relbuf(rel, buf, HASH_WRITE); + } + } else { + _hash_relbuf(rel, buf, HASH_WRITE); + } + + metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_WRITE); + metap = (HashMetaPage) BufferGetPage(metabuf); + _hash_checkpage((Page) metap, LH_META_PAGE); + ++metap->hashm_nkeys; + _hash_wrtbuf(rel, metabuf); +} + +void +_hash_expandtable(Relation rel, Buffer metabuf) +{ + HashMetaPage metap; + Bucket old_bucket; + Bucket new_bucket; + uint32 spare_ndx; + +/* elog(DEBUG, "_hash_expandtable: expanding..."); */ + + metap = (HashMetaPage) BufferGetPage(metabuf); + _hash_checkpage((Page) metap, LH_META_PAGE); + + metap = (HashMetaPage) _hash_chgbufaccess(rel, &metabuf, HASH_READ, HASH_WRITE); + new_bucket = ++metap->MAX_BUCKET; + metap = (HashMetaPage) _hash_chgbufaccess(rel, &metabuf, HASH_WRITE, HASH_READ); + old_bucket = (metap->MAX_BUCKET & metap->LOW_MASK); + + /* + * If the split point is increasing (MAX_BUCKET's log base 2 + * * increases), we need to copy the current contents of the spare + * split bucket to the next bucket. + */ + spare_ndx = _hash_log2(metap->MAX_BUCKET + 1); + if (spare_ndx > metap->OVFL_POINT) { + + metap = (HashMetaPage) _hash_chgbufaccess(rel, &metabuf, HASH_READ, HASH_WRITE); + metap->SPARES[spare_ndx] = metap->SPARES[metap->OVFL_POINT]; + metap->OVFL_POINT = spare_ndx; + metap = (HashMetaPage) _hash_chgbufaccess(rel, &metabuf, HASH_WRITE, HASH_READ); + } + + if (new_bucket > metap->HIGH_MASK) { + + /* Starting a new doubling */ + metap = (HashMetaPage) _hash_chgbufaccess(rel, &metabuf, HASH_READ, HASH_WRITE); + metap->LOW_MASK = metap->HIGH_MASK; + metap->HIGH_MASK = new_bucket | metap->LOW_MASK; + metap = (HashMetaPage) _hash_chgbufaccess(rel, &metabuf, HASH_WRITE, HASH_READ); + + } + /* Relocate records to the new bucket */ + _hash_splitpage(rel, metabuf, old_bucket, new_bucket); +} + + +/* + * _hash_splitpage -- split 'obucket' into 'obucket' and 'nbucket' + * + * this routine is actually misnamed -- we are splitting a bucket that + * consists of a base bucket page and zero or more overflow (bucket + * chain) pages. + */ +static void +_hash_splitpage(Relation rel, + Buffer metabuf, + Bucket obucket, + Bucket nbucket) +{ + Bucket bucket; + Buffer obuf; + Buffer nbuf; + Buffer ovflbuf; + BlockNumber oblkno; + BlockNumber nblkno; + bool null; + Datum datum; + HashItem hitem; + HashPageOpaque oopaque; + HashPageOpaque nopaque; + HashMetaPage metap; + IndexTuple itup; + int itemsz; + OffsetNumber ooffnum; + OffsetNumber noffnum; + OffsetNumber omaxoffnum; + Page opage; + Page npage; + TupleDesc itupdesc; + +/* elog(DEBUG, "_hash_splitpage: splitting %d into %d,%d", + obucket, obucket, nbucket); +*/ + metap = (HashMetaPage) BufferGetPage(metabuf); + _hash_checkpage((Page) metap, LH_META_PAGE); + + /* get the buffers & pages */ + oblkno = BUCKET_TO_BLKNO(obucket); + nblkno = BUCKET_TO_BLKNO(nbucket); + obuf = _hash_getbuf(rel, oblkno, HASH_WRITE); + nbuf = _hash_getbuf(rel, nblkno, HASH_WRITE); + opage = BufferGetPage(obuf); + npage = BufferGetPage(nbuf); + + /* initialize the new bucket */ + _hash_pageinit(npage, BufferGetPageSize(nbuf)); + nopaque = (HashPageOpaque) PageGetSpecialPointer(npage); + nopaque->hasho_prevblkno = InvalidBlockNumber; + nopaque->hasho_nextblkno = InvalidBlockNumber; + nopaque->hasho_flag = LH_BUCKET_PAGE; + nopaque->hasho_oaddr = InvalidOvflAddress; + nopaque->hasho_bucket = nbucket; + _hash_wrtnorelbuf(rel, nbuf); + + /* + * make sure the old bucket isn't empty. advance 'opage' and + * friends through the overflow bucket chain until we find a + * non-empty page. + * + * XXX we should only need this once, if we are careful to + * preserve the invariant that overflow pages are never empty. + */ + _hash_checkpage(opage, LH_BUCKET_PAGE); + oopaque = (HashPageOpaque) PageGetSpecialPointer(opage); + if (PageIsEmpty(opage)) { + oblkno = oopaque->hasho_nextblkno; + _hash_relbuf(rel, obuf, HASH_WRITE); + if (!BlockNumberIsValid(oblkno)) { + /* + * the old bucket is completely empty; of course, the new + * bucket will be as well, but since it's a base bucket + * page we don't care. + */ + _hash_relbuf(rel, nbuf, HASH_WRITE); + return; + } + obuf = _hash_getbuf(rel, oblkno, HASH_WRITE); + opage = BufferGetPage(obuf); + _hash_checkpage(opage, LH_OVERFLOW_PAGE); + if (PageIsEmpty(opage)) { + elog(WARN, "_hash_splitpage: empty overflow page %d", oblkno); + } + oopaque = (HashPageOpaque) PageGetSpecialPointer(opage); + } + + /* + * we are now guaranteed that 'opage' is not empty. partition the + * tuples in the old bucket between the old bucket and the new + * bucket, advancing along their respective overflow bucket chains + * and adding overflow pages as needed. + */ + ooffnum = FirstOffsetNumber; + omaxoffnum = PageGetMaxOffsetNumber(opage); + for (;;) { + /* + * at each iteration through this loop, each of these variables + * should be up-to-date: obuf opage oopaque ooffnum omaxoffnum + */ + + /* check if we're at the end of the page */ + if (ooffnum > omaxoffnum) { + /* at end of page, but check for overflow page */ + oblkno = oopaque->hasho_nextblkno; + if (BlockNumberIsValid(oblkno)) { + /* + * we ran out of tuples on this particular page, but + * we have more overflow pages; re-init values. + */ + _hash_wrtbuf(rel, obuf); + obuf = _hash_getbuf(rel, oblkno, HASH_WRITE); + opage = BufferGetPage(obuf); + _hash_checkpage(opage, LH_OVERFLOW_PAGE); + oopaque = (HashPageOpaque) PageGetSpecialPointer(opage); + + /* we're guaranteed that an ovfl page has at least 1 tuple */ + if (PageIsEmpty(opage)) { + elog(WARN, "_hash_splitpage: empty ovfl page %d!", + oblkno); + } + ooffnum = FirstOffsetNumber; + omaxoffnum = PageGetMaxOffsetNumber(opage); + } else { + /* + * we're at the end of the bucket chain, so now we're + * really done with everything. before quitting, call + * _hash_squeezebucket to ensure the tuples in the + * bucket (including the overflow pages) are packed as + * tightly as possible. + */ + _hash_wrtbuf(rel, obuf); + _hash_wrtbuf(rel, nbuf); + _hash_squeezebucket(rel, metap, obucket); + return; + } + } + + /* hash on the tuple */ + hitem = (HashItem) PageGetItem(opage, PageGetItemId(opage, ooffnum)); + itup = &(hitem->hash_itup); + itupdesc = RelationGetTupleDescriptor(rel); + datum = index_getattr(itup, 1, itupdesc, &null); + bucket = _hash_call(rel, metap, datum); + + if (bucket == nbucket) { + /* + * insert the tuple into the new bucket. if it doesn't + * fit on the current page in the new bucket, we must + * allocate a new overflow page and place the tuple on + * that page instead. + */ + itemsz = IndexTupleDSize(hitem->hash_itup) + + (sizeof(HashItemData) - sizeof(IndexTupleData)); + + itemsz = DOUBLEALIGN(itemsz); + + if (PageGetFreeSpace(npage) < itemsz) { + ovflbuf = _hash_addovflpage(rel, &metabuf, nbuf); + _hash_wrtbuf(rel, nbuf); + nbuf = ovflbuf; + npage = BufferGetPage(nbuf); + _hash_checkpage(npage, LH_BUCKET_PAGE|LH_OVERFLOW_PAGE); + } + + noffnum = OffsetNumberNext(PageGetMaxOffsetNumber(npage)); + (void) PageAddItem(npage, (Item) hitem, itemsz, noffnum, LP_USED); + _hash_wrtnorelbuf(rel, nbuf); + + /* + * now delete the tuple from the old bucket. after this + * section of code, 'ooffnum' will actually point to the + * ItemId to which we would point if we had advanced it + * before the deletion (PageIndexTupleDelete repacks the + * ItemId array). this also means that 'omaxoffnum' is + * exactly one less than it used to be, so we really can + * just decrement it instead of calling + * PageGetMaxOffsetNumber. + */ + PageIndexTupleDelete(opage, ooffnum); + _hash_wrtnorelbuf(rel, obuf); + omaxoffnum = OffsetNumberPrev(omaxoffnum); + + /* + * tidy up. if the old page was an overflow page and it + * is now empty, we must free it (we want to preserve the + * invariant that overflow pages cannot be empty). + */ + if (PageIsEmpty(opage) && + (oopaque->hasho_flag & LH_OVERFLOW_PAGE)) { + obuf = _hash_freeovflpage(rel, obuf); + + /* check that we're not through the bucket chain */ + if (BufferIsInvalid(obuf)) { + _hash_wrtbuf(rel, nbuf); + _hash_squeezebucket(rel, metap, obucket); + return; + } + + /* + * re-init. again, we're guaranteed that an ovfl page + * has at least one tuple. + */ + opage = BufferGetPage(obuf); + _hash_checkpage(opage, LH_OVERFLOW_PAGE); + oblkno = BufferGetBlockNumber(obuf); + oopaque = (HashPageOpaque) PageGetSpecialPointer(opage); + if (PageIsEmpty(opage)) { + elog(WARN, "_hash_splitpage: empty overflow page %d", + oblkno); + } + ooffnum = FirstOffsetNumber; + omaxoffnum = PageGetMaxOffsetNumber(opage); + } + } else { + /* + * the tuple stays on this page. we didn't move anything, + * so we didn't delete anything and therefore we don't + * have to change 'omaxoffnum'. + * + * XXX any hash value from [0, nbucket-1] will map to this + * bucket, which doesn't make sense to me. + */ + ooffnum = OffsetNumberNext(ooffnum); + } + } + /*NOTREACHED*/ +} diff --git a/src/backend/access/hash/hashscan.c b/src/backend/access/hash/hashscan.c new file mode 100644 index 00000000000..c4cce0e70d9 --- /dev/null +++ b/src/backend/access/hash/hashscan.c @@ -0,0 +1,172 @@ +/*------------------------------------------------------------------------- + * + * hashscan.c-- + * manage scans on hash tables + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/hash/hashscan.c,v 1.1.1.1 1996/07/09 06:21:10 scrappy Exp $ + * + * NOTES + * Because we can be doing an index scan on a relation while we + * update it, we need to avoid missing data that moves around in + * the index. The routines and global variables in this file + * guarantee that all scans in the local address space stay + * correctly positioned. This is all we need to worry about, since + * write locking guarantees that no one else will be on the same + * page at the same time as we are. + * + * The scheme is to manage a list of active scans in the current + * backend. Whenever we add or remove records from an index, we + * check the list of active scans to see if any has been affected. + * A scan is affected only if it is on the same relation, and the + * same page, as the update. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/sdir.h" +#include "access/hash.h" + +static void _hash_scandel(IndexScanDesc scan, BlockNumber blkno, OffsetNumber offno); +static bool _hash_scantouched(IndexScanDesc scan, BlockNumber blkno, OffsetNumber offno); + +typedef struct HashScanListData { + IndexScanDesc hashsl_scan; + struct HashScanListData *hashsl_next; +} HashScanListData; + +typedef HashScanListData *HashScanList; + +static HashScanList HashScans = (HashScanList) NULL; + +/* + * _Hash_regscan() -- register a new scan. + */ +void +_hash_regscan(IndexScanDesc scan) +{ + HashScanList new_el; + + new_el = (HashScanList) palloc(sizeof(HashScanListData)); + new_el->hashsl_scan = scan; + new_el->hashsl_next = HashScans; + HashScans = new_el; +} + +/* + * _hash_dropscan() -- drop a scan from the scan list + */ +void +_hash_dropscan(IndexScanDesc scan) +{ + HashScanList chk, last; + + last = (HashScanList) NULL; + for (chk = HashScans; + chk != (HashScanList) NULL && chk->hashsl_scan != scan; + chk = chk->hashsl_next) { + last = chk; + } + + if (chk == (HashScanList) NULL) + elog(WARN, "hash scan list trashed; can't find 0x%lx", scan); + + if (last == (HashScanList) NULL) + HashScans = chk->hashsl_next; + else + last->hashsl_next = chk->hashsl_next; + +#ifdef PERFECT_MEM + pfree (chk); +#endif /* PERFECT_MEM */ +} + +void +_hash_adjscans(Relation rel, ItemPointer tid) +{ + HashScanList l; + Oid relid; + + relid = rel->rd_id; + for (l = HashScans; l != (HashScanList) NULL; l = l->hashsl_next) { + if (relid == l->hashsl_scan->relation->rd_id) + _hash_scandel(l->hashsl_scan, ItemPointerGetBlockNumber(tid), + ItemPointerGetOffsetNumber(tid)); + } +} + +static void +_hash_scandel(IndexScanDesc scan, BlockNumber blkno, OffsetNumber offno) +{ + ItemPointer current; + Buffer buf; + Buffer metabuf; + HashScanOpaque so; + + if (!_hash_scantouched(scan, blkno, offno)) + return; + + metabuf = _hash_getbuf(scan->relation, HASH_METAPAGE, HASH_READ); + + so = (HashScanOpaque) scan->opaque; + buf = so->hashso_curbuf; + + current = &(scan->currentItemData); + if (ItemPointerIsValid(current) + && ItemPointerGetBlockNumber(current) == blkno + && ItemPointerGetOffsetNumber(current) >= offno) { + _hash_step(scan, &buf, BackwardScanDirection, metabuf); + so->hashso_curbuf = buf; + } + + current = &(scan->currentMarkData); + if (ItemPointerIsValid(current) + && ItemPointerGetBlockNumber(current) == blkno + && ItemPointerGetOffsetNumber(current) >= offno) { + ItemPointerData tmp; + tmp = *current; + *current = scan->currentItemData; + scan->currentItemData = tmp; + _hash_step(scan, &buf, BackwardScanDirection, metabuf); + so->hashso_mrkbuf = buf; + tmp = *current; + *current = scan->currentItemData; + scan->currentItemData = tmp; + } +} + +static bool +_hash_scantouched(IndexScanDesc scan, + BlockNumber blkno, + OffsetNumber offno) +{ + ItemPointer current; + + current = &(scan->currentItemData); + if (ItemPointerIsValid(current) + && ItemPointerGetBlockNumber(current) == blkno + && ItemPointerGetOffsetNumber(current) >= offno) + return (true); + + current = &(scan->currentMarkData); + if (ItemPointerIsValid(current) + && ItemPointerGetBlockNumber(current) == blkno + && ItemPointerGetOffsetNumber(current) >= offno) + return (true); + + return (false); +} diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c new file mode 100644 index 00000000000..056235dec85 --- /dev/null +++ b/src/backend/access/hash/hashsearch.c @@ -0,0 +1,425 @@ +/*------------------------------------------------------------------------- + * + * hashsearch.c-- + * search code for postgres hash tables + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/hash/hashsearch.c,v 1.1.1.1 1996/07/09 06:21:10 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "fmgr.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/skey.h" +#include "access/sdir.h" +#include "access/hash.h" + +/* + * _hash_search() -- Finds the page/bucket that the contains the + * scankey and loads it into *bufP. the buffer has a read lock. + */ +void +_hash_search(Relation rel, + int keysz, + ScanKey scankey, + Buffer *bufP, + HashMetaPage metap) +{ + BlockNumber blkno; + Datum keyDatum; + Bucket bucket; + + if (scankey == (ScanKey) NULL || + (keyDatum = scankey[0].sk_argument) == (Datum) NULL) { + /* + * If the scankey argument is NULL, all tuples will satisfy + * the scan so we start the scan at the first bucket (bucket + * 0). + */ + bucket = 0; + } else { + bucket = _hash_call(rel, metap, keyDatum); + } + + blkno = BUCKET_TO_BLKNO(bucket); + + *bufP = _hash_getbuf(rel, blkno, HASH_READ); +} + +/* + * _hash_next() -- Get the next item in a scan. + * + * On entry, we have a valid currentItemData in the scan, and a + * read lock on the page that contains that item. We do not have + * the page pinned. We return the next item in the scan. On + * exit, we have the page containing the next item locked but not + * pinned. + */ +RetrieveIndexResult +_hash_next(IndexScanDesc scan, ScanDirection dir) +{ + Relation rel; + Buffer buf; + Buffer metabuf; + Page page; + OffsetNumber offnum; + RetrieveIndexResult res; + ItemPointer current; + ItemPointer iptr; + HashItem hitem; + IndexTuple itup; + HashScanOpaque so; + + rel = scan->relation; + so = (HashScanOpaque) scan->opaque; + current = &(scan->currentItemData); + + metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ); + + /* + * XXX 10 may 91: somewhere there's a bug in our management of the + * cached buffer for this scan. wei discovered it. the following + * is a workaround so he can work until i figure out what's going on. + */ + + if (!BufferIsValid(so->hashso_curbuf)) { + so->hashso_curbuf = _hash_getbuf(rel, + ItemPointerGetBlockNumber(current), + HASH_READ); + } + + /* we still have the buffer pinned and locked */ + buf = so->hashso_curbuf; + + /* + * step to next valid tuple. note that _hash_step releases our + * lock on 'metabuf'; if we switch to a new 'buf' while looking + * for the next tuple, we come back with a lock on that buffer. + */ + if (!_hash_step(scan, &buf, dir, metabuf)) { + return ((RetrieveIndexResult) NULL); + } + + /* if we're here, _hash_step found a valid tuple */ + current = &(scan->currentItemData); + offnum = ItemPointerGetOffsetNumber(current); + page = BufferGetPage(buf); + _hash_checkpage(page, LH_BUCKET_PAGE|LH_OVERFLOW_PAGE); + hitem = (HashItem) PageGetItem(page, PageGetItemId(page, offnum)); + itup = &hitem->hash_itup; + iptr = (ItemPointer) palloc(sizeof(ItemPointerData)); + memmove((char *) iptr, (char *) &(itup->t_tid), sizeof(ItemPointerData)); + res = FormRetrieveIndexResult(current, iptr); + + return (res); +} + +static void +_hash_readnext(Relation rel, + Buffer *bufp, Page *pagep, HashPageOpaque *opaquep) +{ + BlockNumber blkno; + + blkno = (*opaquep)->hasho_nextblkno; + _hash_relbuf(rel, *bufp, HASH_READ); + *bufp = InvalidBuffer; + if (BlockNumberIsValid(blkno)) { + *bufp = _hash_getbuf(rel, blkno, HASH_READ); + *pagep = BufferGetPage(*bufp); + _hash_checkpage(*pagep, LH_OVERFLOW_PAGE); + *opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep); + Assert(!PageIsEmpty(*pagep)); + } +} + +static void +_hash_readprev(Relation rel, + Buffer *bufp, Page *pagep, HashPageOpaque *opaquep) +{ + BlockNumber blkno; + + blkno = (*opaquep)->hasho_prevblkno; + _hash_relbuf(rel, *bufp, HASH_READ); + *bufp = InvalidBuffer; + if (BlockNumberIsValid(blkno)) { + *bufp = _hash_getbuf(rel, blkno, HASH_READ); + *pagep = BufferGetPage(*bufp); + _hash_checkpage(*pagep, LH_BUCKET_PAGE|LH_OVERFLOW_PAGE); + *opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep); + if (PageIsEmpty(*pagep)) { + Assert((*opaquep)->hasho_flag & LH_BUCKET_PAGE); + _hash_relbuf(rel, *bufp, HASH_READ); + *bufp = InvalidBuffer; + } + } +} + +/* + * _hash_first() -- Find the first item in a scan. + * + * Return the RetrieveIndexResult of the first item in the tree that + * satisfies the qualificatin associated with the scan descriptor. On + * exit, the page containing the current index tuple is read locked + * and pinned, and the scan's opaque data entry is updated to + * include the buffer. + */ +RetrieveIndexResult +_hash_first(IndexScanDesc scan, ScanDirection dir) +{ + Relation rel; + Buffer buf; + Buffer metabuf; + Page page; + HashPageOpaque opaque; + HashMetaPage metap; + HashItem hitem; + IndexTuple itup; + ItemPointer current; + ItemPointer iptr; + OffsetNumber offnum; + RetrieveIndexResult res; + HashScanOpaque so; + + rel = scan->relation; + so = (HashScanOpaque) scan->opaque; + current = &(scan->currentItemData); + + metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ); + metap = (HashMetaPage) BufferGetPage(metabuf); + _hash_checkpage((Page) metap, LH_META_PAGE); + + /* + * XXX -- The attribute number stored in the scan key is the attno + * in the heap relation. We need to transmogrify this into + * the index relation attno here. For the moment, we have + * hardwired attno == 1. + */ + + /* find the correct bucket page and load it into buf */ + _hash_search(rel, 1, scan->keyData, &buf, metap); + page = BufferGetPage(buf); + _hash_checkpage(page, LH_BUCKET_PAGE); + opaque = (HashPageOpaque) PageGetSpecialPointer(page); + + /* + * if we are scanning forward, we need to find the first non-empty + * page (if any) in the bucket chain. since overflow pages are + * never empty, this had better be either the bucket page or the + * first overflow page. + * + * if we are scanning backward, we always go all the way to the + * end of the bucket chain. + */ + if (PageIsEmpty(page)) { + if (BlockNumberIsValid(opaque->hasho_nextblkno)) { + _hash_readnext(rel, &buf, &page, &opaque); + } else { + ItemPointerSetInvalid(current); + so->hashso_curbuf = InvalidBuffer; + return ((RetrieveIndexResult) NULL); + } + } + if (ScanDirectionIsBackward(dir)) { + while (BlockNumberIsValid(opaque->hasho_nextblkno)) { + _hash_readnext(rel, &buf, &page, &opaque); + } + } + + if (!_hash_step(scan, &buf, dir, metabuf)) { + return ((RetrieveIndexResult) NULL); + } + + /* if we're here, _hash_step found a valid tuple */ + current = &(scan->currentItemData); + offnum = ItemPointerGetOffsetNumber(current); + page = BufferGetPage(buf); + _hash_checkpage(page, LH_BUCKET_PAGE|LH_OVERFLOW_PAGE); + hitem = (HashItem) PageGetItem(page, PageGetItemId(page, offnum)); + itup = &hitem->hash_itup; + iptr = (ItemPointer) palloc(sizeof(ItemPointerData)); + memmove((char *) iptr, (char *) &(itup->t_tid), sizeof(ItemPointerData)); + res = FormRetrieveIndexResult(current, iptr); + + return (res); +} + +/* + * _hash_step() -- step to the next valid item in a scan in the bucket. + * + * If no valid record exists in the requested direction, return + * false. Else, return true and set the CurrentItemData for the + * scan to the right thing. + * + * 'bufP' points to the buffer which contains the current page + * that we'll step through. + * + * 'metabuf' is released when this returns. + */ +bool +_hash_step(IndexScanDesc scan, Buffer *bufP, ScanDirection dir, Buffer metabuf) +{ + Relation rel; + ItemPointer current; + HashScanOpaque so; + int allbuckets; + HashMetaPage metap; + Buffer buf; + Page page; + HashPageOpaque opaque; + OffsetNumber maxoff; + OffsetNumber offnum; + Bucket bucket; + BlockNumber blkno; + HashItem hitem; + IndexTuple itup; + + rel = scan->relation; + current = &(scan->currentItemData); + so = (HashScanOpaque) scan->opaque; + allbuckets = (scan->numberOfKeys < 1); + + metap = (HashMetaPage) BufferGetPage(metabuf); + _hash_checkpage((Page) metap, LH_META_PAGE); + + buf = *bufP; + page = BufferGetPage(buf); + _hash_checkpage(page, LH_BUCKET_PAGE|LH_OVERFLOW_PAGE); + opaque = (HashPageOpaque) PageGetSpecialPointer(page); + + /* + * If _hash_step is called from _hash_first, current will not be + * valid, so we can't dereference it. However, in that case, we + * presumably want to start at the beginning/end of the page... + */ + maxoff = PageGetMaxOffsetNumber(page); + if (ItemPointerIsValid(current)) { + offnum = ItemPointerGetOffsetNumber(current); + } else { + offnum = InvalidOffsetNumber; + } + + /* + * 'offnum' now points to the last tuple we have seen (if any). + * + * continue to step through tuples until: + * 1) we get to the end of the bucket chain or + * 2) we find a valid tuple. + */ + do { + bucket = opaque->hasho_bucket; + + switch (dir) { + case ForwardScanDirection: + if (offnum != InvalidOffsetNumber) { + offnum = OffsetNumberNext(offnum); /* move forward */ + } else { + offnum = FirstOffsetNumber; /* new page */ + } + while (offnum > maxoff) { + /* + * either this page is empty (maxoff == + * InvalidOffsetNumber) or we ran off the end. + */ + _hash_readnext(rel, &buf, &page, &opaque); + if (BufferIsInvalid(buf)) { /* end of chain */ + if (allbuckets && bucket < metap->hashm_maxbucket) { + ++bucket; + blkno = BUCKET_TO_BLKNO(bucket); + buf = _hash_getbuf(rel, blkno, HASH_READ); + page = BufferGetPage(buf); + _hash_checkpage(page, LH_BUCKET_PAGE); + opaque = (HashPageOpaque) PageGetSpecialPointer(page); + Assert(opaque->hasho_bucket == bucket); + while (PageIsEmpty(page) && + BlockNumberIsValid(opaque->hasho_nextblkno)) { + _hash_readnext(rel, &buf, &page, &opaque); + } + maxoff = PageGetMaxOffsetNumber(page); + offnum = FirstOffsetNumber; + } else { + maxoff = offnum = InvalidOffsetNumber; + break; /* while */ + } + } else { + /* _hash_readnext never returns an empty page */ + maxoff = PageGetMaxOffsetNumber(page); + offnum = FirstOffsetNumber; + } + } + break; + case BackwardScanDirection: + if (offnum != InvalidOffsetNumber) { + offnum = OffsetNumberPrev(offnum); /* move back */ + } else { + offnum = maxoff; /* new page */ + } + while (offnum < FirstOffsetNumber) { + /* + * either this page is empty (offnum == + * InvalidOffsetNumber) or we ran off the end. + */ + _hash_readprev(rel, &buf, &page, &opaque); + if (BufferIsInvalid(buf)) { /* end of chain */ + if (allbuckets && bucket > 0) { + --bucket; + blkno = BUCKET_TO_BLKNO(bucket); + buf = _hash_getbuf(rel, blkno, HASH_READ); + page = BufferGetPage(buf); + _hash_checkpage(page, LH_BUCKET_PAGE); + opaque = (HashPageOpaque) PageGetSpecialPointer(page); + Assert(opaque->hasho_bucket == bucket); + while (BlockNumberIsValid(opaque->hasho_nextblkno)) { + _hash_readnext(rel, &buf, &page, &opaque); + } + maxoff = offnum = PageGetMaxOffsetNumber(page); + } else { + maxoff = offnum = InvalidOffsetNumber; + break; /* while */ + } + } else { + /* _hash_readprev never returns an empty page */ + maxoff = offnum = PageGetMaxOffsetNumber(page); + } + } + break; + default: + /* NoMovementScanDirection */ + /* this should not be reached */ + break; + } + + /* we ran off the end of the world without finding a match */ + if (offnum == InvalidOffsetNumber) { + _hash_relbuf(rel, metabuf, HASH_READ); + *bufP = so->hashso_curbuf = InvalidBuffer; + ItemPointerSetInvalid(current); + return(false); + } + + /* get ready to check this tuple */ + hitem = (HashItem) PageGetItem(page, PageGetItemId(page, offnum)); + itup = &hitem->hash_itup; + } while (!_hash_checkqual(scan, itup)); + + /* if we made it to here, we've found a valid tuple */ + _hash_relbuf(rel, metabuf, HASH_READ); + blkno = BufferGetBlockNumber(buf); + *bufP = so->hashso_curbuf = buf; + ItemPointerSet(current, blkno, offnum); + return(true); +} diff --git a/src/backend/access/hash/hashstrat.c b/src/backend/access/hash/hashstrat.c new file mode 100644 index 00000000000..cac2a58690e --- /dev/null +++ b/src/backend/access/hash/hashstrat.c @@ -0,0 +1,104 @@ +/*------------------------------------------------------------------------- + * + * btstrat.c-- + * Srategy map entries for the btree indexed access method + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/hash/Attic/hashstrat.c,v 1.1.1.1 1996/07/09 06:21:10 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "access/genam.h" +#include "access/hash.h" + +/* + * only one valid strategy for hash tables: equality. + */ + +static StrategyNumber HTNegate[1] = { + InvalidStrategy +}; + +static StrategyNumber HTCommute[1] = { + HTEqualStrategyNumber +}; + +static StrategyNumber HTNegateCommute[1] = { + InvalidStrategy +}; + +static StrategyEvaluationData HTEvaluationData = { + /* XXX static for simplicity */ + + HTMaxStrategyNumber, + (StrategyTransformMap)HTNegate, + (StrategyTransformMap)HTCommute, + (StrategyTransformMap)HTNegateCommute, + {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL} +}; + +/* ---------------------------------------------------------------- + * RelationGetHashStrategy + * ---------------------------------------------------------------- + */ + +StrategyNumber +_hash_getstrat(Relation rel, + AttrNumber attno, + RegProcedure proc) +{ + StrategyNumber strat; + + strat = RelationGetStrategy(rel, attno, &HTEvaluationData, proc); + + Assert(StrategyNumberIsValid(strat)); + + return (strat); +} + +bool +_hash_invokestrat(Relation rel, + AttrNumber attno, + StrategyNumber strat, + Datum left, + Datum right) +{ + return (RelationInvokeStrategy(rel, &HTEvaluationData, attno, strat, + left, right)); +} + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c new file mode 100644 index 00000000000..f8f49fe7983 --- /dev/null +++ b/src/backend/access/hash/hashutil.c @@ -0,0 +1,147 @@ +/*------------------------------------------------------------------------- + * + * btutils.c-- + * Utility code for Postgres btree implementation. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/hash/hashutil.c,v 1.1.1.1 1996/07/09 06:21:10 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "fmgr.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/iqual.h" +#include "access/hash.h" + +ScanKey +_hash_mkscankey(Relation rel, IndexTuple itup, HashMetaPage metap) +{ + ScanKey skey; + TupleDesc itupdesc; + int natts; + AttrNumber i; + Datum arg; + RegProcedure proc; + bool null; + + natts = rel->rd_rel->relnatts; + itupdesc = RelationGetTupleDescriptor(rel); + + skey = (ScanKey) palloc(natts * sizeof(ScanKeyData)); + + for (i = 0; i < natts; i++) { + arg = index_getattr(itup, i + 1, itupdesc, &null); + proc = metap->hashm_procid; + ScanKeyEntryInitialize(&skey[i], + 0x0, (AttrNumber) (i + 1), proc, arg); + } + + return (skey); +} + +void +_hash_freeskey(ScanKey skey) +{ + pfree(skey); +} + + +bool +_hash_checkqual(IndexScanDesc scan, IndexTuple itup) +{ + if (scan->numberOfKeys > 0) + return (index_keytest(itup, + RelationGetTupleDescriptor(scan->relation), + scan->numberOfKeys, scan->keyData)); + else + return (true); +} + +HashItem +_hash_formitem(IndexTuple itup) +{ + int nbytes_hitem; + HashItem hitem; + Size tuplen; + + /* disallow nulls in hash keys */ + if (itup->t_info & INDEX_NULL_MASK) + elog(WARN, "hash indices cannot include null keys"); + + /* make a copy of the index tuple with room for the sequence number */ + tuplen = IndexTupleSize(itup); + nbytes_hitem = tuplen + + (sizeof(HashItemData) - sizeof(IndexTupleData)); + + hitem = (HashItem) palloc(nbytes_hitem); + memmove((char *) &(hitem->hash_itup), (char *) itup, tuplen); + + return (hitem); +} + +Bucket +_hash_call(Relation rel, HashMetaPage metap, Datum key) +{ + uint32 n; + Bucket bucket; + RegProcedure proc; + + proc = metap->hashm_procid; + n = (uint32) fmgr(proc, key); + bucket = n & metap->hashm_highmask; + if (bucket > metap->hashm_maxbucket) + bucket = bucket & metap->hashm_lowmask; + return (bucket); +} + +/* + * _hash_log2 -- returns ceil(lg2(num)) + */ +uint32 +_hash_log2(uint32 num) +{ + uint32 i, limit; + + limit = 1; + for (i = 0; limit < num; limit = limit << 1, i++) + ; + return (i); +} + +/* + * _hash_checkpage -- sanity checks on the format of all hash pages + */ +void +_hash_checkpage(Page page, int flags) +{ + PageHeader ph = (PageHeader) page; + HashPageOpaque opaque; + + Assert(page); + Assert(ph->pd_lower >= (sizeof(PageHeaderData) - sizeof(ItemIdData))); +#if 1 + Assert(ph->pd_upper <= + (BLCKSZ - DOUBLEALIGN(sizeof(HashPageOpaqueData)))); + Assert(ph->pd_special == + (BLCKSZ - DOUBLEALIGN(sizeof(HashPageOpaqueData)))); + Assert(ph->pd_opaque.od_pagesize == BLCKSZ); +#endif + if (flags) { + opaque = (HashPageOpaque) PageGetSpecialPointer(page); + Assert(opaque->hasho_flag & flags); + } +} diff --git a/src/backend/access/heap/Makefile.inc b/src/backend/access/heap/Makefile.inc new file mode 100644 index 00000000000..f4f4bbb7031 --- /dev/null +++ b/src/backend/access/heap/Makefile.inc @@ -0,0 +1,14 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for access/heap +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/access/heap/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:11 scrappy Exp $ +# +#------------------------------------------------------------------------- + +SUBSRCS+= heapam.c hio.c stats.c diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c new file mode 100644 index 00000000000..4bf31efd832 --- /dev/null +++ b/src/backend/access/heap/heapam.c @@ -0,0 +1,1507 @@ +/*------------------------------------------------------------------------- + * + * heapam.c-- + * heap access method code + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/heap/heapam.c,v 1.1.1.1 1996/07/09 06:21:11 scrappy Exp $ + * + * + * INTERFACE ROUTINES + * heapgettup - fetch next heap tuple from a scan + * heap_open - open a heap relation by relationId + * heap_openr - open a heap relation by name + * heap_close - close a heap relation + * heap_beginscan - begin relation scan + * heap_rescan - restart a relation scan + * heap_endscan - end relation scan + * heap_getnext - retrieve next tuple in scan + * heap_fetch - retrive tuple with tid + * heap_insert - insert tuple into a relation + * heap_delete - delete a tuple from a relation + * heap_replace - replace a tuple in a relation with another tuple + * heap_markpos - mark scan position + * heap_restrpos - restore position to marked location + * + * NOTES + * This file contains the heap_ routines which implement + * the POSTGRES heap access method used for all POSTGRES + * relations. + * + * OLD COMMENTS + * struct relscan hints: (struct should be made AM independent?) + * + * rs_ctid is the tid of the last tuple returned by getnext. + * rs_ptid and rs_ntid are the tids of the previous and next tuples + * returned by getnext, respectively. NULL indicates an end of + * scan (either direction); NON indicates an unknow value. + * + * possible combinations: + * rs_p rs_c rs_n interpretation + * NULL NULL NULL empty scan + * NULL NULL NON at begining of scan + * NULL NULL t1 at begining of scan (with cached tid) + * NON NULL NULL at end of scan + * t1 NULL NULL at end of scan (with cached tid) + * NULL t1 NULL just returned only tuple + * NULL t1 NON just returned first tuple + * NULL t1 t2 returned first tuple (with cached tid) + * NON t1 NULL just returned last tuple + * t2 t1 NULL returned last tuple (with cached tid) + * t1 t2 NON in the middle of a forward scan + * NON t2 t1 in the middle of a reverse scan + * ti tj tk in the middle of a scan (w cached tid) + * + * Here NULL is ...tup == NULL && ...buf == InvalidBuffer, + * and NON is ...tup == NULL && ...buf == UnknownBuffer. + * + * Currently, the NONTID values are not cached with their actual + * values by getnext. Values may be cached by markpos since it stores + * all three tids. + * + * NOTE: the calls to elog() must stop. Should decide on an interface + * between the general and specific AM calls. + * + * XXX probably do not need a free tuple routine for heaps. + * Huh? Free tuple is not necessary for tuples returned by scans, but + * is necessary for tuples which are returned by + * RelationGetTupleByItemPointer. -hirohama + * + *------------------------------------------------------------------------- + */ +#include <sys/file.h> +#include <string.h> + +#include "postgres.h" + +#include "access/attnum.h" +#include "access/heapam.h" +#include "access/hio.h" +#include "access/htup.h" +#include "access/relscan.h" +#include "access/skey.h" + +#include "utils/tqual.h" +#include "access/valid.h" +#include "access/xact.h" + +#include "catalog/catalog.h" +#include "catalog/catname.h" +#include "storage/buf.h" +#include "storage/bufmgr.h" +#include "storage/bufpage.h" +#include "storage/itemid.h" +#include "storage/itemptr.h" +#include "storage/lmgr.h" + +#include "tcop/tcopdebug.h" +#include "miscadmin.h" + +#include "utils/memutils.h" +#include "utils/palloc.h" +#include "fmgr.h" +#include "utils/inval.h" +#include "utils/elog.h" +#include "utils/mcxt.h" +#include "utils/rel.h" +#include "utils/relcache.h" + +static bool ImmediateInvalidation; + +/* ---------------------------------------------------------------- + * heap support routines + * ---------------------------------------------------------------- + */ + +/* ---------------- + * initsdesc - sdesc code common to heap_beginscan and heap_rescan + * ---------------- + */ +static void +initsdesc(HeapScanDesc sdesc, + Relation relation, + int atend, + unsigned nkeys, + ScanKey key) +{ + if (!RelationGetNumberOfBlocks(relation)) { + /* ---------------- + * relation is empty + * ---------------- + */ + sdesc->rs_ntup = sdesc->rs_ctup = sdesc->rs_ptup = NULL; + sdesc->rs_nbuf = sdesc->rs_cbuf = sdesc->rs_pbuf = InvalidBuffer; + } else if (atend) { + /* ---------------- + * reverse scan + * ---------------- + */ + sdesc->rs_ntup = sdesc->rs_ctup = NULL; + sdesc->rs_nbuf = sdesc->rs_cbuf = InvalidBuffer; + sdesc->rs_ptup = NULL; + sdesc->rs_pbuf = UnknownBuffer; + } else { + /* ---------------- + * forward scan + * ---------------- + */ + sdesc->rs_ctup = sdesc->rs_ptup = NULL; + sdesc->rs_cbuf = sdesc->rs_pbuf = InvalidBuffer; + sdesc->rs_ntup = NULL; + sdesc->rs_nbuf = UnknownBuffer; + } /* invalid too */ + + /* we don't have a marked position... */ + ItemPointerSetInvalid(&(sdesc->rs_mptid)); + ItemPointerSetInvalid(&(sdesc->rs_mctid)); + ItemPointerSetInvalid(&(sdesc->rs_mntid)); + ItemPointerSetInvalid(&(sdesc->rs_mcd)); + + /* ---------------- + * copy the scan key, if appropriate + * ---------------- + */ + if (key != NULL) + memmove(sdesc->rs_key, key, nkeys * sizeof(ScanKeyData)); +} + +/* ---------------- + * unpinsdesc - code common to heap_rescan and heap_endscan + * ---------------- + */ +static void +unpinsdesc(HeapScanDesc sdesc) +{ + if (BufferIsValid(sdesc->rs_pbuf)) { + ReleaseBuffer(sdesc->rs_pbuf); + } + + /* ------------------------------------ + * Scan will pin buffer one for each non-NULL tuple pointer + * (ptup, ctup, ntup), so they have to be unpinned multiple + * times. + * ------------------------------------ + */ + if (BufferIsValid(sdesc->rs_cbuf)) { + ReleaseBuffer(sdesc->rs_cbuf); + } + + if (BufferIsValid(sdesc->rs_nbuf)) { + ReleaseBuffer(sdesc->rs_nbuf); + } +} + +/* ------------------------------------------ + * nextpage + * + * figure out the next page to scan after the current page + * taking into account of possible adjustment of degrees of + * parallelism + * ------------------------------------------ + */ +static int +nextpage(int page, int dir) +{ + return((dir<0)?page-1:page+1); +} + +/* ---------------- + * heapgettup - fetch next heap tuple + * + * routine used by heap_getnext() which does most of the + * real work in scanning tuples. + * ---------------- + */ +static HeapTuple +heapgettup(Relation relation, + ItemPointer tid, + int dir, + Buffer *b, + TimeQual timeQual, + int nkeys, + ScanKey key) +{ + ItemId lpp; + Page dp; + int page; + int pages; + int lines; + HeapTuple rtup; + OffsetNumber lineoff; + int linesleft; + + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_heapgettup); + IncrHeapAccessStat(global_heapgettup); + + /* ---------------- + * debugging stuff + * + * check validity of arguments, here and for other functions too + * Note: no locking manipulations needed--this is a local function + * ---------------- + */ +#ifdef HEAPDEBUGALL + if (ItemPointerIsValid(tid)) { + elog(DEBUG, "heapgettup(%.16s, tid=0x%x[%d,%d], dir=%d, ...)", + RelationGetRelationName(relation), tid, tid->ip_blkid, + tid->ip_posid, dir); + } else { + elog(DEBUG, "heapgettup(%.16s, tid=0x%x, dir=%d, ...)", + RelationGetRelationName(relation), tid, dir); + } + elog(DEBUG, "heapgettup(..., b=0x%x, timeQ=0x%x, nkeys=%d, key=0x%x", + b, timeQual, nkeys, key); + if (timeQual == SelfTimeQual) { + elog(DEBUG, "heapgettup: relation(%c)=`%.16s', SelfTimeQual", + relation->rd_rel->relkind, &relation->rd_rel->relname); + } else { + elog(DEBUG, "heapgettup: relation(%c)=`%.16s', timeQual=%d", + relation->rd_rel->relkind, &relation->rd_rel->relname, + timeQual); + } +#endif /* !defined(HEAPDEBUGALL) */ + + if (!ItemPointerIsValid(tid)) { + Assert(!PointerIsValid(tid)); + } + + /* ---------------- + * return null immediately if relation is empty + * ---------------- + */ + if (!(pages = relation->rd_nblocks)) + return (NULL); + + /* ---------------- + * calculate next starting lineoff, given scan direction + * ---------------- + */ + if (!dir) { + /* ---------------- + * ``no movement'' scan direction + * ---------------- + */ + /* assume it is a valid TID XXX */ + if (ItemPointerIsValid(tid) == false) { + *b = InvalidBuffer; + return (NULL); + } + *b = RelationGetBufferWithBuffer(relation, + ItemPointerGetBlockNumber(tid), + *b); + +#ifndef NO_BUFFERISVALID + if (!BufferIsValid(*b)) { + elog(WARN, "heapgettup: failed ReadBuffer"); + } +#endif + + dp = (Page) BufferGetPage(*b); + lineoff = ItemPointerGetOffsetNumber(tid); + lpp = PageGetItemId(dp, lineoff); + + rtup = (HeapTuple)PageGetItem((Page) dp, lpp); + return (rtup); + + } else if (dir < 0) { + /* ---------------- + * reverse scan direction + * ---------------- + */ + if (ItemPointerIsValid(tid) == false) { + tid = NULL; + } + if (tid == NULL) { + page = pages - 1; /* final page */ + } else { + page = ItemPointerGetBlockNumber(tid); /* current page */ + } + if (page < 0) { + *b = InvalidBuffer; + return (NULL); + } + + *b = RelationGetBufferWithBuffer(relation, page, *b); +#ifndef NO_BUFFERISVALID + if (!BufferIsValid(*b)) { + elog(WARN, "heapgettup: failed ReadBuffer"); + } +#endif + + dp = (Page) BufferGetPage(*b); + lines = PageGetMaxOffsetNumber(dp); + if (tid == NULL) { + lineoff = lines; /* final offnum */ + } else { + lineoff = /* previous offnum */ + OffsetNumberPrev(ItemPointerGetOffsetNumber(tid)); + } + /* page and lineoff now reference the physically previous tid */ + + } else { + /* ---------------- + * forward scan direction + * ---------------- + */ + if (ItemPointerIsValid(tid) == false) { + page = 0; /* first page */ + lineoff = FirstOffsetNumber; /* first offnum */ + } else { + page = ItemPointerGetBlockNumber(tid); /* current page */ + lineoff = /* next offnum */ + OffsetNumberNext(ItemPointerGetOffsetNumber(tid)); + } + + if (page >= pages) { + *b = InvalidBuffer; + return (NULL); + } + /* page and lineoff now reference the physically next tid */ + + *b = RelationGetBufferWithBuffer(relation, page, *b); +#ifndef NO_BUFFERISVALID + if (!BufferIsValid(*b)) { + elog(WARN, "heapgettup: failed ReadBuffer"); + } +#endif + + dp = (Page) BufferGetPage(*b); + lines = PageGetMaxOffsetNumber(dp); + } + + /* 'dir' is now non-zero */ + + /* ---------------- + * calculate line pointer and number of remaining items + * to check on this page. + * ---------------- + */ + lpp = PageGetItemId(dp, lineoff); + if (dir < 0) { + linesleft = lineoff - 1; + } else { + linesleft = lines - lineoff; + } + + /* ---------------- + * advance the scan until we find a qualifying tuple or + * run out of stuff to scan + * ---------------- + */ + for (;;) { + while (linesleft >= 0) { + /* ---------------- + * if current tuple qualifies, return it. + * ---------------- + */ + if ((rtup = heap_tuple_satisfies(lpp, relation, (PageHeader) dp, + timeQual, nkeys, key)) != NULL) { + ItemPointer iptr = &(rtup->t_ctid); + if (ItemPointerGetBlockNumber(iptr) != page) { + /* + * set block id to the correct page number + * --- this is a hack to support the virtual fragment + * concept + */ + ItemPointerSetBlockNumber(iptr, page); + } + return (rtup); + } + + /* ---------------- + * otherwise move to the next item on the page + * ---------------- + */ + --linesleft; + if (dir < 0) { + --lpp; /* move back in this page's ItemId array */ + } else { + ++lpp; /* move forward in this page's ItemId array */ + } + } + + /* ---------------- + * if we get here, it means we've exhausted the items on + * this page and it's time to move to the next.. + * ---------------- + */ + page = nextpage(page, dir); + + /* ---------------- + * return NULL if we've exhausted all the pages.. + * ---------------- + */ + if (page < 0 || page >= pages) { + if (BufferIsValid(*b)) + ReleaseBuffer(*b); + *b = InvalidBuffer; + return (NULL); + } + + *b = ReleaseAndReadBuffer(*b, relation, page); + +#ifndef NO_BUFFERISVALID + if (!BufferIsValid(*b)) { + elog(WARN, "heapgettup: failed ReadBuffer"); + } +#endif + dp = (Page) BufferGetPage(*b); + lines = lineoff = PageGetMaxOffsetNumber((Page) dp); + linesleft = lines - 1; + if (dir < 0) { + lpp = PageGetItemId(dp, lineoff); + } else { + lpp = PageGetItemId(dp, FirstOffsetNumber); + } + } +} + +void +doinsert(Relation relation, HeapTuple tup) +{ + RelationPutHeapTupleAtEnd(relation, tup); + return; +} + +/* + * HeapScanIsValid is now a macro in relscan.h -cim 4/27/91 + */ + +/* ---------------- + * SetHeapAccessMethodImmediateInvalidation + * ---------------- + */ +void +SetHeapAccessMethodImmediateInvalidation(bool on) +{ + ImmediateInvalidation = on; +} + +/* ---------------------------------------------------------------- + * heap access method interface + * ---------------------------------------------------------------- + */ +/* ---------------- + * heap_open - open a heap relation by relationId + * + * presently the relcache routines do all the work we need + * to open/close heap relations. + * ---------------- + */ +Relation +heap_open(Oid relationId) +{ + Relation r; + + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_open); + IncrHeapAccessStat(global_open); + + r = (Relation) RelationIdGetRelation(relationId); + + if (RelationIsValid(r) && r->rd_rel->relkind == RELKIND_INDEX) { + elog(WARN, "%s is an index relation", r->rd_rel->relname.data); + } + + return (r); +} + +/* ---------------- + * heap_openr - open a heap relation by name + * + * presently the relcache routines do all the work we need + * to open/close heap relations. + * ---------------- + */ +Relation +heap_openr(char *relationName) +{ + Relation r; + + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_openr); + IncrHeapAccessStat(global_openr); + + r = RelationNameGetRelation(relationName); + + if (RelationIsValid(r) && r->rd_rel->relkind == RELKIND_INDEX) { + elog(WARN, "%s is an index relation", r->rd_rel->relname.data); + } + + return (r); +} + +/* ---------------- + * heap_close - close a heap relation + * + * presently the relcache routines do all the work we need + * to open/close heap relations. + * ---------------- + */ +void +heap_close(Relation relation) +{ + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_close); + IncrHeapAccessStat(global_close); + + (void) RelationClose(relation); +} + + +/* ---------------- + * heap_beginscan - begin relation scan + * ---------------- + */ +HeapScanDesc +heap_beginscan(Relation relation, + int atend, + TimeQual timeQual, + unsigned nkeys, + ScanKey key) +{ + HeapScanDesc sdesc; + + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_beginscan); + IncrHeapAccessStat(global_beginscan); + + /* ---------------- + * sanity checks + * ---------------- + */ + if (RelationIsValid(relation) == false) + elog(WARN, "heap_beginscan: !RelationIsValid(relation)"); + + /* ---------------- + * set relation level read lock + * ---------------- + */ + RelationSetLockForRead(relation); + + /* XXX someday assert SelfTimeQual if relkind == RELKIND_UNCATALOGED */ + if (relation->rd_rel->relkind == RELKIND_UNCATALOGED) { + timeQual = SelfTimeQual; + } + + /* ---------------- + * increment relation ref count while scanning relation + * ---------------- + */ + RelationIncrementReferenceCount(relation); + + /* ---------------- + * allocate and initialize scan descriptor + * ---------------- + */ + sdesc = (HeapScanDesc) palloc(sizeof(HeapScanDescData)); + + relation->rd_nblocks = smgrnblocks(relation->rd_rel->relsmgr, relation); + sdesc->rs_rd = relation; + + if (nkeys) { + /* + * we do this here instead of in initsdesc() because heap_rescan also + * calls initsdesc() and we don't want to allocate memory again + */ + sdesc->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys); + } else { + sdesc->rs_key = NULL; + } + + initsdesc(sdesc, relation, atend, nkeys, key); + + sdesc->rs_atend = atend; + sdesc->rs_tr = timeQual; + sdesc->rs_nkeys = (short)nkeys; + + return (sdesc); +} + +/* ---------------- + * heap_rescan - restart a relation scan + * ---------------- + */ +void +heap_rescan(HeapScanDesc sdesc, + bool scanFromEnd, + ScanKey key) +{ + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_rescan); + IncrHeapAccessStat(global_rescan); + + /* Note: set relation level read lock is still set */ + + /* ---------------- + * unpin scan buffers + * ---------------- + */ + unpinsdesc(sdesc); + + /* ---------------- + * reinitialize scan descriptor + * ---------------- + */ + initsdesc(sdesc, sdesc->rs_rd, scanFromEnd, sdesc->rs_nkeys, key); + sdesc->rs_atend = (bool) scanFromEnd; +} + +/* ---------------- + * heap_endscan - end relation scan + * + * See how to integrate with index scans. + * Check handling if reldesc caching. + * ---------------- + */ +void +heap_endscan(HeapScanDesc sdesc) +{ + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_endscan); + IncrHeapAccessStat(global_endscan); + + /* Note: no locking manipulations needed */ + + /* ---------------- + * unpin scan buffers + * ---------------- + */ + unpinsdesc(sdesc); + + /* ---------------- + * decrement relation reference count and free scan descriptor storage + * ---------------- + */ + RelationDecrementReferenceCount(sdesc->rs_rd); + + /* ---------------- + * Non 2-phase read locks on catalog relations + * ---------------- + */ + if ( IsSystemRelationName(RelationGetRelationName(sdesc->rs_rd)->data) ) + + RelationUnsetLockForRead(sdesc->rs_rd); + + pfree(sdesc); /* XXX */ +} + +/* ---------------- + * heap_getnext - retrieve next tuple in scan + * + * Fix to work with index relations. + * ---------------- + */ + +#ifdef HEAPDEBUGALL +#define HEAPDEBUG_1 \ +elog(DEBUG, "heap_getnext([%s,nkeys=%d],backw=%d,0x%x) called", \ + sdesc->rs_rd->rd_rel->relname.data, sdesc->rs_nkeys, backw, b) + +#define HEAPDEBUG_2 \ + elog(DEBUG, "heap_getnext called with backw (no tracing yet)") + +#define HEAPDEBUG_3 \ + elog(DEBUG, "heap_getnext returns NULL at end") + +#define HEAPDEBUG_4 \ + elog(DEBUG, "heap_getnext valid buffer UNPIN'd") + +#define HEAPDEBUG_5 \ + elog(DEBUG, "heap_getnext next tuple was cached") + +#define HEAPDEBUG_6 \ + elog(DEBUG, "heap_getnext returning EOS") + +#define HEAPDEBUG_7 \ + elog(DEBUG, "heap_getnext returning tuple"); +#else +#define HEAPDEBUG_1 +#define HEAPDEBUG_2 +#define HEAPDEBUG_3 +#define HEAPDEBUG_4 +#define HEAPDEBUG_5 +#define HEAPDEBUG_6 +#define HEAPDEBUG_7 +#endif /* !defined(HEAPDEBUGALL) */ + + +HeapTuple +heap_getnext(HeapScanDesc scandesc, + int backw, + Buffer *b) +{ + register HeapScanDesc sdesc = scandesc; + Buffer localb; + + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_getnext); + IncrHeapAccessStat(global_getnext); + + /* Note: no locking manipulations needed */ + + /* ---------------- + * argument checks + * ---------------- + */ + if (sdesc == NULL) + elog(WARN, "heap_getnext: NULL relscan"); + + /* ---------------- + * initialize return buffer to InvalidBuffer + * ---------------- + */ + if (! PointerIsValid(b)) b = &localb; + (*b) = InvalidBuffer; + + HEAPDEBUG_1; /* heap_getnext( info ) */ + + if (backw) { + /* ---------------- + * handle reverse scan + * ---------------- + */ + HEAPDEBUG_2; /* heap_getnext called with backw */ + + if (sdesc->rs_ptup == sdesc->rs_ctup && + BufferIsInvalid(sdesc->rs_pbuf)) + { + if (BufferIsValid(sdesc->rs_nbuf)) + ReleaseBuffer(sdesc->rs_nbuf); + return (NULL); + } + + /* + * Copy the "current" tuple/buffer + * to "next". Pin/unpin the buffers + * accordingly + */ + if (sdesc->rs_nbuf != sdesc->rs_cbuf) { + if (BufferIsValid(sdesc->rs_nbuf)) + ReleaseBuffer(sdesc->rs_nbuf); + if (BufferIsValid(sdesc->rs_cbuf)) + IncrBufferRefCount(sdesc->rs_cbuf); + } + sdesc->rs_ntup = sdesc->rs_ctup; + sdesc->rs_nbuf = sdesc->rs_cbuf; + + if (sdesc->rs_ptup != NULL) { + if (sdesc->rs_cbuf != sdesc->rs_pbuf) { + if (BufferIsValid(sdesc->rs_cbuf)) + ReleaseBuffer(sdesc->rs_cbuf); + if (BufferIsValid(sdesc->rs_pbuf)) + IncrBufferRefCount(sdesc->rs_pbuf); + } + sdesc->rs_ctup = sdesc->rs_ptup; + sdesc->rs_cbuf = sdesc->rs_pbuf; + } else { /* NONTUP */ + ItemPointer iptr; + + iptr = (sdesc->rs_ctup != NULL) ? + &(sdesc->rs_ctup->t_ctid) : (ItemPointer) NULL; + + /* Don't release sdesc->rs_cbuf at this point, because + heapgettup doesn't increase PrivateRefCount if it + is already set. On a backward scan, both rs_ctup and rs_ntup + usually point to the same buffer page, so + PrivateRefCount[rs_cbuf] should be 2 (or more, if for instance + ctup is stored in a TupleTableSlot). - 01/09/94 */ + + sdesc->rs_ctup = (HeapTuple) + heapgettup(sdesc->rs_rd, + iptr, + -1, + &(sdesc->rs_cbuf), + sdesc->rs_tr, + sdesc->rs_nkeys, + sdesc->rs_key); + } + + if (sdesc->rs_ctup == NULL && !BufferIsValid(sdesc->rs_cbuf)) + { + if (BufferIsValid(sdesc->rs_pbuf)) + ReleaseBuffer(sdesc->rs_pbuf); + sdesc->rs_ptup = NULL; + sdesc->rs_pbuf = InvalidBuffer; + if (BufferIsValid(sdesc->rs_nbuf)) + ReleaseBuffer(sdesc->rs_nbuf); + sdesc->rs_ntup = NULL; + sdesc->rs_nbuf = InvalidBuffer; + return (NULL); + } + + if (BufferIsValid(sdesc->rs_pbuf)) + ReleaseBuffer(sdesc->rs_pbuf); + sdesc->rs_ptup = NULL; + sdesc->rs_pbuf = UnknownBuffer; + + } else { + /* ---------------- + * handle forward scan + * ---------------- + */ + if (sdesc->rs_ctup == sdesc->rs_ntup && + BufferIsInvalid(sdesc->rs_nbuf)) { + if (BufferIsValid(sdesc->rs_pbuf)) + ReleaseBuffer(sdesc->rs_pbuf); + HEAPDEBUG_3; /* heap_getnext returns NULL at end */ + return (NULL); + } + + /* + * Copy the "current" tuple/buffer + * to "previous". Pin/unpin the buffers + * accordingly + */ + if (sdesc->rs_pbuf != sdesc->rs_cbuf) { + if (BufferIsValid(sdesc->rs_pbuf)) + ReleaseBuffer(sdesc->rs_pbuf); + if (BufferIsValid(sdesc->rs_cbuf)) + IncrBufferRefCount(sdesc->rs_cbuf); + } + sdesc->rs_ptup = sdesc->rs_ctup; + sdesc->rs_pbuf = sdesc->rs_cbuf; + + if (sdesc->rs_ntup != NULL) { + if (sdesc->rs_cbuf != sdesc->rs_nbuf) { + if (BufferIsValid(sdesc->rs_cbuf)) + ReleaseBuffer(sdesc->rs_cbuf); + if (BufferIsValid(sdesc->rs_nbuf)) + IncrBufferRefCount(sdesc->rs_nbuf); + } + sdesc->rs_ctup = sdesc->rs_ntup; + sdesc->rs_cbuf = sdesc->rs_nbuf; + HEAPDEBUG_5; /* heap_getnext next tuple was cached */ + } else { /* NONTUP */ + ItemPointer iptr; + + iptr = (sdesc->rs_ctup != NULL) ? + &sdesc->rs_ctup->t_ctid : (ItemPointer) NULL; + + /* Don't release sdesc->rs_cbuf at this point, because + heapgettup doesn't increase PrivateRefCount if it + is already set. On a forward scan, both rs_ctup and rs_ptup + usually point to the same buffer page, so + PrivateRefCount[rs_cbuf] should be 2 (or more, if for instance + ctup is stored in a TupleTableSlot). - 01/09/93 */ + + sdesc->rs_ctup = (HeapTuple) + heapgettup(sdesc->rs_rd, + iptr, + 1, + &sdesc->rs_cbuf, + sdesc->rs_tr, + sdesc->rs_nkeys, + sdesc->rs_key); + } + + if (sdesc->rs_ctup == NULL && !BufferIsValid(sdesc->rs_cbuf)) { + if (BufferIsValid(sdesc->rs_nbuf)) + ReleaseBuffer(sdesc->rs_nbuf); + sdesc->rs_ntup = NULL; + sdesc->rs_nbuf = InvalidBuffer; + if (BufferIsValid(sdesc->rs_pbuf)) + ReleaseBuffer(sdesc->rs_pbuf); + sdesc->rs_ptup = NULL; + sdesc->rs_pbuf = InvalidBuffer; + HEAPDEBUG_6; /* heap_getnext returning EOS */ + return (NULL); + } + + if (BufferIsValid(sdesc->rs_nbuf)) + ReleaseBuffer(sdesc->rs_nbuf); + sdesc->rs_ntup = NULL; + sdesc->rs_nbuf = UnknownBuffer; + } + + /* ---------------- + * if we get here it means we have a new current scan tuple, so + * point to the proper return buffer and return the tuple. + * ---------------- + */ + (*b) = sdesc->rs_cbuf; + + HEAPDEBUG_7; /* heap_getnext returning tuple */ + + return (sdesc->rs_ctup); +} + +/* ---------------- + * heap_fetch - retrive tuple with tid + * + * Currently ignores LP_IVALID during processing! + * ---------------- + */ +HeapTuple +heap_fetch(Relation relation, + TimeQual timeQual, + ItemPointer tid, + Buffer *b) +{ + ItemId lp; + Buffer buffer; + PageHeader dp; + HeapTuple tuple; + OffsetNumber offnum; + + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_fetch); + IncrHeapAccessStat(global_fetch); + + /* + * Note: This is collosally expensive - does two system calls per + * indexscan tuple fetch. Not good, and since we should be doing + * page level locking by the scanner anyway, it is commented out. + */ + + /* RelationSetLockForTupleRead(relation, tid); */ + + /* ---------------- + * get the buffer from the relation descriptor + * Note that this does a buffer pin. + * ---------------- + */ + + buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid)); + +#ifndef NO_BUFFERISVALID + if (!BufferIsValid(buffer)) { + elog(WARN, "heap_fetch: %s relation: ReadBuffer(%lx) failed", + &relation->rd_rel->relname, (long)tid); + } +#endif + + /* ---------------- + * get the item line pointer corresponding to the requested tid + * ---------------- + */ + dp = (PageHeader) BufferGetPage(buffer); + offnum = ItemPointerGetOffsetNumber(tid); + lp = PageGetItemId(dp, offnum); + + /* ---------------- + * more sanity checks + * ---------------- + */ + + Assert(ItemIdIsUsed(lp)); + + /* ---------------- + * check time qualification of tid + * ---------------- + */ + + tuple = heap_tuple_satisfies(lp, relation, dp, + timeQual, 0,(ScanKey)NULL); + + if (tuple == NULL) + { + ReleaseBuffer(buffer); + return (NULL); + } + + /* ---------------- + * all checks passed, now either return a copy of the tuple + * or pin the buffer page and return a pointer, depending on + * whether caller gave us a valid b. + * ---------------- + */ + + if (PointerIsValid(b)) { + *b = buffer; + } else { + tuple = heap_copytuple(tuple); + ReleaseBuffer(buffer); + } + return (tuple); +} + +/* ---------------- + * heap_insert - insert tuple + * + * The assignment of t_min (and thus the others) should be + * removed eventually. + * + * Currently places the tuple onto the last page. If there is no room, + * it is placed on new pages. (Heap relations) + * Note that concurrent inserts during a scan will probably have + * unexpected results, though this will be fixed eventually. + * + * Fix to work with indexes. + * ---------------- + */ +Oid +heap_insert(Relation relation, HeapTuple tup) +{ + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_insert); + IncrHeapAccessStat(global_insert); + + /* ---------------- + * set relation level write lock. If this is a "local" relation (not + * visible to others), we don't need to set a write lock. + * ---------------- + */ + if (!relation->rd_islocal) + RelationSetLockForWrite(relation); + + /* ---------------- + * If the object id of this tuple has already been assigned, trust + * the caller. There are a couple of ways this can happen. At initial + * db creation, the backend program sets oids for tuples. When we + * define an index, we set the oid. Finally, in the future, we may + * allow users to set their own object ids in order to support a + * persistent object store (objects need to contain pointers to one + * another). + * ---------------- + */ + if (!OidIsValid(tup->t_oid)) { + tup->t_oid = newoid(); + LastOidProcessed = tup->t_oid; + } + + TransactionIdStore(GetCurrentTransactionId(), &(tup->t_xmin)); + tup->t_cmin = GetCurrentCommandId(); + StoreInvalidTransactionId(&(tup->t_xmax)); + tup->t_tmin = INVALID_ABSTIME; + tup->t_tmax = CURRENT_ABSTIME; + + doinsert(relation, tup); + + if ( IsSystemRelationName(RelationGetRelationName(relation)->data)) { + RelationUnsetLockForWrite(relation); + + /* ---------------- + * invalidate caches (only works for system relations) + * ---------------- + */ + SetRefreshWhenInvalidate(ImmediateInvalidation); + RelationInvalidateHeapTuple(relation, tup); + SetRefreshWhenInvalidate((bool)!ImmediateInvalidation); + } + + return(tup->t_oid); +} + +/* ---------------- + * heap_delete - delete a tuple + * + * Must decide how to handle errors. + * ---------------- + */ +void +heap_delete(Relation relation, ItemPointer tid) +{ + ItemId lp; + HeapTuple tp; + PageHeader dp; + Buffer b; + + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_delete); + IncrHeapAccessStat(global_delete); + + /* ---------------- + * sanity check + * ---------------- + */ + Assert(ItemPointerIsValid(tid)); + + /* ---------------- + * set relation level write lock + * ---------------- + */ + RelationSetLockForWrite(relation); + + b = ReadBuffer(relation, ItemPointerGetBlockNumber(tid)); + +#ifndef NO_BUFFERISVALID + if (!BufferIsValid(b)) { /* XXX L_SH better ??? */ + elog(WARN, "heap_delete: failed ReadBuffer"); + } +#endif /* NO_BUFFERISVALID */ + + dp = (PageHeader) BufferGetPage(b); + lp = PageGetItemId(dp, ItemPointerGetOffsetNumber(tid)); + + /* ---------------- + * check that we're deleteing a valid item + * ---------------- + */ + if (!(tp = heap_tuple_satisfies(lp, relation, dp, + NowTimeQual, 0, (ScanKey) NULL))) { + + /* XXX call something else */ + ReleaseBuffer(b); + + elog(WARN, "heap_delete: (am)invalid tid"); + } + + /* ---------------- + * get the tuple and lock tell the buffer manager we want + * exclusive access to the page + * ---------------- + */ + + /* ---------------- + * store transaction information of xact deleting the tuple + * ---------------- + */ + TransactionIdStore(GetCurrentTransactionId(), &(tp->t_xmax)); + tp->t_cmax = GetCurrentCommandId(); + ItemPointerSetInvalid(&tp->t_chain); + + /* ---------------- + * invalidate caches + * ---------------- + */ + SetRefreshWhenInvalidate(ImmediateInvalidation); + RelationInvalidateHeapTuple(relation, tp); + SetRefreshWhenInvalidate((bool)!ImmediateInvalidation); + + WriteBuffer(b); + if ( IsSystemRelationName(RelationGetRelationName(relation)->data) ) + RelationUnsetLockForWrite(relation); +} + +/* ---------------- + * heap_replace - replace a tuple + * + * Must decide how to handle errors. + * + * Fix arguments, work with indexes. + * + * 12/30/93 - modified the return value to be 1 when + * a non-functional update is detected. This + * prevents the calling routine from updating + * indices unnecessarily. -kw + * + * ---------------- + */ +int +heap_replace(Relation relation, ItemPointer otid, HeapTuple tup) +{ + ItemId lp; + HeapTuple tp; + Page dp; + Buffer buffer; + + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_replace); + IncrHeapAccessStat(global_replace); + + /* ---------------- + * sanity checks + * ---------------- + */ + Assert(ItemPointerIsValid(otid)); + + /* ---------------- + * set relation level write lock + * ---------------- + */ + if (!relation->rd_islocal) + RelationSetLockForWrite(relation); + + buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(otid)); +#ifndef NO_BUFFERISVALID + if (!BufferIsValid(buffer)) { + /* XXX L_SH better ??? */ + elog(WARN, "amreplace: failed ReadBuffer"); + } +#endif /* NO_BUFFERISVALID */ + + dp = (Page) BufferGetPage(buffer); + lp = PageGetItemId(dp, ItemPointerGetOffsetNumber(otid)); + + /* ---------------- + * logically delete old item + * ---------------- + */ + + tp = (HeapTuple) PageGetItem(dp, lp); + Assert(HeapTupleIsValid(tp)); + + /* ----------------- + * the following test should be able to catch all non-functional + * update attempts and shut out all ghost tuples. + * XXX In the future, Spyros may need to update the rule lock on a tuple + * more than once within the same command and same transaction. + * He will have to introduce a new flag to override the following check. + * -- Wei + * + * ----------------- + */ + + if (TupleUpdatedByCurXactAndCmd(tp)) { + elog(NOTICE, "Non-functional update, only first update is performed"); + if ( IsSystemRelationName(RelationGetRelationName(relation)->data) ) + RelationUnsetLockForWrite(relation); + ReleaseBuffer(buffer); + return(1); + } + + /* ---------------- + * check that we're replacing a valid item - + * + * NOTE that this check must follow the non-functional update test + * above as it can happen that we try to 'replace' the same tuple + * twice in a single transaction. The second time around the + * tuple will fail the NowTimeQual. We don't want to abort the + * xact, we only want to flag the 'non-functional' NOTICE. -mer + * ---------------- + */ + if (!heap_tuple_satisfies(lp, + relation, + (PageHeader)dp, + NowTimeQual, + 0, + (ScanKey)NULL)) + { + ReleaseBuffer(buffer); + elog(WARN, "heap_replace: (am)invalid otid"); + } + + /* XXX order problems if not atomic assignment ??? */ + tup->t_oid = tp->t_oid; + TransactionIdStore(GetCurrentTransactionId(), &(tup->t_xmin)); + tup->t_cmin = GetCurrentCommandId(); + StoreInvalidTransactionId(&(tup->t_xmax)); + tup->t_tmin = INVALID_ABSTIME; + tup->t_tmax = CURRENT_ABSTIME; + ItemPointerSetInvalid(&tup->t_chain); + + /* ---------------- + * insert new item + * ---------------- + */ + if ((unsigned)DOUBLEALIGN(tup->t_len) <= PageGetFreeSpace((Page) dp)) { + RelationPutHeapTuple(relation, BufferGetBlockNumber(buffer), tup); + } else { + /* ---------------- + * new item won't fit on same page as old item, have to look + * for a new place to put it. + * ---------------- + */ + doinsert(relation, tup); + } + + /* ---------------- + * new item in place, now record transaction information + * ---------------- + */ + TransactionIdStore(GetCurrentTransactionId(), &(tp->t_xmax)); + tp->t_cmax = GetCurrentCommandId(); + tp->t_chain = tup->t_ctid; + + /* ---------------- + * invalidate caches + * ---------------- + */ + SetRefreshWhenInvalidate(ImmediateInvalidation); + RelationInvalidateHeapTuple(relation, tp); + SetRefreshWhenInvalidate((bool)!ImmediateInvalidation); + + WriteBuffer(buffer); + + if ( IsSystemRelationName(RelationGetRelationName(relation)->data) ) + RelationUnsetLockForWrite(relation); + + return(0); +} + +/* ---------------- + * heap_markpos - mark scan position + * + * Note: + * Should only one mark be maintained per scan at one time. + * Check if this can be done generally--say calls to get the + * next/previous tuple and NEVER pass struct scandesc to the + * user AM's. Now, the mark is sent to the executor for safekeeping. + * Probably can store this info into a GENERAL scan structure. + * + * May be best to change this call to store the marked position + * (up to 2?) in the scan structure itself. + * Fix to use the proper caching structure. + * ---------------- + */ +void +heap_markpos(HeapScanDesc sdesc) +{ + + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_markpos); + IncrHeapAccessStat(global_markpos); + + /* Note: no locking manipulations needed */ + + if (sdesc->rs_ptup == NULL && + BufferIsUnknown(sdesc->rs_pbuf)) { /* == NONTUP */ + sdesc->rs_ptup = (HeapTuple) + heapgettup(sdesc->rs_rd, + (sdesc->rs_ctup == NULL) ? + (ItemPointer)NULL : &sdesc->rs_ctup->t_ctid, + -1, + &sdesc->rs_pbuf, + sdesc->rs_tr, + sdesc->rs_nkeys, + sdesc->rs_key); + + } else if (sdesc->rs_ntup == NULL && + BufferIsUnknown(sdesc->rs_nbuf)) { /* == NONTUP */ + sdesc->rs_ntup = (HeapTuple) + heapgettup(sdesc->rs_rd, + (sdesc->rs_ctup == NULL) ? + (ItemPointer)NULL : &sdesc->rs_ctup->t_ctid, + 1, + &sdesc->rs_nbuf, + sdesc->rs_tr, + sdesc->rs_nkeys, + sdesc->rs_key); + } + + /* ---------------- + * Should not unpin the buffer pages. They may still be in use. + * ---------------- + */ + if (sdesc->rs_ptup != NULL) { + sdesc->rs_mptid = sdesc->rs_ptup->t_ctid; + } else { + ItemPointerSetInvalid(&sdesc->rs_mptid); + } + if (sdesc->rs_ctup != NULL) { + sdesc->rs_mctid = sdesc->rs_ctup->t_ctid; + } else { + ItemPointerSetInvalid(&sdesc->rs_mctid); + } + if (sdesc->rs_ntup != NULL) { + sdesc->rs_mntid = sdesc->rs_ntup->t_ctid; + } else { + ItemPointerSetInvalid(&sdesc->rs_mntid); + } +} + +/* ---------------- + * heap_restrpos - restore position to marked location + * + * Note: there are bad side effects here. If we were past the end + * of a relation when heapmarkpos is called, then if the relation is + * extended via insert, then the next call to heaprestrpos will set + * cause the added tuples to be visible when the scan continues. + * Problems also arise if the TID's are rearranged!!! + * + * Now pins buffer once for each valid tuple pointer (rs_ptup, + * rs_ctup, rs_ntup) referencing it. + * - 01/13/94 + * + * XXX might be better to do direct access instead of + * using the generality of heapgettup(). + * + * XXX It is very possible that when a scan is restored, that a tuple + * XXX which previously qualified may fail for time range purposes, unless + * XXX some form of locking exists (ie., portals currently can act funny. + * ---------------- + */ +void +heap_restrpos(HeapScanDesc sdesc) +{ + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_restrpos); + IncrHeapAccessStat(global_restrpos); + + /* XXX no amrestrpos checking that ammarkpos called */ + + /* Note: no locking manipulations needed */ + + unpinsdesc(sdesc); + + /* force heapgettup to pin buffer for each loaded tuple */ + sdesc->rs_pbuf = InvalidBuffer; + sdesc->rs_cbuf = InvalidBuffer; + sdesc->rs_nbuf = InvalidBuffer; + + if (!ItemPointerIsValid(&sdesc->rs_mptid)) { + sdesc->rs_ptup = NULL; + } else { + sdesc->rs_ptup = (HeapTuple) + heapgettup(sdesc->rs_rd, + &sdesc->rs_mptid, + 0, + &sdesc->rs_pbuf, + NowTimeQual, + 0, + (ScanKey) NULL); + } + + if (!ItemPointerIsValid(&sdesc->rs_mctid)) { + sdesc->rs_ctup = NULL; + } else { + sdesc->rs_ctup = (HeapTuple) + heapgettup(sdesc->rs_rd, + &sdesc->rs_mctid, + 0, + &sdesc->rs_cbuf, + NowTimeQual, + 0, + (ScanKey) NULL); + } + + if (!ItemPointerIsValid(&sdesc->rs_mntid)) { + sdesc->rs_ntup = NULL; + } else { + sdesc->rs_ntup = (HeapTuple) + heapgettup(sdesc->rs_rd, + &sdesc->rs_mntid, + 0, + &sdesc->rs_nbuf, + NowTimeQual, + 0, + (ScanKey) NULL); + } +} diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c new file mode 100644 index 00000000000..457e1174a30 --- /dev/null +++ b/src/backend/access/heap/hio.c @@ -0,0 +1,195 @@ +/*------------------------------------------------------------------------- + * + * hio.c-- + * POSTGRES heap access method input/output code. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Id: hio.c,v 1.1.1.1 1996/07/09 06:21:11 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <string.h> + +#include "c.h" + +#include "access/heapam.h" +#include "access/hio.h" +#include "access/htup.h" + +#include "storage/block.h" +#include "storage/buf.h" +#include "storage/bufmgr.h" +#include "storage/bufpage.h" +#include "storage/itemid.h" +#include "storage/itemptr.h" +#include "storage/off.h" + +#include "utils/memutils.h" +#include "utils/elog.h" +#include "utils/rel.h" + +/* + * amputunique - place tuple at tid + * Currently on errors, calls elog. Perhaps should return -1? + * Possible errors include the addition of a tuple to the page + * between the time the linep is chosen and the page is L_UP'd. + * + * This should be coordinated with the B-tree code. + * Probably needs to have an amdelunique to allow for + * internal index records to be deleted and reordered as needed. + * For the heap AM, this should never be needed. + */ +void +RelationPutHeapTuple(Relation relation, + BlockNumber blockIndex, + HeapTuple tuple) +{ + Buffer buffer; + Page pageHeader; + BlockNumber numberOfBlocks; + OffsetNumber offnum; + unsigned int len; + ItemId itemId; + Item item; + + /* ---------------- + * increment access statistics + * ---------------- + */ + IncrHeapAccessStat(local_RelationPutHeapTuple); + IncrHeapAccessStat(global_RelationPutHeapTuple); + + Assert(RelationIsValid(relation)); + Assert(HeapTupleIsValid(tuple)); + + numberOfBlocks = RelationGetNumberOfBlocks(relation); + Assert(blockIndex < numberOfBlocks); + + buffer = ReadBuffer(relation, blockIndex); +#ifndef NO_BUFFERISVALID + if (!BufferIsValid(buffer)) { + elog(WARN, "RelationPutHeapTuple: no buffer for %ld in %s", + blockIndex, &relation->rd_rel->relname); + } +#endif + + pageHeader = (Page)BufferGetPage(buffer); + len = (unsigned)DOUBLEALIGN(tuple->t_len); /* be conservative */ + Assert((int)len <= PageGetFreeSpace(pageHeader)); + + offnum = PageAddItem((Page)pageHeader, (Item)tuple, + tuple->t_len, InvalidOffsetNumber, LP_USED); + + itemId = PageGetItemId((Page)pageHeader, offnum); + item = PageGetItem((Page)pageHeader, itemId); + + ItemPointerSet(&((HeapTuple)item)->t_ctid, blockIndex, offnum); + + WriteBuffer(buffer); + /* return an accurate tuple */ + ItemPointerSet(&tuple->t_ctid, blockIndex, offnum); +} + +/* + * The heap_insert routines "know" that a buffer page is initialized to + * zero when a BlockExtend operation is performed. + */ + +#define PageIsNew(page) ((page)->pd_upper == 0) + +/* + * This routine is another in the series of attempts to reduce the number + * of I/O's and system calls executed in the various benchmarks. In + * particular, this routine is used to append data to the end of a relation + * file without excessive lseeks. This code should do no more than 2 semops + * in the ideal case. + * + * Eventually, we should cache the number of blocks in a relation somewhere. + * Until that time, this code will have to do an lseek to determine the number + * of blocks in a relation. + * + * This code should ideally do at most 4 semops, 1 lseek, and possibly 1 write + * to do an append; it's possible to eliminate 2 of the semops if we do direct + * buffer stuff (!); the lseek and the write can go if we get + * RelationGetNumberOfBlocks to be useful. + * + * NOTE: This code presumes that we have a write lock on the relation. + * + * Also note that this routine probably shouldn't have to exist, and does + * screw up the call graph rather badly, but we are wasting so much time and + * system resources being massively general that we are losing badly in our + * performance benchmarks. + */ +void +RelationPutHeapTupleAtEnd(Relation relation, HeapTuple tuple) +{ + Buffer buffer; + Page pageHeader; + BlockNumber lastblock; + OffsetNumber offnum; + unsigned int len; + ItemId itemId; + Item item; + + Assert(RelationIsValid(relation)); + Assert(HeapTupleIsValid(tuple)); + + /* + * XXX This does an lseek - VERY expensive - but at the moment it + * is the only way to accurately determine how many blocks are in + * a relation. A good optimization would be to get this to actually + * work properly. + */ + + lastblock = RelationGetNumberOfBlocks(relation); + + if (lastblock == 0) + { + buffer = ReadBuffer(relation, lastblock); + pageHeader = (Page)BufferGetPage(buffer); + if (PageIsNew((PageHeader) pageHeader)) + { + buffer = ReleaseAndReadBuffer(buffer, relation, P_NEW); + pageHeader = (Page)BufferGetPage(buffer); + PageInit(pageHeader, BufferGetPageSize(buffer), 0); + } + } + else + buffer = ReadBuffer(relation, lastblock - 1); + + pageHeader = (Page)BufferGetPage(buffer); + len = (unsigned)DOUBLEALIGN(tuple->t_len); /* be conservative */ + + /* + * Note that this is true if the above returned a bogus page, which + * it will do for a completely empty relation. + */ + + if (len > PageGetFreeSpace(pageHeader)) + { + buffer = ReleaseAndReadBuffer(buffer, relation, P_NEW); + pageHeader = (Page)BufferGetPage(buffer); + PageInit(pageHeader, BufferGetPageSize(buffer), 0); + + if (len > PageGetFreeSpace(pageHeader)) + elog(WARN, "Tuple is too big: size %d", len); + } + + offnum = PageAddItem((Page)pageHeader, (Item)tuple, + tuple->t_len, InvalidOffsetNumber, LP_USED); + + itemId = PageGetItemId((Page)pageHeader, offnum); + item = PageGetItem((Page)pageHeader, itemId); + + lastblock = BufferGetBlockNumber(buffer); + + ItemPointerSet(&((HeapTuple)item)->t_ctid, lastblock, offnum); + + /* return an accurate tuple */ + ItemPointerSet(&tuple->t_ctid, lastblock, offnum); + + WriteBuffer(buffer); +} diff --git a/src/backend/access/heap/stats.c b/src/backend/access/heap/stats.c new file mode 100644 index 00000000000..d41d01ac1ba --- /dev/null +++ b/src/backend/access/heap/stats.c @@ -0,0 +1,329 @@ +/*------------------------------------------------------------------------- + * + * stats.c-- + * heap access method debugging statistic collection routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/heap/Attic/stats.c,v 1.1.1.1 1996/07/09 06:21:11 scrappy Exp $ + * + * NOTES + * initam should be moved someplace else. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/heapam.h" + +#include "utils/memutils.h" +#include "utils/palloc.h" +#include "utils/elog.h" +#include "utils/mcxt.h" + +/* ---------------- + * InitHeapAccessStatistics + * ---------------- + */ +HeapAccessStatistics heap_access_stats = (HeapAccessStatistics) NULL; + +void +InitHeapAccessStatistics() +{ + MemoryContext oldContext; + HeapAccessStatistics stats; + + /* ---------------- + * make sure we don't initialize things twice + * ---------------- + */ + if (heap_access_stats != NULL) + return; + + /* ---------------- + * allocate statistics structure from the top memory context + * ---------------- + */ + oldContext = MemoryContextSwitchTo(TopMemoryContext); + + stats = (HeapAccessStatistics) + palloc(sizeof(HeapAccessStatisticsData)); + + /* ---------------- + * initialize fields to default values + * ---------------- + */ + stats->global_open = 0; + stats->global_openr = 0; + stats->global_close = 0; + stats->global_beginscan = 0; + stats->global_rescan = 0; + stats->global_endscan = 0; + stats->global_getnext = 0; + stats->global_fetch = 0; + stats->global_insert = 0; + stats->global_delete = 0; + stats->global_replace = 0; + stats->global_markpos = 0; + stats->global_restrpos = 0; + stats->global_BufferGetRelation = 0; + stats->global_RelationIdGetRelation = 0; + stats->global_RelationIdGetRelation_Buf = 0; + stats->global_getreldesc = 0; + stats->global_heapgettup = 0; + stats->global_RelationPutHeapTuple = 0; + stats->global_RelationPutLongHeapTuple = 0; + + stats->local_open = 0; + stats->local_openr = 0; + stats->local_close = 0; + stats->local_beginscan = 0; + stats->local_rescan = 0; + stats->local_endscan = 0; + stats->local_getnext = 0; + stats->local_fetch = 0; + stats->local_insert = 0; + stats->local_delete = 0; + stats->local_replace = 0; + stats->local_markpos = 0; + stats->local_restrpos = 0; + stats->local_BufferGetRelation = 0; + stats->local_RelationIdGetRelation = 0; + stats->local_RelationIdGetRelation_Buf = 0; + stats->local_getreldesc = 0; + stats->local_heapgettup = 0; + stats->local_RelationPutHeapTuple = 0; + stats->local_RelationPutLongHeapTuple = 0; + stats->local_RelationNameGetRelation = 0; + stats->global_RelationNameGetRelation = 0; + + /* ---------------- + * record init times + * ---------------- + */ + time(&stats->init_global_timestamp); + time(&stats->local_reset_timestamp); + time(&stats->last_request_timestamp); + + /* ---------------- + * return to old memory context + * ---------------- + */ + (void) MemoryContextSwitchTo(oldContext); + + heap_access_stats = stats; +} + +/* ---------------- + * ResetHeapAccessStatistics + * ---------------- + */ +void +ResetHeapAccessStatistics() +{ + HeapAccessStatistics stats; + + /* ---------------- + * do nothing if stats aren't initialized + * ---------------- + */ + if (heap_access_stats == NULL) + return; + + stats = heap_access_stats; + + /* ---------------- + * reset local counts + * ---------------- + */ + stats->local_open = 0; + stats->local_openr = 0; + stats->local_close = 0; + stats->local_beginscan = 0; + stats->local_rescan = 0; + stats->local_endscan = 0; + stats->local_getnext = 0; + stats->local_fetch = 0; + stats->local_insert = 0; + stats->local_delete = 0; + stats->local_replace = 0; + stats->local_markpos = 0; + stats->local_restrpos = 0; + stats->local_BufferGetRelation = 0; + stats->local_RelationIdGetRelation = 0; + stats->local_RelationIdGetRelation_Buf = 0; + stats->local_getreldesc = 0; + stats->local_heapgettup = 0; + stats->local_RelationPutHeapTuple = 0; + stats->local_RelationPutLongHeapTuple = 0; + + /* ---------------- + * reset local timestamps + * ---------------- + */ + time(&stats->local_reset_timestamp); + time(&stats->last_request_timestamp); +} + +/* ---------------- + * GetHeapAccessStatistics + * ---------------- + */ +HeapAccessStatistics GetHeapAccessStatistics() +{ + HeapAccessStatistics stats; + + /* ---------------- + * return nothing if stats aren't initialized + * ---------------- + */ + if (heap_access_stats == NULL) + return NULL; + + /* ---------------- + * record the current request time + * ---------------- + */ + time(&heap_access_stats->last_request_timestamp); + + /* ---------------- + * allocate a copy of the stats and return it to the caller. + * ---------------- + */ + stats = (HeapAccessStatistics) + palloc(sizeof(HeapAccessStatisticsData)); + + memmove(stats, + heap_access_stats, + sizeof(HeapAccessStatisticsData)); + + return stats; +} + +/* ---------------- + * PrintHeapAccessStatistics + * ---------------- + */ +void +PrintHeapAccessStatistics(HeapAccessStatistics stats) +{ + /* ---------------- + * return nothing if stats aren't valid + * ---------------- + */ + if (stats == NULL) + return; + + printf("======== heap am statistics ========\n"); + printf("init_global_timestamp: %s", + ctime(&(stats->init_global_timestamp))); + + printf("local_reset_timestamp: %s", + ctime(&(stats->local_reset_timestamp))); + + printf("last_request_timestamp: %s", + ctime(&(stats->last_request_timestamp))); + + printf("local/global_open: %6d/%6d\n", + stats->local_open, stats->global_open); + + printf("local/global_openr: %6d/%6d\n", + stats->local_openr, stats->global_openr); + + printf("local/global_close: %6d/%6d\n", + stats->local_close, stats->global_close); + + printf("local/global_beginscan: %6d/%6d\n", + stats->local_beginscan, stats->global_beginscan); + + printf("local/global_rescan: %6d/%6d\n", + stats->local_rescan, stats->global_rescan); + + printf("local/global_endscan: %6d/%6d\n", + stats->local_endscan, stats->global_endscan); + + printf("local/global_getnext: %6d/%6d\n", + stats->local_getnext, stats->global_getnext); + + printf("local/global_fetch: %6d/%6d\n", + stats->local_fetch, stats->global_fetch); + + printf("local/global_insert: %6d/%6d\n", + stats->local_insert, stats->global_insert); + + printf("local/global_delete: %6d/%6d\n", + stats->local_delete, stats->global_delete); + + printf("local/global_replace: %6d/%6d\n", + stats->local_replace, stats->global_replace); + + printf("local/global_markpos: %6d/%6d\n", + stats->local_markpos, stats->global_markpos); + + printf("local/global_restrpos: %6d/%6d\n", + stats->local_restrpos, stats->global_restrpos); + + printf("================\n"); + + printf("local/global_BufferGetRelation: %6d/%6d\n", + stats->local_BufferGetRelation, + stats->global_BufferGetRelation); + + printf("local/global_RelationIdGetRelation: %6d/%6d\n", + stats->local_RelationIdGetRelation, + stats->global_RelationIdGetRelation); + + printf("local/global_RelationIdGetRelation_Buf: %6d/%6d\n", + stats->local_RelationIdGetRelation_Buf, + stats->global_RelationIdGetRelation_Buf); + + printf("local/global_getreldesc: %6d/%6d\n", + stats->local_getreldesc, stats->global_getreldesc); + + printf("local/global_heapgettup: %6d/%6d\n", + stats->local_heapgettup, stats->global_heapgettup); + + printf("local/global_RelationPutHeapTuple: %6d/%6d\n", + stats->local_RelationPutHeapTuple, + stats->global_RelationPutHeapTuple); + + printf("local/global_RelationPutLongHeapTuple: %6d/%6d\n", + stats->local_RelationPutLongHeapTuple, + stats->global_RelationPutLongHeapTuple); + + printf("===================================\n"); + + printf("\n"); +} + +/* ---------------- + * PrintAndFreeHeapAccessStatistics + * ---------------- + */ +void +PrintAndFreeHeapAccessStatistics(HeapAccessStatistics stats) +{ + PrintHeapAccessStatistics(stats); + if (stats != NULL) + pfree(stats); +} + +/* ---------------------------------------------------------------- + * access method initialization + * ---------------------------------------------------------------- + */ +/* ---------------- + * initam should someday be moved someplace else. + * ---------------- + */ +void +initam() +{ + /* ---------------- + * initialize heap statistics. + * ---------------- + */ + InitHeapAccessStatistics(); +} diff --git a/src/backend/access/heapam.h b/src/backend/access/heapam.h new file mode 100644 index 00000000000..9938dbeea77 --- /dev/null +++ b/src/backend/access/heapam.h @@ -0,0 +1,149 @@ +/*------------------------------------------------------------------------- + * + * heapam.h-- + * POSTGRES heap access method definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: heapam.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef HEAPAM_H +#define HEAPAM_H + +#include <sys/types.h> + +#include "postgres.h" + +#include "access/attnum.h" +#include "access/htup.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "utils/tqual.h" +#include "access/tupdesc.h" +#include "storage/smgr.h" +#include "utils/rel.h" + +/* ---------------------------------------------------------------- + * heap access method statistics + * ---------------------------------------------------------------- + */ + +typedef struct HeapAccessStatisticsData { + time_t init_global_timestamp; /* time global statistics started */ + time_t local_reset_timestamp; /* last time local reset was done */ + time_t last_request_timestamp; /* last time stats were requested */ + + int global_open; + int global_openr; + int global_close; + int global_beginscan; + int global_rescan; + int global_endscan; + int global_getnext; + int global_fetch; + int global_insert; + int global_delete; + int global_replace; + int global_markpos; + int global_restrpos; + int global_BufferGetRelation; + int global_RelationIdGetRelation; + int global_RelationIdGetRelation_Buf; + int global_RelationNameGetRelation; + int global_getreldesc; + int global_heapgettup; + int global_RelationPutHeapTuple; + int global_RelationPutLongHeapTuple; + + int local_open; + int local_openr; + int local_close; + int local_beginscan; + int local_rescan; + int local_endscan; + int local_getnext; + int local_fetch; + int local_insert; + int local_delete; + int local_replace; + int local_markpos; + int local_restrpos; + int local_BufferGetRelation; + int local_RelationIdGetRelation; + int local_RelationIdGetRelation_Buf; + int local_RelationNameGetRelation; + int local_getreldesc; + int local_heapgettup; + int local_RelationPutHeapTuple; + int local_RelationPutLongHeapTuple; +} HeapAccessStatisticsData; + +typedef HeapAccessStatisticsData *HeapAccessStatistics; + +#define IncrHeapAccessStat(x) \ + (heap_access_stats == NULL ? 0 : (heap_access_stats->x)++) + +extern HeapAccessStatistics heap_access_stats; /* in stats.c */ + +/* ---------------- + * function prototypes for heap access method + * ---------------- + */ +/* heap_create, heap_creatr, and heap_destroy are declared in catalog/heap.h */ +#include "catalog/heap.h" + +/* heapam.c */ +extern void doinsert(Relation relation, HeapTuple tup); +extern void SetHeapAccessMethodImmediateInvalidation(bool on); + +extern Relation heap_open(Oid relationId); +extern Relation heap_openr(char *relationName); +extern void heap_close(Relation relation); +extern HeapScanDesc heap_beginscan(Relation relation, int atend, + TimeQual timeQual, unsigned nkeys, ScanKey key); +extern void heap_rescan(HeapScanDesc sdesc, bool scanFromEnd, ScanKey key); +extern void heap_endscan(HeapScanDesc sdesc); +extern HeapTuple heap_getnext(HeapScanDesc scandesc, int backw, Buffer *b); +extern HeapTuple heap_fetch(Relation relation, TimeQual timeQual, + ItemPointer tid, Buffer *b); +extern Oid heap_insert(Relation relation, HeapTuple tup); +extern void heap_delete(Relation relation, ItemPointer tid); +extern int heap_replace(Relation relation, ItemPointer otid, + HeapTuple tup); +extern void heap_markpos(HeapScanDesc sdesc); +extern void heap_restrpos(HeapScanDesc sdesc); + +/* in common/heaptuple.c */ +extern Size ComputeDataSize(TupleDesc tupleDesc, Datum value[], char nulls[]); +extern void DataFill(char *data, TupleDesc tupleDesc, + Datum value[], char nulls[], char *infomask, + bits8 bit[]); +extern int heap_attisnull(HeapTuple tup, int attnum); +extern int heap_sysattrlen(AttrNumber attno); +extern bool heap_sysattrbyval(AttrNumber attno); +extern char *heap_getsysattr(HeapTuple tup, Buffer b, int attnum); +extern char *fastgetattr(HeapTuple tup, unsigned attnum, + TupleDesc att, bool *isnull); +extern char *heap_getattr(HeapTuple tup, Buffer b, int attnum, + TupleDesc att, bool *isnull); +extern HeapTuple heap_copytuple(HeapTuple tuple); +extern void heap_deformtuple(HeapTuple tuple, TupleDesc tdesc, + Datum values[], char nulls[]); +extern HeapTuple heap_formtuple(TupleDesc tupleDescriptor, + Datum value[], char nulls[]); +extern HeapTuple heap_modifytuple(HeapTuple tuple, Buffer buffer, + Relation relation, Datum replValue[], char replNull[], char repl[]); +HeapTuple heap_addheader(uint32 natts, int structlen, char *structure); + +/* in common/heap/stats.c */ +extern void InitHeapAccessStatistics(void); +extern void ResetHeapAccessStatistics(void); +extern HeapAccessStatistics GetHeapAccessStatistics(void); +extern void PrintHeapAccessStatistics(HeapAccessStatistics stats); +extern void PrintAndFreeHeapAccessStatistics(HeapAccessStatistics stats); +extern void initam(void); + +#endif /* HEAPAM_H */ diff --git a/src/backend/access/hio.h b/src/backend/access/hio.h new file mode 100644 index 00000000000..4a699ffcd98 --- /dev/null +++ b/src/backend/access/hio.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * hio.h-- + * POSTGRES heap access method input/output definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: hio.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef HIO_H +#define HIO_H + +#include "c.h" + +#include "storage/block.h" +#include "access/htup.h" +#include "utils/rel.h" + +extern void RelationPutHeapTuple(Relation relation, BlockNumber blockIndex, + HeapTuple tuple); +extern void RelationPutHeapTupleAtEnd(Relation relation, HeapTuple tuple); + +#endif /* HIO_H */ diff --git a/src/backend/access/htup.h b/src/backend/access/htup.h new file mode 100644 index 00000000000..7cf1ecf1762 --- /dev/null +++ b/src/backend/access/htup.h @@ -0,0 +1,115 @@ +/*------------------------------------------------------------------------- + * + * htup.h-- + * POSTGRES heap tuple definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: htup.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef HTUP_H +#define HTUP_H + +#include "access/attnum.h" +#include "storage/bufpage.h" /* just to reduce levels of #include */ +#include "storage/itemptr.h" +#include "utils/nabstime.h" + +#define MinHeapTupleBitmapSize 32 /* 8 * 4 */ + +/* check these, they are likely to be more severely limited by t_hoff */ + +#define MaxHeapAttributeNumber 1600 /* 8 * 200 */ + +/* + * to avoid wasting space, the attributes should be layed out in such a + * way to reduce structure padding. + */ +typedef struct HeapTupleData { + + unsigned int t_len; /* length of entire tuple */ + + ItemPointerData t_ctid; /* current TID of this tuple */ + + ItemPointerData t_chain; /* replaced tuple TID */ + + Oid t_oid; /* OID of this tuple -- 4 bytes */ + + CommandId t_cmin; /* insert CID stamp -- 2 bytes each */ + CommandId t_cmax; /* delete CommandId stamp */ + + TransactionId t_xmin; /* insert XID stamp -- 4 bytes each */ + TransactionId t_xmax; /* delete XID stamp */ + + AbsoluteTime t_tmin; /* time stamps -- 4 bytes each */ + AbsoluteTime t_tmax; + + int16 t_natts; /* number of attributes */ + char t_vtype; /* not used - padding */ + + char t_infomask; /* whether tuple as null or variable + * length attributes + */ + + uint8 t_hoff; /* sizeof tuple header */ + + bits8 t_bits[MinHeapTupleBitmapSize / 8]; + /* bit map of domains */ + + /* MORE DATA FOLLOWS AT END OF STRUCT */ +} HeapTupleData; + +typedef HeapTupleData *HeapTuple; + + +#define SelfItemPointerAttributeNumber (-1) +#define ObjectIdAttributeNumber (-2) +#define MinTransactionIdAttributeNumber (-3) +#define MinCommandIdAttributeNumber (-4) +#define MaxTransactionIdAttributeNumber (-5) +#define MaxCommandIdAttributeNumber (-6) +#define ChainItemPointerAttributeNumber (-7) +#define AnchorItemPointerAttributeNumber (-8) +#define MinAbsoluteTimeAttributeNumber (-9) +#define MaxAbsoluteTimeAttributeNumber (-10) +#define VersionTypeAttributeNumber (-11) +#define FirstLowInvalidHeapAttributeNumber (-12) + + +/* ---------------- + * support macros + * ---------------- + */ +#define GETSTRUCT(TUP) (((char *)(TUP)) + ((HeapTuple)(TUP))->t_hoff) + + +/* + * BITMAPLEN(NATTS) - + * Computes minimum size of bitmap given number of domains. + */ +#define BITMAPLEN(NATTS) \ + ((((((int)(NATTS) - 1) >> 3) + 4 - (MinHeapTupleBitmapSize >> 3)) \ + & ~03) + (MinHeapTupleBitmapSize >> 3)) + +/* + * HeapTupleIsValid + * True iff the heap tuple is valid. + */ +#define HeapTupleIsValid(tuple) PointerIsValid(tuple) + +/* + * information stored in t_infomask: + */ +#define HEAP_HASNULL 0x01 /* has null attribute(s) */ +#define HEAP_HASVARLENA 0x02 /* has variable length attribute(s) */ + +#define HeapTupleNoNulls(tuple) \ + (!(((HeapTuple) (tuple))->t_infomask & HEAP_HASNULL)) + +#define HeapTupleAllFixed(tuple) \ + (!(((HeapTuple) (tuple))->t_infomask & HEAP_HASVARLENA)) + +#endif /* HTUP_H */ diff --git a/src/backend/access/ibit.h b/src/backend/access/ibit.h new file mode 100644 index 00000000000..990c23ab4dd --- /dev/null +++ b/src/backend/access/ibit.h @@ -0,0 +1,34 @@ +/*------------------------------------------------------------------------- + * + * ibit.h-- + * POSTGRES index valid attribute bit map definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: ibit.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef IBIT_H +#define IBIT_H + +#include "c.h" +#include "utils/memutils.h" + +typedef struct IndexAttributeBitMapData { + char bits[(MaxIndexAttributeNumber + MaxBitsPerByte - 1) + / MaxBitsPerByte]; +} IndexAttributeBitMapData; + +typedef IndexAttributeBitMapData *IndexAttributeBitMap; + +#define IndexAttributeBitMapSize sizeof(IndexAttributeBitMapData) + +/* + * IndexAttributeBitMapIsValid -- + * True iff attribute bit map is valid. + */ +#define IndexAttributeBitMapIsValid(bits) PointerIsValid(bits) + +#endif /* IBIT_H */ diff --git a/src/backend/access/index/Makefile.inc b/src/backend/access/index/Makefile.inc new file mode 100644 index 00000000000..0bc58830c8f --- /dev/null +++ b/src/backend/access/index/Makefile.inc @@ -0,0 +1,14 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for access/index +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/access/index/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:11 scrappy Exp $ +# +#------------------------------------------------------------------------- + +SUBSRCS+= genam.c indexam.c istrat.c diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c new file mode 100644 index 00000000000..3d02ba57009 --- /dev/null +++ b/src/backend/access/index/genam.c @@ -0,0 +1,275 @@ +/*------------------------------------------------------------------------- + * + * genam.c-- + * general index access method routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/index/genam.c,v 1.1.1.1 1996/07/09 06:21:11 scrappy Exp $ + * + * NOTES + * many of the old access method routines have been turned into + * macros and moved to genam.h -cim 4/30/91 + * + *------------------------------------------------------------------------- + */ +/* + * OLD COMMENTS + * Scans are implemented as follows: + * + * `0' represents an invalid item pointer. + * `-' represents an unknown item pointer. + * `X' represents a known item pointers. + * `+' represents known or invalid item pointers. + * `*' represents any item pointers. + * + * State is represented by a triple of these symbols in the order of + * previous, current, next. Note that the case of reverse scans works + * identically. + * + * State Result + * (1) + + - + 0 0 (if the next item pointer is invalid) + * (2) + X - (otherwise) + * (3) * 0 0 * 0 0 (no change) + * (4) + X 0 X 0 0 (shift) + * (5) * + X + X - (shift, add unknown) + * + * All other states cannot occur. + * + * Note: + *It would be possible to cache the status of the previous and + * next item pointer using the flags. + * ---------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/attnum.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "access/itup.h" +#include "access/relscan.h" +#include "access/sdir.h" +#include "access/skey.h" + +#include "storage/bufmgr.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" + +#include "catalog/catname.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_index.h" +#include "catalog/pg_proc.h" + +#include "catalog/index.h" + +/* ---------------------------------------------------------------- + * general access method routines + * + * All indexed access methods use an identical scan structure. + * We don't know how the various AMs do locking, however, so we don't + * do anything about that here. + * + * The intent is that an AM implementor will define a front-end routine + * that calls this one, to fill in the scan, and then does whatever kind + * of locking he wants. + * ---------------------------------------------------------------- + */ + +/* ---------------- + * RelationGetIndexScan -- Create and fill an IndexScanDesc. + * + * This routine creates an index scan structure and sets its contents + * up correctly. This routine calls AMrescan to set up the scan with + * the passed key. + * + * Parameters: + * relation -- index relation for scan. + * scanFromEnd -- if true, begin scan at one of the index's + * endpoints. + * numberOfKeys -- count of scan keys (more than one won't + * necessarily do anything useful, yet). + * key -- the ScanKey for the starting position of the scan. + * + * Returns: + * An initialized IndexScanDesc. + * + * Side Effects: + * Bumps the ref count on the relation to keep it in the cache. + * + * ---------------- + */ +IndexScanDesc +RelationGetIndexScan(Relation relation, + bool scanFromEnd, + uint16 numberOfKeys, + ScanKey key) +{ + IndexScanDesc scan; + + if (! RelationIsValid(relation)) + elog(WARN, "RelationGetIndexScan: relation invalid"); + + scan = (IndexScanDesc) palloc(sizeof(IndexScanDescData)); + + scan->relation = relation; + scan->opaque = NULL; + scan->numberOfKeys = numberOfKeys; + + ItemPointerSetInvalid(&scan->previousItemData); + ItemPointerSetInvalid(&scan->currentItemData); + ItemPointerSetInvalid(&scan->nextItemData); + ItemPointerSetInvalid(&scan->previousMarkData); + ItemPointerSetInvalid(&scan->currentMarkData); + ItemPointerSetInvalid(&scan->nextMarkData); + + if (numberOfKeys > 0) { + scan->keyData = (ScanKey) palloc(sizeof(ScanKeyData) * numberOfKeys); + } else { + scan->keyData = NULL; + } + + index_rescan(scan, scanFromEnd, key); + + return (scan); +} + +/* ---------------- + * IndexScanRestart -- Restart an index scan. + * + * This routine isn't used by any existing access method. It's + * appropriate if relation level locks are what you want. + * + * Returns: + * None. + * + * Side Effects: + * None. + * ---------------- + */ +void +IndexScanRestart(IndexScanDesc scan, + bool scanFromEnd, + ScanKey key) +{ + if (! IndexScanIsValid(scan)) + elog(WARN, "IndexScanRestart: invalid scan"); + + ItemPointerSetInvalid(&scan->previousItemData); + ItemPointerSetInvalid(&scan->currentItemData); + ItemPointerSetInvalid(&scan->nextItemData); + + if (RelationGetNumberOfBlocks(scan->relation) == 0) + scan->flags = ScanUnmarked; + else if (scanFromEnd) + scan->flags = ScanUnmarked | ScanUncheckedPrevious; + else + scan->flags = ScanUnmarked | ScanUncheckedNext; + + scan->scanFromEnd = (bool) scanFromEnd; + + if (scan->numberOfKeys > 0) + memmove(scan->keyData, + key, + scan->numberOfKeys * sizeof(ScanKeyData)); +} + +/* ---------------- + * IndexScanEnd -- End and index scan. + * + * This routine is not used by any existing access method, but is + * suitable for use if you don't want to do sophisticated locking. + * + * Returns: + * None. + * + * Side Effects: + * None. + * ---------------- + */ +void +IndexScanEnd(IndexScanDesc scan) +{ + if (! IndexScanIsValid(scan)) + elog(WARN, "IndexScanEnd: invalid scan"); + + pfree(scan); +} + +/* ---------------- + * IndexScanMarkPosition -- Mark current position in a scan. + * + * This routine isn't used by any existing access method, but is the + * one that AM implementors should use, if they don't want to do any + * special locking. If relation-level locking is sufficient, this is + * the routine for you. + * + * Returns: + * None. + * + * Side Effects: + * None. + * ---------------- + */ +void +IndexScanMarkPosition(IndexScanDesc scan) +{ + RetrieveIndexResult result; + + if (scan->flags & ScanUncheckedPrevious) { + result = + index_getnext(scan, BackwardScanDirection); + + if (result != NULL) { + scan->previousItemData = result->index_iptr; + } else { + ItemPointerSetInvalid(&scan->previousItemData); + } + + } else if (scan->flags & ScanUncheckedNext) { + result = (RetrieveIndexResult) + index_getnext(scan, ForwardScanDirection); + + if (result != NULL) { + scan->nextItemData = result->index_iptr; + } else { + ItemPointerSetInvalid(&scan->nextItemData); + } + } + + scan->previousMarkData = scan->previousItemData; + scan->currentMarkData = scan->currentItemData; + scan->nextMarkData = scan->nextItemData; + + scan->flags = 0x0; /* XXX should have a symbolic name */ +} + +/* ---------------- + * IndexScanRestorePosition -- Restore position on a marked scan. + * + * This routine isn't used by any existing access method, but is the + * one that AM implementors should use if they don't want to do any + * special locking. If relation-level locking is sufficient, then + * this is the one you want. + * + * Returns: + * None. + * + * Side Effects: + * None. + * ---------------- + */ +void +IndexScanRestorePosition(IndexScanDesc scan) +{ + if (scan->flags & ScanUnmarked) + elog(WARN, "IndexScanRestorePosition: no mark to restore"); + + scan->previousItemData = scan->previousMarkData; + scan->currentItemData = scan->currentMarkData; + scan->nextItemData = scan->nextMarkData; + + scan->flags = 0x0; /* XXX should have a symbolic name */ +} diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c new file mode 100644 index 00000000000..bffe3a41f3a --- /dev/null +++ b/src/backend/access/index/indexam.c @@ -0,0 +1,411 @@ +/*------------------------------------------------------------------------- + * + * indexam.c-- + * general index access method routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/index/indexam.c,v 1.1.1.1 1996/07/09 06:21:11 scrappy Exp $ + * + * INTERFACE ROUTINES + * index_open - open an index relation by relationId + * index_openr - open a index relation by name + * index_close - close a index relation + * index_beginscan - start a scan of an index + * index_rescan - restart a scan of an index + * index_endscan - end a scan + * index_insert - insert an index tuple into a relation + * index_delete - delete an item from an index relation + * index_markpos - mark a scan position + * index_restrpos - restore a scan position + * index_getnext - get the next tuple from a scan + * ** index_fetch - retrieve tuple with tid + * ** index_replace - replace a tuple + * ** index_getattr - get an attribute from an index tuple + * index_getprocid - get a support procedure id from the rel tuple + * + * IndexScanIsValid - check index scan + * + * NOTES + * This file contains the index_ routines which used + * to be a scattered collection of stuff in access/genam. + * + * The ** routines: index_fetch, index_replace, and index_getattr + * have not yet been implemented. They may not be needed. + * + * old comments + * Scans are implemented as follows: + * + * `0' represents an invalid item pointer. + * `-' represents an unknown item pointer. + * `X' represents a known item pointers. + * `+' represents known or invalid item pointers. + * `*' represents any item pointers. + * + * State is represented by a triple of these symbols in the order of + * previous, current, next. Note that the case of reverse scans works + * identically. + * + * State Result + * (1) + + - + 0 0 (if the next item pointer is invalid) + * (2) + X - (otherwise) + * (3) * 0 0 * 0 0 (no change) + * (4) + X 0 X 0 0 (shift) + * (5) * + X + X - (shift, add unknown) + * + * All other states cannot occur. + * + * Note: It would be possible to cache the status of the previous and + * next item pointer using the flags. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/attnum.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "access/itup.h" +#include "access/relscan.h" +#include "access/sdir.h" +#include "access/skey.h" +#include "access/funcindex.h" + +#include "storage/lmgr.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/relcache.h" + +#include "catalog/catname.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_index.h" +#include "catalog/pg_proc.h" + +#include "catalog/index.h" + +#include "fmgr.h" + +/* ---------------- + * undefine macros we aren't going to use that would otherwise + * get in our way.. delete is defined in c.h and the am's are + * defined in heapam.h + * ---------------- + */ +#undef delete +#undef aminsert +#undef amdelete +#undef ambeginscan +#undef amrescan +#undef amendscan +#undef ammarkpos +#undef amrestrpos +#undef amgettuple + +/* ---------------------------------------------------------------- + * macros used in index_ routines + * ---------------------------------------------------------------- + */ +#define RELATION_CHECKS \ +Assert(RelationIsValid(relation)); \ + Assert(PointerIsValid(relation->rd_am)) + +#define SCAN_CHECKS \ + Assert(IndexScanIsValid(scan)); \ + Assert(RelationIsValid(scan->relation)); \ + Assert(PointerIsValid(scan->relation->rd_am)) + +#define GET_REL_PROCEDURE(x,y) \ + CppConcat(procedure = relation->rd_am->,y); \ + if (! RegProcedureIsValid(procedure)) \ + elog(WARN, "index_%s: invalid %s regproc", \ + CppAsString(x), CppAsString(y)) + +#define GET_SCAN_PROCEDURE(x,y) \ + CppConcat(procedure = scan->relation->rd_am->,y); \ + if (! RegProcedureIsValid(procedure)) \ + elog(WARN, "index_%s: invalid %s regproc", \ + CppAsString(x), CppAsString(y)) + + +/* ---------------------------------------------------------------- + * index_ interface functions + * ---------------------------------------------------------------- + */ +/* ---------------- + * index_open - open an index relation by relationId + * + * presently the relcache routines do all the work we need + * to open/close index relations. + * ---------------- + */ +Relation +index_open(Oid relationId) +{ + return RelationIdGetRelation(relationId); +} + +/* ---------------- + * index_openr - open a index relation by name + * + * presently the relcache routines do all the work we need + * to open/close index relations. + * ---------------- + */ +Relation +index_openr(char *relationName) +{ + return RelationNameGetRelation(relationName); +} + +/* ---------------- + * index_close - close a index relation + * + * presently the relcache routines do all the work we need + * to open/close index relations. + * ---------------- + */ +void +index_close(Relation relation) +{ + (void) RelationClose(relation); +} + +/* ---------------- + * index_insert - insert an index tuple into a relation + * ---------------- + */ +InsertIndexResult +index_insert(Relation relation, + IndexTuple indexTuple) +{ + RegProcedure procedure; + InsertIndexResult specificResult; + + RELATION_CHECKS; + GET_REL_PROCEDURE(insert,aminsert); + + /* ---------------- + * have the am's insert proc do all the work. + * ---------------- + */ + specificResult = (InsertIndexResult) + fmgr(procedure, relation, indexTuple, NULL); + + /* ---------------- + * the insert proc is supposed to return a "specific result" and + * this routine has to return a "general result" so after we get + * something back from the insert proc, we allocate a + * "general result" and copy some crap between the two. + * + * As far as I'm concerned all this result shit is needlessly c + * omplicated and should be eliminated. -cim 1/19/91 + * + * mao concurs. regardless of how we feel here, however, it is + * important to free memory we don't intend to return to anyone. + * 2/28/91 + * + * this "general result" crap is now gone. -ay 3/6/95 + * ---------------- + */ + + return (specificResult); +} + +/* ---------------- + * index_delete - delete an item from an index relation + * ---------------- + */ +void +index_delete(Relation relation, ItemPointer indexItem) +{ + RegProcedure procedure; + + RELATION_CHECKS; + GET_REL_PROCEDURE(delete,amdelete); + + (void) fmgr(procedure, relation, indexItem); +} + +/* ---------------- + * index_beginscan - start a scan of an index + * ---------------- + */ +IndexScanDesc +index_beginscan(Relation relation, + bool scanFromEnd, + uint16 numberOfKeys, + ScanKey key) +{ + IndexScanDesc scandesc; + RegProcedure procedure; + + RELATION_CHECKS; + GET_REL_PROCEDURE(beginscan,ambeginscan); + + RelationSetRIntentLock(relation); + + scandesc = (IndexScanDesc) + fmgr(procedure, relation, scanFromEnd, numberOfKeys, key); + + return scandesc; +} + +/* ---------------- + * index_rescan - restart a scan of an index + * ---------------- + */ +void +index_rescan(IndexScanDesc scan, bool scanFromEnd, ScanKey key) +{ + RegProcedure procedure; + + SCAN_CHECKS; + GET_SCAN_PROCEDURE(rescan,amrescan); + + (void) fmgr(procedure, scan, scanFromEnd, key); +} + +/* ---------------- + * index_endscan - end a scan + * ---------------- + */ +void +index_endscan(IndexScanDesc scan) +{ + RegProcedure procedure; + + SCAN_CHECKS; + GET_SCAN_PROCEDURE(endscan,amendscan); + + (void) fmgr(procedure, scan); + + RelationUnsetRIntentLock(scan->relation); +} + +/* ---------------- + * index_markpos - mark a scan position + * ---------------- + */ +void +index_markpos(IndexScanDesc scan) +{ + RegProcedure procedure; + + SCAN_CHECKS; + GET_SCAN_PROCEDURE(markpos,ammarkpos); + + (void) fmgr(procedure, scan); +} + +/* ---------------- + * index_restrpos - restore a scan position + * ---------------- + */ +void +index_restrpos(IndexScanDesc scan) +{ + RegProcedure procedure; + + SCAN_CHECKS; + GET_SCAN_PROCEDURE(restrpos,amrestrpos); + + (void) fmgr(procedure, scan); +} + +/* ---------------- + * index_getnext - get the next tuple from a scan + * + * A RetrieveIndexResult is a index tuple/heap tuple pair + * ---------------- + */ +RetrieveIndexResult +index_getnext(IndexScanDesc scan, + ScanDirection direction) +{ + RegProcedure procedure; + RetrieveIndexResult result; + + SCAN_CHECKS; + GET_SCAN_PROCEDURE(getnext,amgettuple); + + /* ---------------- + * have the am's gettuple proc do all the work. + * ---------------- + */ + result = (RetrieveIndexResult) + fmgr(procedure, scan, direction); + + return result; +} + +/* ---------------- + * index_getprocid + * + * Some indexed access methods may require support routines that are + * not in the operator class/operator model imposed by pg_am. These + * access methods may store the OIDs of registered procedures they + * need in pg_amproc. These registered procedure OIDs are ordered in + * a way that makes sense to the access method, and used only by the + * access method. The general index code doesn't know anything about + * the routines involved; it just builds an ordered list of them for + * each attribute on which an index is defined. + * + * This routine returns the requested procedure OID for a particular + * indexed attribute. + * ---------------- + */ +RegProcedure +index_getprocid(Relation irel, + AttrNumber attnum, + uint16 procnum) +{ + RegProcedure *loc; + int natts; + + natts = irel->rd_rel->relnatts; + + loc = irel->rd_support; + + Assert(loc != NULL); + + return (loc[(natts * (procnum - 1)) + (attnum - 1)]); +} + +Datum +GetIndexValue(HeapTuple tuple, + TupleDesc hTupDesc, + int attOff, + AttrNumber attrNums[], + FuncIndexInfo *fInfo, + bool *attNull, + Buffer buffer) +{ + Datum returnVal; + bool isNull; + + if (PointerIsValid(fInfo) && FIgetProcOid(fInfo) != InvalidOid) { + int i; + Datum *attData = (Datum *)palloc(FIgetnArgs(fInfo)*sizeof(Datum)); + + for (i = 0; i < FIgetnArgs(fInfo); i++) { + attData[i] = (Datum) heap_getattr(tuple, + buffer, + attrNums[i], + hTupDesc, + attNull); + } + returnVal = (Datum)fmgr_array_args(FIgetProcOid(fInfo), + FIgetnArgs(fInfo), + (char **) attData, + &isNull); + pfree(attData); + *attNull = FALSE; + }else { + returnVal = (Datum) heap_getattr(tuple, buffer, attrNums[attOff], + hTupDesc, attNull); + } + return returnVal; +} diff --git a/src/backend/access/index/istrat.c b/src/backend/access/index/istrat.c new file mode 100644 index 00000000000..602d2bd9e94 --- /dev/null +++ b/src/backend/access/index/istrat.c @@ -0,0 +1,679 @@ +/*------------------------------------------------------------------------- + * + * istrat.c-- + * index scan strategy manipulation code and index strategy manipulation + * operator code. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/index/Attic/istrat.c,v 1.1.1.1 1996/07/09 06:21:11 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/attnum.h" +#include "access/heapam.h" +#include "access/istrat.h" +#include "access/itup.h" /* for MaxIndexAttributeNumber */ +#include "access/skey.h" +#include "utils/tqual.h" /* for NowTimeQual */ + +#include "fmgr.h" +#include "utils/elog.h" +#include "utils/rel.h" + +#include "catalog/catname.h" +#include "catalog/pg_amop.h" +#include "catalog/pg_amproc.h" +#include "catalog/pg_index.h" +#include "catalog/pg_proc.h" + +/* ---------------------------------------------------------------- + * misc strategy support routines + * ---------------------------------------------------------------- + */ + +/* + * StrategyNumberIsValid + * StrategyNumberIsInBounds + * StrategyMapIsValid + * StrategyTransformMapIsValid + * IndexStrategyIsValid + * + * ... are now macros in istrat.h -cim 4/27/91 + */ + +/* + * StrategyMapGetScanKeyEntry -- + * Returns a scan key entry of a index strategy mapping member. + * + * Note: + * Assumes that the index strategy mapping is valid. + * Assumes that the index strategy number is valid. + * Bounds checking should be done outside this routine. + */ +ScanKey +StrategyMapGetScanKeyEntry(StrategyMap map, + StrategyNumber strategyNumber) +{ + Assert(StrategyMapIsValid(map)); + Assert(StrategyNumberIsValid(strategyNumber)); + return (&map->entry[strategyNumber - 1]); +} + +/* + * IndexStrategyGetStrategyMap -- + * Returns an index strategy mapping of an index strategy. + * + * Note: + * Assumes that the index strategy is valid. + * Assumes that the number of index strategies is valid. + * Bounds checking should be done outside this routine. + */ +StrategyMap +IndexStrategyGetStrategyMap(IndexStrategy indexStrategy, + StrategyNumber maxStrategyNum, + AttrNumber attrNum) +{ + Assert(IndexStrategyIsValid(indexStrategy)); + Assert(StrategyNumberIsValid(maxStrategyNum)); + Assert(AttributeNumberIsValid(attrNum)); + + maxStrategyNum = AMStrategies(maxStrategyNum); /* XXX */ + return + &indexStrategy->strategyMapData[maxStrategyNum * (attrNum - 1)]; +} + +/* + * AttributeNumberGetIndexStrategySize -- + * Computes the size of an index strategy. + */ +Size +AttributeNumberGetIndexStrategySize(AttrNumber maxAttributeNumber, + StrategyNumber maxStrategyNumber) +{ + maxStrategyNumber = AMStrategies(maxStrategyNumber); /* XXX */ + return + maxAttributeNumber * maxStrategyNumber * sizeof (ScanKeyData); +} + +/* + * StrategyTransformMapIsValid is now a macro in istrat.h -cim 4/27/91 + */ + +/* ---------------- + * StrategyOperatorIsValid + * ---------------- + */ +bool +StrategyOperatorIsValid(StrategyOperator operator, + StrategyNumber maxStrategy) +{ + return (bool) + (PointerIsValid(operator) && + StrategyNumberIsInBounds(operator->strategy, maxStrategy) && + !(operator->flags & ~(SK_NEGATE | SK_COMMUTE))); +} + +/* ---------------- + * StrategyTermIsValid + * ---------------- + */ +bool +StrategyTermIsValid(StrategyTerm term, + StrategyNumber maxStrategy) +{ + Index index; + + if (! PointerIsValid(term) || term->degree == 0) + return false; + + for (index = 0; index < term->degree; index += 1) { + if (! StrategyOperatorIsValid(&term->operatorData[index], + maxStrategy)) { + + return false; + } + } + + return true; +} + +/* ---------------- + * StrategyExpressionIsValid + * ---------------- + */ +bool +StrategyExpressionIsValid(StrategyExpression expression, + StrategyNumber maxStrategy) +{ + StrategyTerm *termP; + + if (!PointerIsValid(expression)) + return true; + + if (!StrategyTermIsValid(expression->term[0], maxStrategy)) + return false; + + termP = &expression->term[1]; + while (StrategyTermIsValid(*termP, maxStrategy)) + termP += 1; + + return (bool) + (! PointerIsValid(*termP)); +} + +/* ---------------- + * StrategyEvaluationIsValid + * ---------------- + */ +bool +StrategyEvaluationIsValid(StrategyEvaluation evaluation) +{ + Index index; + + if (! PointerIsValid(evaluation) || + ! StrategyNumberIsValid(evaluation->maxStrategy) || + ! StrategyTransformMapIsValid(evaluation->negateTransform) || + ! StrategyTransformMapIsValid(evaluation->commuteTransform) || + ! StrategyTransformMapIsValid(evaluation->negateCommuteTransform)) { + + return false; + } + + for (index = 0; index < evaluation->maxStrategy; index += 1) { + if (! StrategyExpressionIsValid(evaluation->expression[index], + evaluation->maxStrategy)) { + + return false; + } + } + return true; +} + +/* ---------------- + * StrategyTermEvaluate + * ---------------- + */ +static bool +StrategyTermEvaluate(StrategyTerm term, + StrategyMap map, + Datum left, + Datum right) +{ + Index index; + long tmpres; + bool result; + StrategyOperator operator; + ScanKey entry; + + for (index = 0, operator = &term->operatorData[0]; + index < term->degree; index += 1, operator += 1) { + + entry = &map->entry[operator->strategy - 1]; + + Assert(RegProcedureIsValid(entry->sk_procedure)); + + switch (operator->flags ^ entry->sk_flags) { + case 0x0: + tmpres = (long) FMGR_PTR2(entry->sk_func, entry->sk_procedure, + left, right); + break; + + case SK_NEGATE: + tmpres = (long) !FMGR_PTR2(entry->sk_func, entry->sk_procedure, + left, right); + break; + + case SK_COMMUTE: + tmpres = (long) FMGR_PTR2(entry->sk_func, entry->sk_procedure, + right, left); + break; + + case SK_NEGATE | SK_COMMUTE: + tmpres = (long) !FMGR_PTR2(entry->sk_func, entry->sk_procedure, + right, left); + break; + + default: + elog(FATAL, "StrategyTermEvaluate: impossible case %d", + operator->flags ^ entry->sk_flags); + } + + result = (bool) tmpres; + if (!result) + return result; + } + + return result; +} + + +/* ---------------- + * RelationGetStrategy + * ---------------- + */ +StrategyNumber +RelationGetStrategy(Relation relation, + AttrNumber attributeNumber, + StrategyEvaluation evaluation, + RegProcedure procedure) +{ + StrategyNumber strategy; + StrategyMap strategyMap; + ScanKey entry; + Index index; + int numattrs; + + Assert(RelationIsValid(relation)); + numattrs = RelationGetNumberOfAttributes(relation); + + Assert(relation->rd_rel->relkind == RELKIND_INDEX); /* XXX use accessor */ + Assert(AttributeNumberIsValid(attributeNumber)); + Assert( (attributeNumber >= 1) && (attributeNumber < 1 + numattrs)); + + Assert(StrategyEvaluationIsValid(evaluation)); + Assert(RegProcedureIsValid(procedure)); + + strategyMap = + IndexStrategyGetStrategyMap(RelationGetIndexStrategy(relation), + evaluation->maxStrategy, + attributeNumber); + + /* get a strategy number for the procedure ignoring flags for now */ + for (index = 0; index < evaluation->maxStrategy; index += 1) { + if (strategyMap->entry[index].sk_procedure == procedure) { + break; + } + } + + if (index == evaluation->maxStrategy) + return InvalidStrategy; + + strategy = 1 + index; + entry = StrategyMapGetScanKeyEntry(strategyMap, strategy); + + Assert(!(entry->sk_flags & ~(SK_NEGATE | SK_COMMUTE))); + + switch (entry->sk_flags & (SK_NEGATE | SK_COMMUTE)) { + case 0x0: + return strategy; + + case SK_NEGATE: + strategy = evaluation->negateTransform->strategy[strategy - 1]; + break; + + case SK_COMMUTE: + strategy = evaluation->commuteTransform->strategy[strategy - 1]; + break; + + case SK_NEGATE | SK_COMMUTE: + strategy = evaluation->negateCommuteTransform->strategy[strategy - 1]; + break; + + default: + elog(FATAL, "RelationGetStrategy: impossible case %d", entry->sk_flags); + } + + + if (! StrategyNumberIsInBounds(strategy, evaluation->maxStrategy)) { + if (! StrategyNumberIsValid(strategy)) { + elog(WARN, "RelationGetStrategy: corrupted evaluation"); + } + } + + return strategy; +} + +/* ---------------- + * RelationInvokeStrategy + * ---------------- + */ +bool /* XXX someday, this may return Datum */ +RelationInvokeStrategy(Relation relation, + StrategyEvaluation evaluation, + AttrNumber attributeNumber, + StrategyNumber strategy, + Datum left, + Datum right) +{ + StrategyNumber newStrategy; + StrategyMap strategyMap; + ScanKey entry; + StrategyTermData termData; + int numattrs; + + Assert(RelationIsValid(relation)); + Assert(relation->rd_rel->relkind == RELKIND_INDEX); /* XXX use accessor */ + numattrs = RelationGetNumberOfAttributes(relation); + + Assert(StrategyEvaluationIsValid(evaluation)); + Assert(AttributeNumberIsValid(attributeNumber)); + Assert( (attributeNumber >= 1) && (attributeNumber < 1 + numattrs)); + + Assert(StrategyNumberIsInBounds(strategy, evaluation->maxStrategy)); + + termData.degree = 1; + + strategyMap = + IndexStrategyGetStrategyMap(RelationGetIndexStrategy(relation), + evaluation->maxStrategy, + attributeNumber); + + entry = StrategyMapGetScanKeyEntry(strategyMap, strategy); + + if (RegProcedureIsValid(entry->sk_procedure)) { + termData.operatorData[0].strategy = strategy; + termData.operatorData[0].flags = 0x0; + + return + StrategyTermEvaluate(&termData, strategyMap, left, right); + } + + + newStrategy = evaluation->negateTransform->strategy[strategy - 1]; + if (newStrategy != strategy && StrategyNumberIsValid(newStrategy)) { + + entry = StrategyMapGetScanKeyEntry(strategyMap, newStrategy); + + if (RegProcedureIsValid(entry->sk_procedure)) { + termData.operatorData[0].strategy = newStrategy; + termData.operatorData[0].flags = SK_NEGATE; + + return + StrategyTermEvaluate(&termData, strategyMap, left, right); + } + } + + newStrategy = evaluation->commuteTransform->strategy[strategy - 1]; + if (newStrategy != strategy && StrategyNumberIsValid(newStrategy)) { + + entry = StrategyMapGetScanKeyEntry(strategyMap, newStrategy); + + if (RegProcedureIsValid(entry->sk_procedure)) { + termData.operatorData[0].strategy = newStrategy; + termData.operatorData[0].flags = SK_COMMUTE; + + return + StrategyTermEvaluate(&termData, strategyMap, left, right); + } + } + + newStrategy = evaluation->negateCommuteTransform->strategy[strategy - 1]; + if (newStrategy != strategy && StrategyNumberIsValid(newStrategy)) { + + entry = StrategyMapGetScanKeyEntry(strategyMap, newStrategy); + + if (RegProcedureIsValid(entry->sk_procedure)) { + termData.operatorData[0].strategy = newStrategy; + termData.operatorData[0].flags = SK_NEGATE | SK_COMMUTE; + + return + StrategyTermEvaluate(&termData, strategyMap, left, right); + } + } + + if (PointerIsValid(evaluation->expression[strategy - 1])) { + StrategyTerm *termP; + + termP = &evaluation->expression[strategy - 1]->term[0]; + while (PointerIsValid(*termP)) { + Index index; + + for (index = 0; index < (*termP)->degree; index += 1) { + entry = StrategyMapGetScanKeyEntry(strategyMap, + (*termP)->operatorData[index].strategy); + + if (! RegProcedureIsValid(entry->sk_procedure)) { + break; + } + } + + if (index == (*termP)->degree) { + return + StrategyTermEvaluate(*termP, strategyMap, left, right); + } + + termP += 1; + } + } + + elog(WARN, "RelationInvokeStrategy: cannot evaluate strategy %d", + strategy); + + /* not reached, just to make compiler happy */ + return FALSE; + + +} + +/* ---------------- + * OperatorRelationFillScanKeyEntry + * ---------------- + */ +static void +OperatorRelationFillScanKeyEntry(Relation operatorRelation, + Oid operatorObjectId, + ScanKey entry) +{ + HeapScanDesc scan; + ScanKeyData scanKeyData; + HeapTuple tuple; + + ScanKeyEntryInitialize(&scanKeyData, 0, + ObjectIdAttributeNumber, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(operatorObjectId)); + + scan = heap_beginscan(operatorRelation, false, NowTimeQual, + 1, &scanKeyData); + + tuple = heap_getnext(scan, false, (Buffer *)NULL); + if (! HeapTupleIsValid(tuple)) { + elog(WARN, "OperatorObjectIdFillScanKeyEntry: unknown operator %lu", + (uint32) operatorObjectId); + } + + entry->sk_flags = 0; + entry->sk_procedure = + ((OperatorTupleForm) GETSTRUCT(tuple))->oprcode; + fmgr_info(entry->sk_procedure, &entry->sk_func, &entry->sk_nargs); + + if (! RegProcedureIsValid(entry->sk_procedure)) { + elog(WARN, + "OperatorObjectIdFillScanKeyEntry: no procedure for operator %lu", + (uint32) operatorObjectId); + } + + heap_endscan(scan); +} + + +/* + * IndexSupportInitialize -- + * Initializes an index strategy and associated support procedures. + */ +void +IndexSupportInitialize(IndexStrategy indexStrategy, + RegProcedure *indexSupport, + Oid indexObjectId, + Oid accessMethodObjectId, + StrategyNumber maxStrategyNumber, + StrategyNumber maxSupportNumber, + AttrNumber maxAttributeNumber) +{ + Relation relation; + Relation operatorRelation; + HeapScanDesc scan; + HeapTuple tuple; + ScanKeyData entry[2]; + StrategyMap map; + AttrNumber attributeNumber; + int attributeIndex; + Oid operatorClassObjectId[ MaxIndexAttributeNumber ]; + + maxStrategyNumber = AMStrategies(maxStrategyNumber); + + ScanKeyEntryInitialize(&entry[0], 0, Anum_pg_index_indexrelid, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(indexObjectId)); + + relation = heap_openr(IndexRelationName); + scan = heap_beginscan(relation, false, NowTimeQual, 1, entry); + tuple = heap_getnext(scan, 0, (Buffer *)NULL); + if (! HeapTupleIsValid(tuple)) + elog(WARN, "IndexSupportInitialize: corrupted catalogs"); + + /* + * XXX note that the following assumes the INDEX tuple is well formed and + * that the key[] and class[] are 0 terminated. + */ + for (attributeIndex=0; attributeIndex<maxAttributeNumber; attributeIndex++) + { + IndexTupleForm iform; + + iform = (IndexTupleForm) GETSTRUCT(tuple); + + if (!OidIsValid(iform->indkey[attributeIndex])) { + if (attributeIndex == 0) { + elog(WARN, "IndexSupportInitialize: no pg_index tuple"); + } + break; + } + + operatorClassObjectId[attributeIndex] + = iform->indclass[attributeIndex]; + } + + heap_endscan(scan); + heap_close(relation); + + /* if support routines exist for this access method, load them */ + if (maxSupportNumber > 0) { + + ScanKeyEntryInitialize(&entry[0], 0, Anum_pg_amproc_amid, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(accessMethodObjectId)); + + ScanKeyEntryInitialize(&entry[1], 0, Anum_pg_amproc_amopclaid, + ObjectIdEqualRegProcedure, 0); + +/* relation = heap_openr(Name_pg_amproc); */ + relation = heap_openr(AccessMethodProcedureRelationName); + + + for (attributeNumber = maxAttributeNumber; attributeNumber > 0; + attributeNumber--) { + + int16 support; + Form_pg_amproc form; + RegProcedure *loc; + + loc = &indexSupport[((attributeNumber - 1) * maxSupportNumber)]; + + for (support = maxSupportNumber; --support >= 0; ) { + loc[support] = InvalidOid; + } + + entry[1].sk_argument = + ObjectIdGetDatum(operatorClassObjectId[attributeNumber - 1]); + + scan = heap_beginscan(relation, false, NowTimeQual, 2, entry); + + while (tuple = heap_getnext(scan, 0, (Buffer *)NULL), + HeapTupleIsValid(tuple)) { + + form = (Form_pg_amproc) GETSTRUCT(tuple); + loc[(form->amprocnum - 1)] = form->amproc; + } + + heap_endscan(scan); + } + heap_close(relation); + } + + ScanKeyEntryInitialize(&entry[0], 0, + Anum_pg_amop_amopid, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(accessMethodObjectId)); + + ScanKeyEntryInitialize(&entry[1], 0, + Anum_pg_amop_amopclaid, + ObjectIdEqualRegProcedure, 0); + + relation = heap_openr(AccessMethodOperatorRelationName); + operatorRelation = heap_openr(OperatorRelationName); + + for (attributeNumber = maxAttributeNumber; attributeNumber > 0; + attributeNumber--) { + + StrategyNumber strategy; + + entry[1].sk_argument = + ObjectIdGetDatum(operatorClassObjectId[attributeNumber - 1]); + + map = IndexStrategyGetStrategyMap(indexStrategy, + maxStrategyNumber, + attributeNumber); + + for (strategy = 1; strategy <= maxStrategyNumber; strategy++) + ScanKeyEntrySetIllegal(StrategyMapGetScanKeyEntry(map, strategy)); + + scan = heap_beginscan(relation, false, NowTimeQual, 2, entry); + + while (tuple = heap_getnext(scan, 0, (Buffer *)NULL), + HeapTupleIsValid(tuple)) { + Form_pg_amop form; + + form = (Form_pg_amop) GETSTRUCT(tuple); + + OperatorRelationFillScanKeyEntry(operatorRelation, + form->amopopr, + StrategyMapGetScanKeyEntry(map, form->amopstrategy)); + } + + heap_endscan(scan); + } + + heap_close(operatorRelation); + heap_close(relation); +} + +/* ---------------- + * IndexStrategyDisplay + * ---------------- + */ +#ifdef ISTRATDEBUG +int +IndexStrategyDisplay(IndexStrategy indexStrategy, + StrategyNumber numberOfStrategies, + int numberOfAttributes) +{ + StrategyMap strategyMap; + AttrNumber attributeNumber; + StrategyNumber strategyNumber; + + for (attributeNumber = 1; attributeNumber <= numberOfAttributes; + attributeNumber += 1) { + + strategyMap = IndexStrategyGetStrategyMap(indexStrategy, + numberOfStrategies, + attributeNumber); + + for (strategyNumber = 1; + strategyNumber <= AMStrategies(numberOfStrategies); + strategyNumber += 1) { + + printf(":att %d\t:str %d\t:opr 0x%x(%d)\n", + attributeNumber, strategyNumber, + strategyMap->entry[strategyNumber - 1].sk_procedure, + strategyMap->entry[strategyNumber - 1].sk_procedure); + } + } +} +#endif /* defined(ISTRATDEBUG) */ + + diff --git a/src/backend/access/iqual.h b/src/backend/access/iqual.h new file mode 100644 index 00000000000..5fab98a15bd --- /dev/null +++ b/src/backend/access/iqual.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * iqual.h-- + * Index scan key qualification definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: iqual.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef IQUAL_H +#define IQUAL_H + +#include "c.h" + +#include "storage/itemid.h" +#include "utils/rel.h" +#include "access/skey.h" + +/* ---------------- + * index tuple qualification support + * ---------------- + */ + +extern int NIndexTupleProcessed; + +extern bool index_keytest(IndexTuple tuple, TupleDesc tupdesc, + int scanKeySize, ScanKey key); + +#endif /* IQUAL_H */ diff --git a/src/backend/access/istrat.h b/src/backend/access/istrat.h new file mode 100644 index 00000000000..201e70e6602 --- /dev/null +++ b/src/backend/access/istrat.h @@ -0,0 +1,80 @@ +/*------------------------------------------------------------------------- + * + * istrat.h-- + * POSTGRES index strategy definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: istrat.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef ISTRAT_H +#define ISTRAT_H + +#include "postgres.h" +#include "access/attnum.h" +#include "access/skey.h" +#include "access/strat.h" +#include "utils/rel.h" /* for Relation */ + +/* + * StrategyNumberIsValid -- + * True iff the strategy number is valid. + */ +#define StrategyNumberIsValid(strategyNumber) \ + ((bool) ((strategyNumber) != InvalidStrategy)) + +/* + * StrategyNumberIsInBounds -- + * True iff strategy number is within given bounds. + * + * Note: + * Assumes StrategyNumber is an unsigned type. + * Assumes the bounded interval to be (0,max]. + */ +#define StrategyNumberIsInBounds(strategyNumber, maxStrategyNumber) \ + ((bool)(InvalidStrategy < (strategyNumber) && \ + (strategyNumber) <= (maxStrategyNumber))) + +/* + * StrategyMapIsValid -- + * True iff the index strategy mapping is valid. + */ +#define StrategyMapIsValid(map) PointerIsValid(map) + +/* + * IndexStrategyIsValid -- + * True iff the index strategy is valid. + */ +#define IndexStrategyIsValid(s) PointerIsValid(s) + +extern ScanKey StrategyMapGetScanKeyEntry(StrategyMap map, + StrategyNumber strategyNumber); +extern StrategyMap IndexStrategyGetStrategyMap(IndexStrategy indexStrategy, + StrategyNumber maxStrategyNum, AttrNumber attrNum); + +extern Size +AttributeNumberGetIndexStrategySize(AttrNumber maxAttributeNumber, + StrategyNumber maxStrategyNumber); +extern bool StrategyOperatorIsValid(StrategyOperator operator, + StrategyNumber maxStrategy); +extern bool StrategyTermIsValid(StrategyTerm term, + StrategyNumber maxStrategy); +extern bool StrategyExpressionIsValid(StrategyExpression expression, + StrategyNumber maxStrategy); +extern bool StrategyEvaluationIsValid(StrategyEvaluation evaluation); +extern StrategyNumber RelationGetStrategy(Relation relation, + AttrNumber attributeNumber, StrategyEvaluation evaluation, + RegProcedure procedure); +extern bool RelationInvokeStrategy(Relation relation, + StrategyEvaluation evaluation, AttrNumber attributeNumber, + StrategyNumber strategy, Datum left, Datum right); +extern void IndexSupportInitialize(IndexStrategy indexStrategy, + RegProcedure *indexSupport, Oid indexObjectId, + Oid accessMethodObjectId, StrategyNumber maxStrategyNumber, + StrategyNumber maxSupportNumber, AttrNumber maxAttributeNumber); + + +#endif /* ISTRAT_H */ diff --git a/src/backend/access/itup.h b/src/backend/access/itup.h new file mode 100644 index 00000000000..028bf430b0d --- /dev/null +++ b/src/backend/access/itup.h @@ -0,0 +1,104 @@ +/*------------------------------------------------------------------------- + * + * itup.h-- + * POSTGRES index tuple definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: itup.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef ITUP_H +#define ITUP_H + +#include "c.h" +#include "access/ibit.h" +#include "access/tupdesc.h" /* for TupleDesc */ +#include "storage/itemptr.h" + +#define MaxIndexAttributeNumber 7 + +typedef struct IndexTupleData { + ItemPointerData t_tid; /* reference TID to base tuple */ + + /* + * t_info is layed out in the following fashion: + * + * 15th (leftmost) bit: "has nulls" bit + * 14th bit: "has varlenas" bit + * 13th bit: "has rules" bit - (removed ay 11/94) + * bits 12-0 bit: size of tuple. + */ + + unsigned short t_info; /* various info about tuple */ + + /* + * please make sure sizeof(IndexTupleData) is MAXALIGN'ed. + * See IndexInfoFindDataOffset() for the reason. + */ + +} IndexTupleData; /* MORE DATA FOLLOWS AT END OF STRUCT */ + +typedef IndexTupleData *IndexTuple; + + +typedef struct InsertIndexResultData { + ItemPointerData pointerData; +} InsertIndexResultData; + +typedef InsertIndexResultData *InsertIndexResult; + + +typedef struct RetrieveIndexResultData { + ItemPointerData index_iptr; + ItemPointerData heap_iptr; +} RetrieveIndexResultData; + +typedef RetrieveIndexResultData *RetrieveIndexResult; + + +/*----------------- + * PredInfo - + * used for partial indices + *----------------- + */ +typedef struct PredInfo { + Node *pred; + Node *oldPred; +} PredInfo; + + +/* ---------------- + * externs + * ---------------- + */ + +#define INDEX_SIZE_MASK 0x1FFF +#define INDEX_NULL_MASK 0x8000 +#define INDEX_VAR_MASK 0x4000 + +#define IndexTupleSize(itup) (((IndexTuple) (itup))->t_info & 0x1FFF) +#define IndexTupleDSize(itup) ((itup).t_info & 0x1FFF) +#define IndexTupleNoNulls(itup) (!(((IndexTuple) (itup))->t_info & 0x8000)) +#define IndexTupleAllFixed(itup) (!(((IndexTuple) (itup))->t_info & 0x4000)) + +#define IndexTupleHasMinHeader(itup) (IndexTupleNoNulls(itup)) + + +/* indextuple.h */ +extern IndexTuple index_formtuple(TupleDesc tupleDescriptor, + Datum value[], char null[]); +extern char *fastgetiattr(IndexTuple tup, int attnum, + TupleDesc att, bool *isnull); +extern Datum index_getattr(IndexTuple tuple, AttrNumber attNum, + TupleDesc tupDesc, bool *isNullOutP); +extern RetrieveIndexResult +FormRetrieveIndexResult(ItemPointer indexItemPointer, + ItemPointer heapItemPointer); +extern void CopyIndexTuple(IndexTuple source, IndexTuple *target); + + +#endif /* ITUP_H */ + diff --git a/src/backend/access/nbtree.h b/src/backend/access/nbtree.h new file mode 100644 index 00000000000..d5c37a23950 --- /dev/null +++ b/src/backend/access/nbtree.h @@ -0,0 +1,264 @@ +/*------------------------------------------------------------------------- + * + * nbtree.h-- + * header file for postgres btree access method implementation. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: nbtree.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NBTREE_H +#define NBTREE_H + +#include "access/attnum.h" +#include "access/itup.h" +#include "access/htup.h" +#include "access/tupdesc.h" + +#include "access/istrat.h" +#include "access/funcindex.h" +#include "access/relscan.h" +#include "access/sdir.h" +#include "nodes/pg_list.h" + +/* + * BTPageOpaqueData -- At the end of every page, we store a pointer + * to both siblings in the tree. See Lehman and Yao's paper for more + * info. In addition, we need to know what sort of page this is + * (leaf or internal), and whether the page is available for reuse. + * + * Lehman and Yao's algorithm requires a ``high key'' on every page. + * The high key on a page is guaranteed to be greater than or equal + * to any key that appears on this page. Our insertion algorithm + * guarantees that we can use the initial least key on our right + * sibling as the high key. We allocate space for the line pointer + * to the high key in the opaque data at the end of the page. + * + * Rightmost pages in the tree have no high key. + */ + +typedef struct BTPageOpaqueData { + BlockNumber btpo_prev; + BlockNumber btpo_next; + uint16 btpo_flags; + +#define BTP_LEAF (1 << 0) +#define BTP_ROOT (1 << 1) +#define BTP_FREE (1 << 2) +#define BTP_META (1 << 3) + +} BTPageOpaqueData; + +typedef BTPageOpaqueData *BTPageOpaque; + +/* + * ScanOpaqueData is used to remember which buffers we're currently + * examining in the scan. We keep these buffers locked and pinned + * and recorded in the opaque entry of the scan in order to avoid + * doing a ReadBuffer() for every tuple in the index. This avoids + * semop() calls, which are expensive. + */ + +typedef struct BTScanOpaqueData { + Buffer btso_curbuf; + Buffer btso_mrkbuf; +} BTScanOpaqueData; + +typedef BTScanOpaqueData *BTScanOpaque; + +/* + * BTItems are what we store in the btree. Each item has an index + * tuple, including key and pointer values. In addition, we must + * guarantee that all tuples in the index are unique, in order to + * satisfy some assumptions in Lehman and Yao. The way that we do + * this is by generating a new OID for every insertion that we do in + * the tree. This adds eight bytes to the size of btree index + * tuples. Note that we do not use the OID as part of a composite + * key; the OID only serves as a unique identifier for a given index + * tuple (logical position within a page). + */ + +typedef struct BTItemData { + Oid bti_oid; + int32 bti_dummy; /* padding to make bti_itup + * align at 8-byte boundary + */ + IndexTupleData bti_itup; +} BTItemData; + +typedef BTItemData *BTItem; + +/* + * BTStackData -- As we descend a tree, we push the (key, pointer) + * pairs from internal nodes onto a private stack. If we split a + * leaf, we use this stack to walk back up the tree and insert data + * into parent nodes (and possibly to split them, too). Lehman and + * Yao's update algorithm guarantees that under no circumstances can + * our private stack give us an irredeemably bad picture up the tree. + * Again, see the paper for details. + */ + +typedef struct BTStackData { + BlockNumber bts_blkno; + OffsetNumber bts_offset; + BTItem bts_btitem; + struct BTStackData *bts_parent; +} BTStackData; + +typedef BTStackData *BTStack; + +/* + * We need to be able to tell the difference between read and write + * requests for pages, in order to do locking correctly. + */ + +#define BT_READ 0 +#define BT_WRITE 1 + +/* + * Similarly, the difference between insertion and non-insertion binary + * searches on a given page makes a difference when we're descending the + * tree. + */ + +#define BT_INSERTION 0 +#define BT_DESCENT 1 + +/* + * In general, the btree code tries to localize its knowledge about + * page layout to a couple of routines. However, we need a special + * value to indicate "no page number" in those places where we expect + * page numbers. + */ + +#define P_NONE 0 +#define P_LEFTMOST(opaque) ((opaque)->btpo_prev == P_NONE) +#define P_RIGHTMOST(opaque) ((opaque)->btpo_next == P_NONE) + +#define P_HIKEY ((OffsetNumber) 1) +#define P_FIRSTKEY ((OffsetNumber) 2) + +/* + * Strategy numbers -- ordering of these is <, <=, =, >=, > + */ + +#define BTLessStrategyNumber 1 +#define BTLessEqualStrategyNumber 2 +#define BTEqualStrategyNumber 3 +#define BTGreaterEqualStrategyNumber 4 +#define BTGreaterStrategyNumber 5 +#define BTMaxStrategyNumber 5 + +/* + * When a new operator class is declared, we require that the user + * supply us with an amproc procedure for determining whether, for + * two keys a and b, a < b, a = b, or a > b. This routine must + * return < 0, 0, > 0, respectively, in these three cases. Since we + * only have one such proc in amproc, it's number 1. + */ + +#define BTORDER_PROC 1 + + +/* + * prototypes for functions in nbtinsert.c + */ +extern InsertIndexResult _bt_doinsert(Relation rel, BTItem btitem); +extern bool _bt_itemcmp(Relation rel, Size keysz, BTItem item1, BTItem item2, + StrategyNumber strat); + +/* + * prototypes for functions in nbtpage.c + */ +extern void _bt_metapinit(Relation rel); +extern void _bt_checkmeta(Relation rel); +extern Buffer _bt_getroot(Relation rel, int access); +extern Buffer _bt_getbuf(Relation rel, BlockNumber blkno, int access); +extern void _bt_relbuf(Relation rel, Buffer buf, int access); +extern void _bt_wrtbuf(Relation rel, Buffer buf); +extern void _bt_wrtnorelbuf(Relation rel, Buffer buf); +extern void _bt_pageinit(Page page, Size size); +extern void _bt_metaproot(Relation rel, BlockNumber rootbknum); +extern Buffer _bt_getstackbuf(Relation rel, BTStack stack, int access); +extern void _bt_setpagelock(Relation rel, BlockNumber blkno, int access); +extern void _bt_unsetpagelock(Relation rel, BlockNumber blkno, int access); +extern void _bt_pagedel(Relation rel, ItemPointer tid); + +/* + * prototypes for functions in nbtree.c + */ +extern bool BuildingBtree; /* in nbtree.c */ + +extern void btbuild(Relation heap, Relation index, int natts, + AttrNumber *attnum, IndexStrategy istrat, uint16 pcount, + Datum *params, FuncIndexInfo *finfo, PredInfo *predInfo); +extern InsertIndexResult btinsert(Relation rel, IndexTuple itup); +extern char *btgettuple(IndexScanDesc scan, ScanDirection dir); +extern char *btbeginscan(Relation rel, bool fromEnd, uint16 keysz, + ScanKey scankey); + +extern void btrescan(IndexScanDesc scan, bool fromEnd, ScanKey scankey); +extern void btmovescan(IndexScanDesc scan, Datum v); +extern void btendscan(IndexScanDesc scan); +extern void btmarkpos(IndexScanDesc scan); +extern void btrestrpos(IndexScanDesc scan); +extern void btdelete(Relation rel, ItemPointer tid); + +/* + * prototypes for functions in nbtscan.c + */ +extern void _bt_regscan(IndexScanDesc scan); +extern void _bt_dropscan(IndexScanDesc scan); +extern void _bt_adjscans(Relation rel, ItemPointer tid); +extern void _bt_scandel(IndexScanDesc scan, BlockNumber blkno, + OffsetNumber offno); +extern bool _bt_scantouched(IndexScanDesc scan, BlockNumber blkno, + OffsetNumber offno); + +/* + * prototypes for functions in nbtsearch.c + */ +extern BTStack _bt_search(Relation rel, int keysz, ScanKey scankey, + Buffer *bufP); +extern Buffer _bt_moveright(Relation rel, Buffer buf, int keysz, + ScanKey scankey, int access); +extern bool _bt_skeycmp(Relation rel, Size keysz, ScanKey scankey, + Page page, ItemId itemid, StrategyNumber strat); +extern OffsetNumber _bt_binsrch(Relation rel, Buffer buf, int keysz, + ScanKey scankey, int srchtype); +extern RetrieveIndexResult _bt_next(IndexScanDesc scan, ScanDirection dir); +extern RetrieveIndexResult _bt_first(IndexScanDesc scan, ScanDirection dir); +extern bool _bt_step(IndexScanDesc scan, Buffer *bufP, ScanDirection dir); + +/* + * prototypes for functions in nbtstrat.c + */ +extern StrategyNumber _bt_getstrat(Relation rel, AttrNumber attno, + RegProcedure proc); +extern bool _bt_invokestrat(Relation rel, AttrNumber attno, + StrategyNumber strat, Datum left, Datum right); + +/* + * prototypes for functions in nbtutils.c + */ +extern ScanKey _bt_mkscankey(Relation rel, IndexTuple itup); +extern void _bt_freeskey(ScanKey skey); +extern void _bt_freestack(BTStack stack); +extern void _bt_orderkeys(Relation relation, uint16 *numberOfKeys, + ScanKey key); +extern bool _bt_checkqual(IndexScanDesc scan, IndexTuple itup); +extern BTItem _bt_formitem(IndexTuple itup); + +/* + * prototypes for functions in nbtsort.c + */ +extern void *_bt_spoolinit(Relation index, int ntapes); +extern void _bt_spooldestroy(void *spool); +extern void _bt_spool(Relation index, BTItem btitem, void *spool); +extern void _bt_upperbuild(Relation index, BlockNumber blk, int level); +extern void _bt_leafbuild(Relation index, void *spool); + +#endif /* NBTREE_H */ diff --git a/src/backend/access/nbtree/Makefile.inc b/src/backend/access/nbtree/Makefile.inc new file mode 100644 index 00000000000..50854008c01 --- /dev/null +++ b/src/backend/access/nbtree/Makefile.inc @@ -0,0 +1,15 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for access/nbtree (btree acess methods) +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/access/nbtree/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:11 scrappy Exp $ +# +#------------------------------------------------------------------------- + +SUBSRCS+= nbtcompare.c nbtinsert.c nbtpage.c nbtree.c nbtscan.c nbtsearch.c \ + nbtstrat.c nbtutils.c nbtsort.c diff --git a/src/backend/access/nbtree/README b/src/backend/access/nbtree/README new file mode 100644 index 00000000000..a204ad4af08 --- /dev/null +++ b/src/backend/access/nbtree/README @@ -0,0 +1,68 @@ +$Header: /cvsroot/pgsql/src/backend/access/nbtree/README,v 1.1.1.1 1996/07/09 06:21:12 scrappy Exp $ + +This directory contains a correct implementation of Lehman and Yao's +btree management algorithm that supports concurrent access for Postgres. +We have made the following changes in order to incorporate their algorithm +into Postgres: + + + The requirement that all btree keys be unique is too onerous, + but the algorithm won't work correctly without it. As a result, + this implementation adds an OID (guaranteed to be unique) to + every key in the index. This guarantees uniqueness within a set + of duplicates. Space overhead is four bytes. + + For this reason, when we're passed an index tuple to store by the + common access method code, we allocate a larger one and copy the + supplied tuple into it. No Postgres code outside of the btree + access method knows about this xid or sequence number. + + + Lehman and Yao don't require read locks, but assume that in- + memory copies of tree nodes are unshared. Postgres shares + in-memory buffers among backends. As a result, we do page- + level read locking on btree nodes in order to guarantee that + no record is modified while we are examining it. This reduces + concurrency but guaranteees correct behavior. + + + Read locks on a page are held for as long as a scan has a pointer + to the page. However, locks are always surrendered before the + sibling page lock is acquired (for readers), so we remain deadlock- + free. I will do a formal proof if I get bored anytime soon. + +In addition, the following things are handy to know: + + + Page zero of every btree is a meta-data page. This page stores + the location of the root page, a pointer to a list of free + pages, and other stuff that's handy to know. + + + This algorithm doesn't really work, since it requires ordered + writes, and UNIX doesn't support ordered writes. + + + There's one other case where we may screw up in this + implementation. When we start a scan, we descend the tree + to the key nearest the one in the qual, and once we get there, + position ourselves correctly for the qual type (eg, <, >=, etc). + If we happen to step off a page, decide we want to get back to + it, and fetch the page again, and if some bad person has split + the page and moved the last tuple we saw off of it, then the + code complains about botched concurrency in an elog(WARN, ...) + and gives up the ghost. This is the ONLY violation of Lehman + and Yao's guarantee of correct behavior that I am aware of in + this code. + +Notes to operator class implementors: + + With this implementation, we require the user to supply us with + a procedure for pg_amproc. This procedure should take two keys + A and B and return < 0, 0, or > 0 if A < B, A = B, or A > B, + respectively. See the contents of that relation for the btree + access method for some samples. + +Notes to mao for implementation document: + + On deletions, we need to adjust the position of active scans on + the index. The code in nbtscan.c handles this. We don't need to + do this for splits because of the way splits are handled; if they + happen behind us, we'll automatically go to the next page, and if + they happen in front of us, we're not affected by them. For + insertions, if we inserted a tuple behind the current scan location + on the current scan page, we move one space ahead. diff --git a/src/backend/access/nbtree/nbtcompare.c b/src/backend/access/nbtree/nbtcompare.c new file mode 100644 index 00000000000..e567b3c44cb --- /dev/null +++ b/src/backend/access/nbtree/nbtcompare.c @@ -0,0 +1,173 @@ +/*------------------------------------------------------------------------- + * + * btcompare.c-- + * Comparison functions for btree access method. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtcompare.c,v 1.1.1.1 1996/07/09 06:21:12 scrappy Exp $ + * + * NOTES + * These functions are stored in pg_amproc. For each operator class + * defined on btrees, they compute + * + * compare(a, b): + * < 0 if a < b, + * = 0 if a == b, + * > 0 if a > b. + *------------------------------------------------------------------------- + */ +#include <string.h> +#include "postgres.h" +#include "utils/nabstime.h" + +int32 +btint2cmp(int16 a, int16 b) +{ + return ((int32) (a - b)); +} + +int32 +btint4cmp(int32 a, int32 b) +{ + return (a - b); +} + +int32 +btint24cmp(int16 a, int32 b) +{ + return (((int32) a) - b); +} + +int32 +btint42cmp(int32 a, int16 b) +{ + return (a - ((int32) b)); +} + +int32 +btfloat4cmp(float32 a, float32 b) +{ + if (*a > *b) + return (1); + else if (*a == *b) + return (0); + else + return (-1); +} + +int32 +btfloat8cmp(float64 a, float64 b) +{ + if (*a > *b) + return (1); + else if (*a == *b) + return (0); + else + return (-1); +} + +int32 +btoidcmp(Oid a, Oid b) +{ + if (a > b) + return (1); + else if (a == b) + return (0); + else + return (-1); +} + +int32 +btabstimecmp(AbsoluteTime a, AbsoluteTime b) +{ + if (AbsoluteTimeIsBefore(a, b)) + return (1); + else if (AbsoluteTimeIsBefore(b, a)) + return (-1); + else + return (0); +} + +int32 +btcharcmp(char a, char b) +{ + return ((int32) (a - b)); +} + +int32 +btchar2cmp(uint16 a, uint16 b) +{ + return (strncmp((char *) &a, (char *) &b, 2)); +} + +int32 +btchar4cmp(uint32 a, uint32 b) +{ + return (strncmp((char *) &a, (char *) &b, 4)); +} + +int32 +btchar8cmp(char *a, char *b) +{ + return (strncmp(a, b, 8)); +} + +int32 +btchar16cmp(char *a, char *b) +{ + return (strncmp(a, b, 16)); +} + +int32 +btnamecmp(NameData *a, NameData *b) +{ + return (strncmp(a->data, b->data, NAMEDATALEN)); +} + +int32 +bttextcmp(struct varlena *a, struct varlena *b) +{ + char *ap, *bp; + int len; + int res; + + ap = VARDATA(a); + bp = VARDATA(b); + + /* len is the length of the shorter of the two strings */ + if ((len = VARSIZE(a)) > VARSIZE(b)) + len = VARSIZE(b); + + /* len includes the four bytes in which string length is stored */ + len -= sizeof(VARSIZE(a)); + + /* + * If the two strings differ in the first len bytes, or if they're + * the same in the first len bytes and they're both len bytes long, + * we're done. + */ + + res = 0; + if (len > 0) { + do { + res = (int) (*ap++ - *bp++); + len--; + } while (res == 0 && len != 0); + } + + if (res != 0 || VARSIZE(a) == VARSIZE(b)) + return (res); + + /* + * The two strings are the same in the first len bytes, and they + * are of different lengths. + */ + + if (VARSIZE(a) < VARSIZE(b)) + return (-1); + else + return (1); +} diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c new file mode 100644 index 00000000000..536c0aa385d --- /dev/null +++ b/src/backend/access/nbtree/nbtinsert.c @@ -0,0 +1,831 @@ +/*------------------------------------------------------------------------- + * + * btinsert.c-- + * Item insertion in Lehman and Yao btrees for Postgres. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtinsert.c,v 1.1.1.1 1996/07/09 06:21:12 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/nbtree.h" + +static InsertIndexResult _bt_insertonpg(Relation rel, Buffer buf, BTStack stack, int keysz, ScanKey scankey, BTItem btitem, BTItem afteritem); +static Buffer _bt_split(Relation rel, Buffer buf); +static OffsetNumber _bt_findsplitloc(Relation rel, Page page, OffsetNumber start, OffsetNumber maxoff, Size llimit); +static void _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf); +static OffsetNumber _bt_pgaddtup(Relation rel, Buffer buf, int keysz, ScanKey itup_scankey, Size itemsize, BTItem btitem, BTItem afteritem); +static bool _bt_goesonpg(Relation rel, Buffer buf, Size keysz, ScanKey scankey, BTItem afteritem); +static void _bt_updateitem(Relation rel, Size keysz, Buffer buf, Oid bti_oid, BTItem newItem); + +/* + * _bt_doinsert() -- Handle insertion of a single btitem in the tree. + * + * This routine is called by the public interface routines, btbuild + * and btinsert. By here, btitem is filled in, and has a unique + * (xid, seqno) pair. + */ +InsertIndexResult +_bt_doinsert(Relation rel, BTItem btitem) +{ + ScanKey itup_scankey; + IndexTuple itup; + BTStack stack; + Buffer buf; + BlockNumber blkno; + int natts; + InsertIndexResult res; + + itup = &(btitem->bti_itup); + + /* we need a scan key to do our search, so build one */ + itup_scankey = _bt_mkscankey(rel, itup); + natts = rel->rd_rel->relnatts; + + /* find the page containing this key */ + stack = _bt_search(rel, natts, itup_scankey, &buf); + blkno = BufferGetBlockNumber(buf); + + /* trade in our read lock for a write lock */ + _bt_relbuf(rel, buf, BT_READ); + buf = _bt_getbuf(rel, blkno, BT_WRITE); + + /* + * If the page was split between the time that we surrendered our + * read lock and acquired our write lock, then this page may no + * longer be the right place for the key we want to insert. In this + * case, we need to move right in the tree. See Lehman and Yao for + * an excruciatingly precise description. + */ + + buf = _bt_moveright(rel, buf, natts, itup_scankey, BT_WRITE); + + /* do the insertion */ + res = _bt_insertonpg(rel, buf, stack, natts, itup_scankey, + btitem, (BTItem) NULL); + + /* be tidy */ + _bt_freestack(stack); + _bt_freeskey(itup_scankey); + + return (res); +} + +/* + * _bt_insertonpg() -- Insert a tuple on a particular page in the index. + * + * This recursive procedure does the following things: + * + * + if necessary, splits the target page. + * + finds the right place to insert the tuple (taking into + * account any changes induced by a split). + * + inserts the tuple. + * + if the page was split, pops the parent stack, and finds the + * right place to insert the new child pointer (by walking + * right using information stored in the parent stack). + * + invoking itself with the appropriate tuple for the right + * child page on the parent. + * + * On entry, we must have the right buffer on which to do the + * insertion, and the buffer must be pinned and locked. On return, + * we will have dropped both the pin and the write lock on the buffer. + * + * The locking interactions in this code are critical. You should + * grok Lehman and Yao's paper before making any changes. In addition, + * you need to understand how we disambiguate duplicate keys in this + * implementation, in order to be able to find our location using + * L&Y "move right" operations. Since we may insert duplicate user + * keys, and since these dups may propogate up the tree, we use the + * 'afteritem' parameter to position ourselves correctly for the + * insertion on internal pages. + */ +static InsertIndexResult +_bt_insertonpg(Relation rel, + Buffer buf, + BTStack stack, + int keysz, + ScanKey scankey, + BTItem btitem, + BTItem afteritem) +{ + InsertIndexResult res; + Page page; + Buffer rbuf; + Buffer pbuf; + Page rpage; + ScanKey newskey; + BTItem ritem; + BTPageOpaque rpageop; + BlockNumber rbknum, itup_blkno; + OffsetNumber itup_off; + int itemsz; + InsertIndexResult newres; + BTItem new_item = (BTItem) NULL; + BTItem lowLeftItem; + + page = BufferGetPage(buf); + itemsz = IndexTupleDSize(btitem->bti_itup) + + (sizeof(BTItemData) - sizeof(IndexTupleData)); + + itemsz = DOUBLEALIGN(itemsz); /* be safe, PageAddItem will do this + but we need to be consistent */ + + if (PageGetFreeSpace(page) < itemsz) { + + /* split the buffer into left and right halves */ + rbuf = _bt_split(rel, buf); + + /* which new page (left half or right half) gets the tuple? */ + if (_bt_goesonpg(rel, buf, keysz, scankey, afteritem)) { + /* left page */ + itup_off = _bt_pgaddtup(rel, buf, keysz, scankey, + itemsz, btitem, afteritem); + itup_blkno = BufferGetBlockNumber(buf); + } else { + /* right page */ + itup_off = _bt_pgaddtup(rel, rbuf, keysz, scankey, + itemsz, btitem, afteritem); + itup_blkno = BufferGetBlockNumber(rbuf); + } + + /* + * By here, + * + * + our target page has been split; + * + the original tuple has been inserted; + * + we have write locks on both the old (left half) and new + * (right half) buffers, after the split; and + * + we have the key we want to insert into the parent. + * + * Do the parent insertion. We need to hold onto the locks for + * the child pages until we locate the parent, but we can release + * them before doing the actual insertion (see Lehman and Yao for + * the reasoning). + */ + + if (stack == (BTStack) NULL) { + + /* create a new root node and release the split buffers */ + _bt_newroot(rel, buf, rbuf); + _bt_relbuf(rel, buf, BT_WRITE); + _bt_relbuf(rel, rbuf, BT_WRITE); + + } else { + + /* form a index tuple that points at the new right page */ + rbknum = BufferGetBlockNumber(rbuf); + rpage = BufferGetPage(rbuf); + rpageop = (BTPageOpaque) PageGetSpecialPointer(rpage); + + /* + * By convention, the first entry (0) on every + * non-rightmost page is the high key for that page. In + * order to get the lowest key on the new right page, we + * actually look at its second (1) entry. + */ + + if (! P_RIGHTMOST(rpageop)) { + ritem = (BTItem) PageGetItem(rpage, + PageGetItemId(rpage, P_FIRSTKEY)); + } else { + ritem = (BTItem) PageGetItem(rpage, + PageGetItemId(rpage, P_HIKEY)); + } + + /* get a unique btitem for this key */ + new_item = _bt_formitem(&(ritem->bti_itup)); + + ItemPointerSet(&(new_item->bti_itup.t_tid), rbknum, P_HIKEY); + + /* find the parent buffer */ + pbuf = _bt_getstackbuf(rel, stack, BT_WRITE); + + /* + * If the key of new_item is < than the key of the item + * in the parent page pointing to the left page + * (stack->bts_btitem), we have to update the latter key; + * otherwise the keys on the parent page wouldn't be + * monotonically increasing after we inserted the new + * pointer to the right page (new_item). This only + * happens if our left page is the leftmost page and a + * new minimum key had been inserted before, which is not + * reflected in the parent page but didn't matter so + * far. If there are duplicate keys and this new minimum + * key spills over to our new right page, we get an + * inconsistency if we don't update the left key in the + * parent page. + */ + + if (_bt_itemcmp(rel, keysz, stack->bts_btitem, new_item, + BTGreaterStrategyNumber)) { + lowLeftItem = + (BTItem) PageGetItem(page, + PageGetItemId(page, P_FIRSTKEY)); + /* page must have right pointer after split */ + _bt_updateitem(rel, keysz, pbuf, stack->bts_btitem->bti_oid, + lowLeftItem); + } + + /* don't need the children anymore */ + _bt_relbuf(rel, buf, BT_WRITE); + _bt_relbuf(rel, rbuf, BT_WRITE); + + newskey = _bt_mkscankey(rel, &(new_item->bti_itup)); + newres = _bt_insertonpg(rel, pbuf, stack->bts_parent, + keysz, newskey, new_item, + stack->bts_btitem); + + /* be tidy */ + pfree(newres); + pfree(newskey); + pfree(new_item); + } + } else { + itup_off = _bt_pgaddtup(rel, buf, keysz, scankey, + itemsz, btitem, afteritem); + itup_blkno = BufferGetBlockNumber(buf); + + _bt_relbuf(rel, buf, BT_WRITE); + } + + /* by here, the new tuple is inserted */ + res = (InsertIndexResult) palloc(sizeof(InsertIndexResultData)); + ItemPointerSet(&(res->pointerData), itup_blkno, itup_off); + + return (res); +} + +/* + * _bt_split() -- split a page in the btree. + * + * On entry, buf is the page to split, and is write-locked and pinned. + * Returns the new right sibling of buf, pinned and write-locked. The + * pin and lock on buf are maintained. + */ +static Buffer +_bt_split(Relation rel, Buffer buf) +{ + Buffer rbuf; + Page origpage; + Page leftpage, rightpage; + BTPageOpaque ropaque, lopaque, oopaque; + Buffer sbuf; + Page spage; + BTPageOpaque sopaque; + Size itemsz; + ItemId itemid; + BTItem item; + OffsetNumber leftoff, rightoff; + OffsetNumber start; + OffsetNumber maxoff; + OffsetNumber firstright; + OffsetNumber i; + Size llimit; + + rbuf = _bt_getbuf(rel, P_NEW, BT_WRITE); + origpage = BufferGetPage(buf); + leftpage = PageGetTempPage(origpage, sizeof(BTPageOpaqueData)); + rightpage = BufferGetPage(rbuf); + + _bt_pageinit(rightpage, BufferGetPageSize(rbuf)); + _bt_pageinit(leftpage, BufferGetPageSize(buf)); + + /* init btree private data */ + oopaque = (BTPageOpaque) PageGetSpecialPointer(origpage); + lopaque = (BTPageOpaque) PageGetSpecialPointer(leftpage); + ropaque = (BTPageOpaque) PageGetSpecialPointer(rightpage); + + /* if we're splitting this page, it won't be the root when we're done */ + oopaque->btpo_flags &= ~BTP_ROOT; + lopaque->btpo_flags = ropaque->btpo_flags = oopaque->btpo_flags; + lopaque->btpo_prev = oopaque->btpo_prev; + ropaque->btpo_prev = BufferGetBlockNumber(buf); + lopaque->btpo_next = BufferGetBlockNumber(rbuf); + ropaque->btpo_next = oopaque->btpo_next; + + /* + * If the page we're splitting is not the rightmost page at its + * level in the tree, then the first (0) entry on the page is the + * high key for the page. We need to copy that to the right + * half. Otherwise (meaning the rightmost page case), we should + * treat the line pointers beginning at zero as user data. + * + * We leave a blank space at the start of the line table for the + * left page. We'll come back later and fill it in with the high + * key item we get from the right key. + */ + + leftoff = P_FIRSTKEY; + ropaque->btpo_next = oopaque->btpo_next; + if (! P_RIGHTMOST(oopaque)) { + /* splitting a non-rightmost page, start at the first data item */ + start = P_FIRSTKEY; + + /* copy the original high key to the new page */ + itemid = PageGetItemId(origpage, P_HIKEY); + itemsz = ItemIdGetLength(itemid); + item = (BTItem) PageGetItem(origpage, itemid); + (void) PageAddItem(rightpage, (Item) item, itemsz, P_HIKEY, LP_USED); + rightoff = P_FIRSTKEY; + } else { + /* splitting a rightmost page, "high key" is the first data item */ + start = P_HIKEY; + + /* the new rightmost page will not have a high key */ + rightoff = P_HIKEY; + } + maxoff = PageGetMaxOffsetNumber(origpage); + llimit = PageGetFreeSpace(leftpage) / 2; + firstright = _bt_findsplitloc(rel, origpage, start, maxoff, llimit); + + for (i = start; i <= maxoff; i = OffsetNumberNext(i)) { + itemid = PageGetItemId(origpage, i); + itemsz = ItemIdGetLength(itemid); + item = (BTItem) PageGetItem(origpage, itemid); + + /* decide which page to put it on */ + if (i < firstright) { + (void) PageAddItem(leftpage, (Item) item, itemsz, leftoff, + LP_USED); + leftoff = OffsetNumberNext(leftoff); + } else { + (void) PageAddItem(rightpage, (Item) item, itemsz, rightoff, + LP_USED); + rightoff = OffsetNumberNext(rightoff); + } + } + + /* + * Okay, page has been split, high key on right page is correct. Now + * set the high key on the left page to be the min key on the right + * page. + */ + + if (P_RIGHTMOST(ropaque)) { + itemid = PageGetItemId(rightpage, P_HIKEY); + } else { + itemid = PageGetItemId(rightpage, P_FIRSTKEY); + } + itemsz = ItemIdGetLength(itemid); + item = (BTItem) PageGetItem(rightpage, itemid); + + /* + * We left a hole for the high key on the left page; fill it. The + * modal crap is to tell the page manager to put the new item on the + * page and not screw around with anything else. Whoever designed + * this interface has presumably crawled back into the dung heap they + * came from. No one here will admit to it. + */ + + PageManagerModeSet(OverwritePageManagerMode); + (void) PageAddItem(leftpage, (Item) item, itemsz, P_HIKEY, LP_USED); + PageManagerModeSet(ShufflePageManagerMode); + + /* + * By here, the original data page has been split into two new halves, + * and these are correct. The algorithm requires that the left page + * never move during a split, so we copy the new left page back on top + * of the original. Note that this is not a waste of time, since we + * also require (in the page management code) that the center of a + * page always be clean, and the most efficient way to guarantee this + * is just to compact the data by reinserting it into a new left page. + */ + + PageRestoreTempPage(leftpage, origpage); + + /* write these guys out */ + _bt_wrtnorelbuf(rel, rbuf); + _bt_wrtnorelbuf(rel, buf); + + /* + * Finally, we need to grab the right sibling (if any) and fix the + * prev pointer there. We are guaranteed that this is deadlock-free + * since no other writer will be moving holding a lock on that page + * and trying to move left, and all readers release locks on a page + * before trying to fetch its neighbors. + */ + + if (! P_RIGHTMOST(ropaque)) { + sbuf = _bt_getbuf(rel, ropaque->btpo_next, BT_WRITE); + spage = BufferGetPage(sbuf); + sopaque = (BTPageOpaque) PageGetSpecialPointer(spage); + sopaque->btpo_prev = BufferGetBlockNumber(rbuf); + + /* write and release the old right sibling */ + _bt_wrtbuf(rel, sbuf); + } + + /* split's done */ + return (rbuf); +} + +/* + * _bt_findsplitloc() -- find a safe place to split a page. + * + * In order to guarantee the proper handling of searches for duplicate + * keys, the first duplicate in the chain must either be the first + * item on the page after the split, or the entire chain must be on + * one of the two pages. That is, + * [1 2 2 2 3 4 5] + * must become + * [1] [2 2 2 3 4 5] + * or + * [1 2 2 2] [3 4 5] + * but not + * [1 2 2] [2 3 4 5]. + * However, + * [2 2 2 2 2 3 4] + * may be split as + * [2 2 2 2] [2 3 4]. + */ +static OffsetNumber +_bt_findsplitloc(Relation rel, + Page page, + OffsetNumber start, + OffsetNumber maxoff, + Size llimit) +{ + OffsetNumber i; + OffsetNumber saferight; + ItemId nxtitemid, safeitemid; + BTItem safeitem, nxtitem; + IndexTuple safetup, nxttup; + Size nbytes; + TupleDesc itupdesc; + int natts; + int attno; + Datum attsafe; + Datum attnext; + bool null; + + itupdesc = RelationGetTupleDescriptor(rel); + natts = rel->rd_rel->relnatts; + + saferight = start; + safeitemid = PageGetItemId(page, saferight); + nbytes = ItemIdGetLength(safeitemid) + sizeof(ItemIdData); + safeitem = (BTItem) PageGetItem(page, safeitemid); + safetup = &(safeitem->bti_itup); + + i = OffsetNumberNext(start); + + while (nbytes < llimit) { + + /* check the next item on the page */ + nxtitemid = PageGetItemId(page, i); + nbytes += (ItemIdGetLength(nxtitemid) + sizeof(ItemIdData)); + nxtitem = (BTItem) PageGetItem(page, nxtitemid); + nxttup = &(nxtitem->bti_itup); + + /* test against last known safe item */ + for (attno = 1; attno <= natts; attno++) { + attsafe = index_getattr(safetup, attno, itupdesc, &null); + attnext = index_getattr(nxttup, attno, itupdesc, &null); + + /* + * If the tuple we're looking at isn't equal to the last safe one + * we saw, then it's our new safe tuple. + */ + + if (!_bt_invokestrat(rel, attno, BTEqualStrategyNumber, + attsafe, attnext)) { + safetup = nxttup; + saferight = i; + + /* break is for the attno for loop */ + break; + } + } + i = OffsetNumberNext(i); + } + + /* + * If the chain of dups starts at the beginning of the page and extends + * past the halfway mark, we can split it in the middle. + */ + + if (saferight == start) + saferight = i; + + return (saferight); +} + +/* + * _bt_newroot() -- Create a new root page for the index. + * + * We've just split the old root page and need to create a new one. + * In order to do this, we add a new root page to the file, then lock + * the metadata page and update it. This is guaranteed to be deadlock- + * free, because all readers release their locks on the metadata page + * before trying to lock the root, and all writers lock the root before + * trying to lock the metadata page. We have a write lock on the old + * root page, so we have not introduced any cycles into the waits-for + * graph. + * + * On entry, lbuf (the old root) and rbuf (its new peer) are write- + * locked. We don't drop the locks in this routine; that's done by + * the caller. On exit, a new root page exists with entries for the + * two new children. The new root page is neither pinned nor locked. + */ +static void +_bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf) +{ + Buffer rootbuf; + Page lpage, rpage, rootpage; + BlockNumber lbkno, rbkno; + BlockNumber rootbknum; + BTPageOpaque rootopaque; + ItemId itemid; + BTItem item; + Size itemsz; + BTItem new_item; + + /* get a new root page */ + rootbuf = _bt_getbuf(rel, P_NEW, BT_WRITE); + rootpage = BufferGetPage(rootbuf); + _bt_pageinit(rootpage, BufferGetPageSize(rootbuf)); + + /* set btree special data */ + rootopaque = (BTPageOpaque) PageGetSpecialPointer(rootpage); + rootopaque->btpo_prev = rootopaque->btpo_next = P_NONE; + rootopaque->btpo_flags |= BTP_ROOT; + + /* + * Insert the internal tuple pointers. + */ + + lbkno = BufferGetBlockNumber(lbuf); + rbkno = BufferGetBlockNumber(rbuf); + lpage = BufferGetPage(lbuf); + rpage = BufferGetPage(rbuf); + + /* + * step over the high key on the left page while building the + * left page pointer. + */ + itemid = PageGetItemId(lpage, P_FIRSTKEY); + itemsz = ItemIdGetLength(itemid); + item = (BTItem) PageGetItem(lpage, itemid); + new_item = _bt_formitem(&(item->bti_itup)); + ItemPointerSet(&(new_item->bti_itup.t_tid), lbkno, P_FIRSTKEY); + + /* + * insert the left page pointer into the new root page. the root + * page is the rightmost page on its level so the "high key" item + * is the first data item. + */ + (void) PageAddItem(rootpage, (Item) new_item, itemsz, P_HIKEY, LP_USED); + pfree(new_item); + + /* + * the right page is the rightmost page on the second level, so + * the "high key" item is the first data item on that page as well. + */ + itemid = PageGetItemId(rpage, P_HIKEY); + itemsz = ItemIdGetLength(itemid); + item = (BTItem) PageGetItem(rpage, itemid); + new_item = _bt_formitem(&(item->bti_itup)); + ItemPointerSet(&(new_item->bti_itup.t_tid), rbkno, P_HIKEY); + + /* + * insert the right page pointer into the new root page. + */ + (void) PageAddItem(rootpage, (Item) new_item, itemsz, P_FIRSTKEY, LP_USED); + pfree(new_item); + + /* write and let go of the root buffer */ + rootbknum = BufferGetBlockNumber(rootbuf); + _bt_wrtbuf(rel, rootbuf); + + /* update metadata page with new root block number */ + _bt_metaproot(rel, rootbknum); +} + +/* + * _bt_pgaddtup() -- add a tuple to a particular page in the index. + * + * This routine adds the tuple to the page as requested, and keeps the + * write lock and reference associated with the page's buffer. It is + * an error to call pgaddtup() without a write lock and reference. If + * afteritem is non-null, it's the item that we expect our new item + * to follow. Otherwise, we do a binary search for the correct place + * and insert the new item there. + */ +static OffsetNumber +_bt_pgaddtup(Relation rel, + Buffer buf, + int keysz, + ScanKey itup_scankey, + Size itemsize, + BTItem btitem, + BTItem afteritem) +{ + OffsetNumber itup_off; + OffsetNumber first; + Page page; + BTPageOpaque opaque; + BTItem chkitem; + Oid afteroid; + + page = BufferGetPage(buf); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + first = P_RIGHTMOST(opaque) ? P_HIKEY : P_FIRSTKEY; + + if (afteritem == (BTItem) NULL) { + itup_off = _bt_binsrch(rel, buf, keysz, itup_scankey, BT_INSERTION); + } else { + afteroid = afteritem->bti_oid; + itup_off = first; + + do { + chkitem = + (BTItem) PageGetItem(page, PageGetItemId(page, itup_off)); + itup_off = OffsetNumberNext(itup_off); + } while (chkitem->bti_oid != afteroid); + } + + (void) PageAddItem(page, (Item) btitem, itemsize, itup_off, LP_USED); + + /* write the buffer, but hold our lock */ + _bt_wrtnorelbuf(rel, buf); + + return (itup_off); +} + +/* + * _bt_goesonpg() -- Does a new tuple belong on this page? + * + * This is part of the complexity introduced by allowing duplicate + * keys into the index. The tuple belongs on this page if: + * + * + there is no page to the right of this one; or + * + it is less than the high key on the page; or + * + the item it is to follow ("afteritem") appears on this + * page. + */ +static bool +_bt_goesonpg(Relation rel, + Buffer buf, + Size keysz, + ScanKey scankey, + BTItem afteritem) +{ + Page page; + ItemId hikey; + BTPageOpaque opaque; + BTItem chkitem; + OffsetNumber offnum, maxoff; + Oid afteroid; + bool found; + + page = BufferGetPage(buf); + + /* no right neighbor? */ + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + if (P_RIGHTMOST(opaque)) + return (true); + + /* + * this is a non-rightmost page, so it must have a high key item. + * + * If the scan key is < the high key (the min key on the next page), + * then it for sure belongs here. + */ + hikey = PageGetItemId(page, P_HIKEY); + if (_bt_skeycmp(rel, keysz, scankey, page, hikey, BTLessStrategyNumber)) + return (true); + + /* + * If the scan key is > the high key, then it for sure doesn't belong + * here. + */ + + if (_bt_skeycmp(rel, keysz, scankey, page, hikey, BTGreaterStrategyNumber)) + return (false); + + /* + * If we have no adjacency information, and the item is equal to the + * high key on the page (by here it is), then the item does not belong + * on this page. + */ + + if (afteritem == (BTItem) NULL) + return (false); + + /* damn, have to work for it. i hate that. */ + afteroid = afteritem->bti_oid; + maxoff = PageGetMaxOffsetNumber(page); + + /* + * Search the entire page for the afteroid. We need to do this, rather + * than doing a binary search and starting from there, because if the + * key we're searching for is the leftmost key in the tree at this + * level, then a binary search will do the wrong thing. Splits are + * pretty infrequent, so the cost isn't as bad as it could be. + */ + + found = false; + for (offnum = P_FIRSTKEY; + offnum <= maxoff; + offnum = OffsetNumberNext(offnum)) { + chkitem = (BTItem) PageGetItem(page, PageGetItemId(page, offnum)); + if (chkitem->bti_oid == afteroid) { + found = true; + break; + } + } + + return (found); +} + +/* + * _bt_itemcmp() -- compare item1 to item2 using a requested + * strategy (<, <=, =, >=, >) + * + */ +bool +_bt_itemcmp(Relation rel, + Size keysz, + BTItem item1, + BTItem item2, + StrategyNumber strat) +{ + TupleDesc tupDes; + IndexTuple indexTuple1, indexTuple2; + Datum attrDatum1, attrDatum2; + int i; + bool isNull; + bool compare; + + tupDes = RelationGetTupleDescriptor(rel); + indexTuple1 = &(item1->bti_itup); + indexTuple2 = &(item2->bti_itup); + + for (i = 1; i <= keysz; i++) { + attrDatum1 = index_getattr(indexTuple1, i, tupDes, &isNull); + attrDatum2 = index_getattr(indexTuple2, i, tupDes, &isNull); + compare = _bt_invokestrat(rel, i, strat, attrDatum1, attrDatum2); + if (!compare) { + return (false); + } + } + return (true); +} + +/* + * _bt_updateitem() -- updates the key of the item identified by the + * oid with the key of newItem (done in place) + * + */ +static void +_bt_updateitem(Relation rel, + Size keysz, + Buffer buf, + Oid bti_oid, + BTItem newItem) +{ + Page page; + OffsetNumber maxoff; + OffsetNumber i; + ItemPointerData itemPtrData; + BTItem item; + IndexTuple oldIndexTuple, newIndexTuple; + + page = BufferGetPage(buf); + maxoff = PageGetMaxOffsetNumber(page); + + /* locate item on the page */ + i = P_HIKEY; + do { + item = (BTItem) PageGetItem(page, PageGetItemId(page, i)); + i = OffsetNumberNext(i); + } while (i <= maxoff && item->bti_oid != bti_oid); + + /* this should never happen (in theory) */ + if (item->bti_oid != bti_oid) { + elog(FATAL, "_bt_getstackbuf was lying!!"); + } + + oldIndexTuple = &(item->bti_itup); + newIndexTuple = &(newItem->bti_itup); + + /* keep the original item pointer */ + ItemPointerCopy(&(oldIndexTuple->t_tid), &itemPtrData); + CopyIndexTuple(newIndexTuple, &oldIndexTuple); + ItemPointerCopy(&itemPtrData, &(oldIndexTuple->t_tid)); +} diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c new file mode 100644 index 00000000000..ce411a80d11 --- /dev/null +++ b/src/backend/access/nbtree/nbtpage.c @@ -0,0 +1,523 @@ +/*------------------------------------------------------------------------- + * + * btpage.c-- + * BTree-specific page management code for the Postgres btree access + * method. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtpage.c,v 1.1.1.1 1996/07/09 06:21:12 scrappy Exp $ + * + * NOTES + * Postgres btree pages look like ordinary relation pages. The opaque + * data at high addresses includes pointers to left and right siblings + * and flag data describing page state. The first page in a btree, page + * zero, is special -- it stores meta-information describing the tree. + * Pages one and higher store the actual tree data. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "access/genam.h" +#include "access/nbtree.h" + +#define BTREE_METAPAGE 0 +#define BTREE_MAGIC 0x053162 +#define BTREE_VERSION 0 + +typedef struct BTMetaPageData { + uint32 btm_magic; + uint32 btm_version; + BlockNumber btm_root; +} BTMetaPageData; + +#define BTPageGetMeta(p) \ + ((BTMetaPageData *) &((PageHeader) p)->pd_linp[0]) + +extern bool BuildingBtree; + +/* + * We use high-concurrency locking on btrees. There are two cases in + * which we don't do locking. One is when we're building the btree. + * Since the creating transaction has not committed, no one can see + * the index, and there's no reason to share locks. The second case + * is when we're just starting up the database system. We use some + * special-purpose initialization code in the relation cache manager + * (see utils/cache/relcache.c) to allow us to do indexed scans on + * the system catalogs before we'd normally be able to. This happens + * before the lock table is fully initialized, so we can't use it. + * Strictly speaking, this violates 2pl, but we don't do 2pl on the + * system catalogs anyway, so I declare this to be okay. + */ + +#define USELOCKING (!BuildingBtree && !IsInitProcessingMode()) + +/* + * _bt_metapinit() -- Initialize the metadata page of a btree. + */ +void +_bt_metapinit(Relation rel) +{ + Buffer buf; + Page pg; + int nblocks; + BTMetaPageData metad; + BTPageOpaque op; + + /* can't be sharing this with anyone, now... */ + if (USELOCKING) + RelationSetLockForWrite(rel); + + if ((nblocks = RelationGetNumberOfBlocks(rel)) != 0) { + elog(WARN, "Cannot initialize non-empty btree %s", + RelationGetRelationName(rel)); + } + + buf = ReadBuffer(rel, P_NEW); + pg = BufferGetPage(buf); + _bt_pageinit(pg, BufferGetPageSize(buf)); + + metad.btm_magic = BTREE_MAGIC; + metad.btm_version = BTREE_VERSION; + metad.btm_root = P_NONE; + memmove((char *) BTPageGetMeta(pg), (char *) &metad, sizeof(metad)); + + op = (BTPageOpaque) PageGetSpecialPointer(pg); + op->btpo_flags = BTP_META; + + WriteBuffer(buf); + + /* all done */ + if (USELOCKING) + RelationUnsetLockForWrite(rel); +} + +/* + * _bt_checkmeta() -- Verify that the metadata stored in a btree are + * reasonable. + */ +void +_bt_checkmeta(Relation rel) +{ + Buffer metabuf; + Page metap; + BTMetaPageData *metad; + BTPageOpaque op; + int nblocks; + + /* if the relation is empty, this is init time; don't complain */ + if ((nblocks = RelationGetNumberOfBlocks(rel)) == 0) + return; + + metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ); + metap = BufferGetPage(metabuf); + op = (BTPageOpaque) PageGetSpecialPointer(metap); + if (!(op->btpo_flags & BTP_META)) { + elog(WARN, "Invalid metapage for index %s", + RelationGetRelationName(rel)); + } + metad = BTPageGetMeta(metap); + + if (metad->btm_magic != BTREE_MAGIC) { + elog(WARN, "Index %s is not a btree", + RelationGetRelationName(rel)); + } + + if (metad->btm_version != BTREE_VERSION) { + elog(WARN, "Version mismatch on %s: version %d file, version %d code", + RelationGetRelationName(rel), + metad->btm_version, BTREE_VERSION); + } + + _bt_relbuf(rel, metabuf, BT_READ); +} + +/* + * _bt_getroot() -- Get the root page of the btree. + * + * Since the root page can move around the btree file, we have to read + * its location from the metadata page, and then read the root page + * itself. If no root page exists yet, we have to create one. The + * standard class of race conditions exists here; I think I covered + * them all in the Hopi Indian rain dance of lock requests below. + * + * We pass in the access type (BT_READ or BT_WRITE), and return the + * root page's buffer with the appropriate lock type set. Reference + * count on the root page gets bumped by ReadBuffer. The metadata + * page is unlocked and unreferenced by this process when this routine + * returns. + */ +Buffer +_bt_getroot(Relation rel, int access) +{ + Buffer metabuf; + Page metapg; + BTPageOpaque metaopaque; + Buffer rootbuf; + Page rootpg; + BTPageOpaque rootopaque; + BlockNumber rootblkno; + BTMetaPageData *metad; + + metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ); + metapg = BufferGetPage(metabuf); + metaopaque = (BTPageOpaque) PageGetSpecialPointer(metapg); + Assert(metaopaque->btpo_flags & BTP_META); + metad = BTPageGetMeta(metapg); + + /* if no root page initialized yet, do it */ + if (metad->btm_root == P_NONE) { + + /* turn our read lock in for a write lock */ + _bt_relbuf(rel, metabuf, BT_READ); + metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE); + metapg = BufferGetPage(metabuf); + metaopaque = (BTPageOpaque) PageGetSpecialPointer(metapg); + Assert(metaopaque->btpo_flags & BTP_META); + metad = BTPageGetMeta(metapg); + + /* + * Race condition: if someone else initialized the metadata between + * the time we released the read lock and acquired the write lock, + * above, we want to avoid doing it again. + */ + + if (metad->btm_root == P_NONE) { + + /* + * Get, initialize, write, and leave a lock of the appropriate + * type on the new root page. Since this is the first page in + * the tree, it's a leaf. + */ + + rootbuf = _bt_getbuf(rel, P_NEW, BT_WRITE); + rootblkno = BufferGetBlockNumber(rootbuf); + rootpg = BufferGetPage(rootbuf); + metad->btm_root = rootblkno; + _bt_pageinit(rootpg, BufferGetPageSize(rootbuf)); + rootopaque = (BTPageOpaque) PageGetSpecialPointer(rootpg); + rootopaque->btpo_flags |= (BTP_LEAF | BTP_ROOT); + _bt_wrtnorelbuf(rel, rootbuf); + + /* swap write lock for read lock, if appropriate */ + if (access != BT_WRITE) { + _bt_setpagelock(rel, rootblkno, BT_READ); + _bt_unsetpagelock(rel, rootblkno, BT_WRITE); + } + + /* okay, metadata is correct */ + _bt_wrtbuf(rel, metabuf); + } else { + + /* + * Metadata initialized by someone else. In order to guarantee + * no deadlocks, we have to release the metadata page and start + * all over again. + */ + + _bt_relbuf(rel, metabuf, BT_WRITE); + return (_bt_getroot(rel, access)); + } + } else { + rootbuf = _bt_getbuf(rel, metad->btm_root, access); + + /* done with the meta page */ + _bt_relbuf(rel, metabuf, BT_READ); + } + + /* + * Race condition: If the root page split between the time we looked + * at the metadata page and got the root buffer, then we got the wrong + * buffer. + */ + + rootpg = BufferGetPage(rootbuf); + rootopaque = (BTPageOpaque) PageGetSpecialPointer(rootpg); + if (!(rootopaque->btpo_flags & BTP_ROOT)) { + + /* it happened, try again */ + _bt_relbuf(rel, rootbuf, access); + return (_bt_getroot(rel, access)); + } + + /* + * By here, we have a correct lock on the root block, its reference + * count is correct, and we have no lock set on the metadata page. + * Return the root block. + */ + + return (rootbuf); +} + +/* + * _bt_getbuf() -- Get a buffer by block number for read or write. + * + * When this routine returns, the appropriate lock is set on the + * requested buffer its reference count is correct. + */ +Buffer +_bt_getbuf(Relation rel, BlockNumber blkno, int access) +{ + Buffer buf; + Page page; + + /* + * If we want a new block, we can't set a lock of the appropriate type + * until we've instantiated the buffer. + */ + + if (blkno != P_NEW) { + if (access == BT_WRITE) + _bt_setpagelock(rel, blkno, BT_WRITE); + else + _bt_setpagelock(rel, blkno, BT_READ); + + buf = ReadBuffer(rel, blkno); + } else { + buf = ReadBuffer(rel, blkno); + blkno = BufferGetBlockNumber(buf); + page = BufferGetPage(buf); + _bt_pageinit(page, BufferGetPageSize(buf)); + + if (access == BT_WRITE) + _bt_setpagelock(rel, blkno, BT_WRITE); + else + _bt_setpagelock(rel, blkno, BT_READ); + } + + /* ref count and lock type are correct */ + return (buf); +} + +/* + * _bt_relbuf() -- release a locked buffer. + */ +void +_bt_relbuf(Relation rel, Buffer buf, int access) +{ + BlockNumber blkno; + + blkno = BufferGetBlockNumber(buf); + + /* access had better be one of read or write */ + if (access == BT_WRITE) + _bt_unsetpagelock(rel, blkno, BT_WRITE); + else + _bt_unsetpagelock(rel, blkno, BT_READ); + + ReleaseBuffer(buf); +} + +/* + * _bt_wrtbuf() -- write a btree page to disk. + * + * This routine releases the lock held on the buffer and our reference + * to it. It is an error to call _bt_wrtbuf() without a write lock + * or a reference to the buffer. + */ +void +_bt_wrtbuf(Relation rel, Buffer buf) +{ + BlockNumber blkno; + + blkno = BufferGetBlockNumber(buf); + WriteBuffer(buf); + _bt_unsetpagelock(rel, blkno, BT_WRITE); +} + +/* + * _bt_wrtnorelbuf() -- write a btree page to disk, but do not release + * our reference or lock. + * + * It is an error to call _bt_wrtnorelbuf() without a write lock + * or a reference to the buffer. + */ +void +_bt_wrtnorelbuf(Relation rel, Buffer buf) +{ + BlockNumber blkno; + + blkno = BufferGetBlockNumber(buf); + WriteNoReleaseBuffer(buf); +} + +/* + * _bt_pageinit() -- Initialize a new page. + */ +void +_bt_pageinit(Page page, Size size) +{ + /* + * Cargo-cult programming -- don't really need this to be zero, but + * creating new pages is an infrequent occurrence and it makes me feel + * good when I know they're empty. + */ + + memset(page, 0, size); + + PageInit(page, size, sizeof(BTPageOpaqueData)); +} + +/* + * _bt_metaproot() -- Change the root page of the btree. + * + * Lehman and Yao require that the root page move around in order to + * guarantee deadlock-free short-term, fine-granularity locking. When + * we split the root page, we record the new parent in the metadata page + * for the relation. This routine does the work. + * + * No direct preconditions, but if you don't have the a write lock on + * at least the old root page when you call this, you're making a big + * mistake. On exit, metapage data is correct and we no longer have + * a reference to or lock on the metapage. + */ +void +_bt_metaproot(Relation rel, BlockNumber rootbknum) +{ + Buffer metabuf; + Page metap; + BTPageOpaque metaopaque; + BTMetaPageData *metad; + + metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE); + metap = BufferGetPage(metabuf); + metaopaque = (BTPageOpaque) PageGetSpecialPointer(metap); + Assert(metaopaque->btpo_flags & BTP_META); + metad = BTPageGetMeta(metap); + metad->btm_root = rootbknum; + _bt_wrtbuf(rel, metabuf); +} + +/* + * _bt_getstackbuf() -- Walk back up the tree one step, and find the item + * we last looked at in the parent. + * + * This is possible because we save a bit image of the last item + * we looked at in the parent, and the update algorithm guarantees + * that if items above us in the tree move, they only move right. + */ +Buffer +_bt_getstackbuf(Relation rel, BTStack stack, int access) +{ + Buffer buf; + BlockNumber blkno; + OffsetNumber start, offnum, maxoff; + OffsetNumber i; + Page page; + ItemId itemid; + BTItem item; + BTPageOpaque opaque; + + blkno = stack->bts_blkno; + buf = _bt_getbuf(rel, blkno, access); + page = BufferGetPage(buf); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + maxoff = PageGetMaxOffsetNumber(page); + + if (maxoff >= stack->bts_offset) { + itemid = PageGetItemId(page, stack->bts_offset); + item = (BTItem) PageGetItem(page, itemid); + + /* if the item is where we left it, we're done */ + if (item->bti_oid == stack->bts_btitem->bti_oid) + return (buf); + + /* if the item has just moved right on this page, we're done */ + for (i = OffsetNumberNext(stack->bts_offset); + i <= maxoff; + i = OffsetNumberNext(i)) { + itemid = PageGetItemId(page, i); + item = (BTItem) PageGetItem(page, itemid); + + /* if the item is where we left it, we're done */ + if (item->bti_oid == stack->bts_btitem->bti_oid) + return (buf); + } + } + + /* by here, the item we're looking for moved right at least one page */ + for (;;) { + blkno = opaque->btpo_next; + if (P_RIGHTMOST(opaque)) + elog(FATAL, "my bits moved right off the end of the world!"); + + _bt_relbuf(rel, buf, access); + buf = _bt_getbuf(rel, blkno, access); + page = BufferGetPage(buf); + maxoff = PageGetMaxOffsetNumber(page); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + + /* if we have a right sibling, step over the high key */ + start = P_RIGHTMOST(opaque) ? P_HIKEY : P_FIRSTKEY; + + /* see if it's on this page */ + for (offnum = start; + offnum <= maxoff; + offnum = OffsetNumberNext(offnum)) { + itemid = PageGetItemId(page, offnum); + item = (BTItem) PageGetItem(page, itemid); + if (item->bti_oid == stack->bts_btitem->bti_oid) + return (buf); + } + } +} + +void +_bt_setpagelock(Relation rel, BlockNumber blkno, int access) +{ + ItemPointerData iptr; + + if (USELOCKING) { + ItemPointerSet(&iptr, blkno, P_HIKEY); + + if (access == BT_WRITE) + RelationSetSingleWLockPage(rel, &iptr); + else + RelationSetSingleRLockPage(rel, &iptr); + } +} + +void +_bt_unsetpagelock(Relation rel, BlockNumber blkno, int access) +{ + ItemPointerData iptr; + + if (USELOCKING) { + ItemPointerSet(&iptr, blkno, P_HIKEY); + + if (access == BT_WRITE) + RelationUnsetSingleWLockPage(rel, &iptr); + else + RelationUnsetSingleRLockPage(rel, &iptr); + } +} + +void +_bt_pagedel(Relation rel, ItemPointer tid) +{ + Buffer buf; + Page page; + BlockNumber blkno; + OffsetNumber offno; + + blkno = ItemPointerGetBlockNumber(tid); + offno = ItemPointerGetOffsetNumber(tid); + + buf = _bt_getbuf(rel, blkno, BT_WRITE); + page = BufferGetPage(buf); + + PageIndexTupleDelete(page, offno); + + /* write the buffer and release the lock */ + _bt_wrtbuf(rel, buf); +} diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c new file mode 100644 index 00000000000..06016119964 --- /dev/null +++ b/src/backend/access/nbtree/nbtree.c @@ -0,0 +1,516 @@ +/*------------------------------------------------------------------------- + * + * btree.c-- + * Implementation of Lehman and Yao's btree management algorithm for + * Postgres. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtree.c,v 1.1.1.1 1996/07/09 06:21:12 scrappy Exp $ + * + * NOTES + * This file contains only the public interface routines. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/sdir.h" +#include "access/nbtree.h" +#include "access/funcindex.h" + +#include "nodes/execnodes.h" +#include "nodes/plannodes.h" + +#include "executor/executor.h" +#include "executor/tuptable.h" + +#include "catalog/index.h" + +bool BuildingBtree = false; +bool FastBuild = false; /* turn this on to make bulk builds work*/ + +/* + * btbuild() -- build a new btree index. + * + * We use a global variable to record the fact that we're creating + * a new index. This is used to avoid high-concurrency locking, + * since the index won't be visible until this transaction commits + * and since building is guaranteed to be single-threaded. + */ +void +btbuild(Relation heap, + Relation index, + int natts, + AttrNumber *attnum, + IndexStrategy istrat, + uint16 pcount, + Datum *params, + FuncIndexInfo *finfo, + PredInfo *predInfo) +{ + HeapScanDesc hscan; + Buffer buffer; + HeapTuple htup; + IndexTuple itup; + TupleDesc htupdesc, itupdesc; + Datum *attdata; + bool *nulls; + InsertIndexResult res; + int nhtups, nitups; + int i; + BTItem btitem; + ExprContext *econtext; + TupleTable tupleTable; + TupleTableSlot *slot; + Oid hrelid, irelid; + Node *pred, *oldPred; + void *spool; + + /* note that this is a new btree */ + BuildingBtree = true; + + pred = predInfo->pred; + oldPred = predInfo->oldPred; + + /* initialize the btree index metadata page (if this is a new index) */ + if (oldPred == NULL) + _bt_metapinit(index); + + /* get tuple descriptors for heap and index relations */ + htupdesc = RelationGetTupleDescriptor(heap); + itupdesc = RelationGetTupleDescriptor(index); + + /* get space for data items that'll appear in the index tuple */ + attdata = (Datum *) palloc(natts * sizeof(Datum)); + nulls = (bool *) palloc(natts * sizeof(bool)); + + /* + * If this is a predicate (partial) index, we will need to evaluate the + * predicate using ExecQual, which requires the current tuple to be in a + * slot of a TupleTable. In addition, ExecQual must have an ExprContext + * referring to that slot. Here, we initialize dummy TupleTable and + * ExprContext objects for this purpose. --Nels, Feb '92 + */ +#ifndef OMIT_PARTIAL_INDEX + if (pred != NULL || oldPred != NULL) { + tupleTable = ExecCreateTupleTable(1); + slot = ExecAllocTableSlot(tupleTable); + econtext = makeNode(ExprContext); + FillDummyExprContext(econtext, slot, htupdesc, InvalidBuffer); + } +#endif /* OMIT_PARTIAL_INDEX */ + + /* start a heap scan */ + hscan = heap_beginscan(heap, 0, NowTimeQual, 0, (ScanKey) NULL); + htup = heap_getnext(hscan, 0, &buffer); + + /* build the index */ + nhtups = nitups = 0; + + if (FastBuild) { + spool = _bt_spoolinit(index, 7); + res = (InsertIndexResult) NULL; + } + + for (; HeapTupleIsValid(htup); htup = heap_getnext(hscan, 0, &buffer)) { + + nhtups++; + + /* + * If oldPred != NULL, this is an EXTEND INDEX command, so skip + * this tuple if it was already in the existing partial index + */ + if (oldPred != NULL) { +#ifndef OMIT_PARTIAL_INDEX + + /*SetSlotContents(slot, htup);*/ + slot->val = htup; + if (ExecQual((List*)oldPred, econtext) == true) { + nitups++; + continue; + } +#endif /* OMIT_PARTIAL_INDEX */ + } + + /* Skip this tuple if it doesn't satisfy the partial-index predicate */ + if (pred != NULL) { +#ifndef OMIT_PARTIAL_INDEX + /* SetSlotContents(slot, htup); */ + slot->val = htup; + if (ExecQual((List*)pred, econtext) == false) + continue; +#endif /* OMIT_PARTIAL_INDEX */ + } + + nitups++; + + /* + * For the current heap tuple, extract all the attributes + * we use in this index, and note which are null. + */ + + for (i = 1; i <= natts; i++) { + int attoff; + bool attnull; + + /* + * Offsets are from the start of the tuple, and are + * zero-based; indices are one-based. The next call + * returns i - 1. That's data hiding for you. + */ + + attoff = AttrNumberGetAttrOffset(i); + attdata[attoff] = GetIndexValue(htup, + htupdesc, + attoff, + attnum, + finfo, + &attnull, + buffer); + nulls[attoff] = (attnull ? 'n' : ' '); + } + + /* form an index tuple and point it at the heap tuple */ + itup = index_formtuple(itupdesc, attdata, nulls); + + /* + * If the single index key is null, we don't insert it into + * the index. Btrees support scans on <, <=, =, >=, and >. + * Relational algebra says that A op B (where op is one of the + * operators above) returns null if either A or B is null. This + * means that no qualification used in an index scan could ever + * return true on a null attribute. It also means that indices + * can't be used by ISNULL or NOTNULL scans, but that's an + * artifact of the strategy map architecture chosen in 1986, not + * of the way nulls are handled here. + */ + + if (itup->t_info & INDEX_NULL_MASK) { + pfree(itup); + continue; + } + + itup->t_tid = htup->t_ctid; + btitem = _bt_formitem(itup); + + /* + * if we are doing bottom-up btree build, we insert the index + * into a spool page for subsequent processing. otherwise, we + * insert into the btree. + */ + if (FastBuild) { + _bt_spool(index, btitem, spool); + } else { + res = _bt_doinsert(index, btitem); + } + + pfree(btitem); + pfree(itup); + if (res) { + pfree(res); + } + } + + /* okay, all heap tuples are indexed */ + heap_endscan(hscan); + + if (pred != NULL || oldPred != NULL) { +#ifndef OMIT_PARTIAL_INDEX + ExecDestroyTupleTable(tupleTable, true); + pfree(econtext); +#endif /* OMIT_PARTIAL_INDEX */ + } + + /* + * if we are doing bottom-up btree build, we now have a bunch of + * sorted runs in the spool pages. finish the build by (1) + * merging the runs, (2) inserting the sorted tuples into btree + * pages and (3) building the upper levels. + */ + if (FastBuild) { + _bt_spool(index, (BTItem) NULL, spool); /* flush spool */ + _bt_leafbuild(index, spool); + _bt_spooldestroy(spool); + } + + /* + * Since we just counted the tuples in the heap, we update its + * stats in pg_class to guarantee that the planner takes advantage + * of the index we just created. Finally, only update statistics + * during normal index definitions, not for indices on system catalogs + * created during bootstrap processing. We must close the relations + * before updatings statistics to guarantee that the relcache entries + * are flushed when we increment the command counter in UpdateStats(). + */ + if (IsNormalProcessingMode()) + { + hrelid = heap->rd_id; + irelid = index->rd_id; + heap_close(heap); + index_close(index); + UpdateStats(hrelid, nhtups, true); + UpdateStats(irelid, nitups, false); + if (oldPred != NULL) { + if (nitups == nhtups) pred = NULL; + UpdateIndexPredicate(irelid, oldPred, pred); + } + } + + /* be tidy */ + pfree(nulls); + pfree(attdata); + + /* all done */ + BuildingBtree = false; +} + +/* + * btinsert() -- insert an index tuple into a btree. + * + * Descend the tree recursively, find the appropriate location for our + * new tuple, put it there, set its unique OID as appropriate, and + * return an InsertIndexResult to the caller. + */ +InsertIndexResult +btinsert(Relation rel, IndexTuple itup) +{ + BTItem btitem; + InsertIndexResult res; + + if (itup->t_info & INDEX_NULL_MASK) + return ((InsertIndexResult) NULL); + + btitem = _bt_formitem(itup); + + res = _bt_doinsert(rel, btitem); + pfree(btitem); + + return (res); +} + +/* + * btgettuple() -- Get the next tuple in the scan. + */ +char * +btgettuple(IndexScanDesc scan, ScanDirection dir) +{ + RetrieveIndexResult res; + + /* + * If we've already initialized this scan, we can just advance it + * in the appropriate direction. If we haven't done so yet, we + * call a routine to get the first item in the scan. + */ + + if (ItemPointerIsValid(&(scan->currentItemData))) + res = _bt_next(scan, dir); + else + res = _bt_first(scan, dir); + + return ((char *) res); +} + +/* + * btbeginscan() -- start a scan on a btree index + */ +char * +btbeginscan(Relation rel, bool fromEnd, uint16 keysz, ScanKey scankey) +{ + IndexScanDesc scan; + StrategyNumber strat; + BTScanOpaque so; + + /* first order the keys in the qualification */ + if (keysz > 1) + _bt_orderkeys(rel, &keysz, scankey); + + /* now get the scan */ + scan = RelationGetIndexScan(rel, fromEnd, keysz, scankey); + so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData)); + so->btso_curbuf = so->btso_mrkbuf = InvalidBuffer; + scan->opaque = so; + + /* finally, be sure that the scan exploits the tree order */ + scan->scanFromEnd = false; + scan->flags = 0x0; + if (keysz > 0) { + strat = _bt_getstrat(scan->relation, 1 /* XXX */, + scankey[0].sk_procedure); + + if (strat == BTLessStrategyNumber + || strat == BTLessEqualStrategyNumber) + scan->scanFromEnd = true; + } else { + scan->scanFromEnd = true; + } + + /* register scan in case we change pages it's using */ + _bt_regscan(scan); + + return ((char *) scan); +} + +/* + * btrescan() -- rescan an index relation + */ +void +btrescan(IndexScanDesc scan, bool fromEnd, ScanKey scankey) +{ + ItemPointer iptr; + BTScanOpaque so; + + so = (BTScanOpaque) scan->opaque; + + /* we hold a read lock on the current page in the scan */ + if (ItemPointerIsValid(iptr = &(scan->currentItemData))) { + _bt_relbuf(scan->relation, so->btso_curbuf, BT_READ); + so->btso_curbuf = InvalidBuffer; + ItemPointerSetInvalid(iptr); + } + + /* and we hold a read lock on the last marked item in the scan */ + if (ItemPointerIsValid(iptr = &(scan->currentMarkData))) { + _bt_relbuf(scan->relation, so->btso_mrkbuf, BT_READ); + so->btso_mrkbuf = InvalidBuffer; + ItemPointerSetInvalid(iptr); + } + + /* reset the scan key */ + if (scan->numberOfKeys > 0) { + memmove(scan->keyData, + scankey, + scan->numberOfKeys * sizeof(ScanKeyData)); + } +} + +void +btmovescan(IndexScanDesc scan, Datum v) +{ + ItemPointer iptr; + BTScanOpaque so; + + so = (BTScanOpaque) scan->opaque; + + /* release any locks we still hold */ + if (ItemPointerIsValid(iptr = &(scan->currentItemData))) { + _bt_relbuf(scan->relation, so->btso_curbuf, BT_READ); + so->btso_curbuf = InvalidBuffer; + ItemPointerSetInvalid(iptr); + } + + scan->keyData[0].sk_argument = v; +} + +/* + * btendscan() -- close down a scan + */ +void +btendscan(IndexScanDesc scan) +{ + ItemPointer iptr; + BTScanOpaque so; + + so = (BTScanOpaque) scan->opaque; + + /* release any locks we still hold */ + if (ItemPointerIsValid(iptr = &(scan->currentItemData))) { + if (BufferIsValid(so->btso_curbuf)) + _bt_relbuf(scan->relation, so->btso_curbuf, BT_READ); + so->btso_curbuf = InvalidBuffer; + ItemPointerSetInvalid(iptr); + } + + if (ItemPointerIsValid(iptr = &(scan->currentMarkData))) { + if (BufferIsValid(so->btso_mrkbuf)) + _bt_relbuf(scan->relation, so->btso_mrkbuf, BT_READ); + so->btso_mrkbuf = InvalidBuffer; + ItemPointerSetInvalid(iptr); + } + + /* don't need scan registered anymore */ + _bt_dropscan(scan); + + /* be tidy */ +#ifdef PERFECT_MMGR + pfree (scan->opaque); +#endif /* PERFECT_MMGR */ +} + +/* + * btmarkpos() -- save current scan position + */ +void +btmarkpos(IndexScanDesc scan) +{ + ItemPointer iptr; + BTScanOpaque so; + + so = (BTScanOpaque) scan->opaque; + + /* release lock on old marked data, if any */ + if (ItemPointerIsValid(iptr = &(scan->currentMarkData))) { + _bt_relbuf(scan->relation, so->btso_mrkbuf, BT_READ); + so->btso_mrkbuf = InvalidBuffer; + ItemPointerSetInvalid(iptr); + } + + /* bump lock on currentItemData and copy to currentMarkData */ + if (ItemPointerIsValid(&(scan->currentItemData))) { + so->btso_mrkbuf = _bt_getbuf(scan->relation, + BufferGetBlockNumber(so->btso_curbuf), + BT_READ); + scan->currentMarkData = scan->currentItemData; + } +} + +/* + * btrestrpos() -- restore scan to last saved position + */ +void +btrestrpos(IndexScanDesc scan) +{ + ItemPointer iptr; + BTScanOpaque so; + + so = (BTScanOpaque) scan->opaque; + + /* release lock on current data, if any */ + if (ItemPointerIsValid(iptr = &(scan->currentItemData))) { + _bt_relbuf(scan->relation, so->btso_curbuf, BT_READ); + so->btso_curbuf = InvalidBuffer; + ItemPointerSetInvalid(iptr); + } + + /* bump lock on currentMarkData and copy to currentItemData */ + if (ItemPointerIsValid(&(scan->currentMarkData))) { + so->btso_curbuf = _bt_getbuf(scan->relation, + BufferGetBlockNumber(so->btso_mrkbuf), + BT_READ); + + scan->currentItemData = scan->currentMarkData; + } +} + +/* stubs */ +void +btdelete(Relation rel, ItemPointer tid) +{ + /* adjust any active scans that will be affected by this deletion */ + _bt_adjscans(rel, tid); + + /* delete the data from the page */ + _bt_pagedel(rel, tid); +} diff --git a/src/backend/access/nbtree/nbtscan.c b/src/backend/access/nbtree/nbtscan.c new file mode 100644 index 00000000000..62a029bc06f --- /dev/null +++ b/src/backend/access/nbtree/nbtscan.c @@ -0,0 +1,164 @@ +/*------------------------------------------------------------------------- + * + * btscan.c-- + * manage scans on btrees. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/nbtree/Attic/nbtscan.c,v 1.1.1.1 1996/07/09 06:21:12 scrappy Exp $ + * + * + * NOTES + * Because we can be doing an index scan on a relation while we update + * it, we need to avoid missing data that moves around in the index. + * The routines and global variables in this file guarantee that all + * scans in the local address space stay correctly positioned. This + * is all we need to worry about, since write locking guarantees that + * no one else will be on the same page at the same time as we are. + * + * The scheme is to manage a list of active scans in the current backend. + * Whenever we add or remove records from an index, or whenever we + * split a leaf page, we check the list of active scans to see if any + * has been affected. A scan is affected only if it is on the same + * relation, and the same page, as the update. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/sdir.h" +#include "access/nbtree.h" + +typedef struct BTScanListData { + IndexScanDesc btsl_scan; + struct BTScanListData *btsl_next; +} BTScanListData; + +typedef BTScanListData *BTScanList; + +static BTScanList BTScans = (BTScanList) NULL; + +/* + * _bt_regscan() -- register a new scan. + */ +void +_bt_regscan(IndexScanDesc scan) +{ + BTScanList new_el; + + new_el = (BTScanList) palloc(sizeof(BTScanListData)); + new_el->btsl_scan = scan; + new_el->btsl_next = BTScans; + BTScans = new_el; +} + +/* + * _bt_dropscan() -- drop a scan from the scan list + */ +void +_bt_dropscan(IndexScanDesc scan) +{ + BTScanList chk, last; + + last = (BTScanList) NULL; + for (chk = BTScans; + chk != (BTScanList) NULL && chk->btsl_scan != scan; + chk = chk->btsl_next) { + last = chk; + } + + if (chk == (BTScanList) NULL) + elog(WARN, "btree scan list trashed; can't find 0x%lx", scan); + + if (last == (BTScanList) NULL) + BTScans = chk->btsl_next; + else + last->btsl_next = chk->btsl_next; + +#ifdef PERFECT_MEM + pfree (chk); +#endif /* PERFECT_MEM */ +} + +void +_bt_adjscans(Relation rel, ItemPointer tid) +{ + BTScanList l; + Oid relid; + + relid = rel->rd_id; + for (l = BTScans; l != (BTScanList) NULL; l = l->btsl_next) { + if (relid == l->btsl_scan->relation->rd_id) + _bt_scandel(l->btsl_scan, ItemPointerGetBlockNumber(tid), + ItemPointerGetOffsetNumber(tid)); + } +} + +void +_bt_scandel(IndexScanDesc scan, BlockNumber blkno, OffsetNumber offno) +{ + ItemPointer current; + Buffer buf; + BTScanOpaque so; + + if (!_bt_scantouched(scan, blkno, offno)) + return; + + so = (BTScanOpaque) scan->opaque; + buf = so->btso_curbuf; + + current = &(scan->currentItemData); + if (ItemPointerIsValid(current) + && ItemPointerGetBlockNumber(current) == blkno + && ItemPointerGetOffsetNumber(current) >= offno) { + _bt_step(scan, &buf, BackwardScanDirection); + so->btso_curbuf = buf; + } + + current = &(scan->currentMarkData); + if (ItemPointerIsValid(current) + && ItemPointerGetBlockNumber(current) == blkno + && ItemPointerGetOffsetNumber(current) >= offno) { + ItemPointerData tmp; + tmp = *current; + *current = scan->currentItemData; + scan->currentItemData = tmp; + _bt_step(scan, &buf, BackwardScanDirection); + so->btso_mrkbuf = buf; + tmp = *current; + *current = scan->currentItemData; + scan->currentItemData = tmp; + } +} + +bool +_bt_scantouched(IndexScanDesc scan, BlockNumber blkno, OffsetNumber offno) +{ + ItemPointer current; + + current = &(scan->currentItemData); + if (ItemPointerIsValid(current) + && ItemPointerGetBlockNumber(current) == blkno + && ItemPointerGetOffsetNumber(current) >= offno) + return (true); + + current = &(scan->currentMarkData); + if (ItemPointerIsValid(current) + && ItemPointerGetBlockNumber(current) == blkno + && ItemPointerGetOffsetNumber(current) >= offno) + return (true); + + return (false); +} diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c new file mode 100644 index 00000000000..d7a7fc7d62e --- /dev/null +++ b/src/backend/access/nbtree/nbtsearch.c @@ -0,0 +1,1133 @@ +/*------------------------------------------------------------------------- + * + * btsearch.c-- + * search code for postgres btrees. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtsearch.c,v 1.1.1.1 1996/07/09 06:21:12 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "fmgr.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/skey.h" +#include "access/sdir.h" +#include "access/nbtree.h" + +static BTStack _bt_searchr(Relation rel, int keysz, ScanKey scankey, Buffer *bufP, BTStack stack_in); +static OffsetNumber _bt_firsteq(Relation rel, TupleDesc itupdesc, Page page, Size keysz, ScanKey scankey, OffsetNumber offnum); +static int _bt_compare(Relation rel, TupleDesc itupdesc, Page page, int keysz, ScanKey scankey, OffsetNumber offnum); +static bool _bt_twostep(IndexScanDesc scan, Buffer *bufP, ScanDirection dir); +static RetrieveIndexResult _bt_endpoint(IndexScanDesc scan, ScanDirection dir); + +/* + * _bt_search() -- Search for a scan key in the index. + * + * This routine is actually just a helper that sets things up and + * calls a recursive-descent search routine on the tree. + */ +BTStack +_bt_search(Relation rel, int keysz, ScanKey scankey, Buffer *bufP) +{ + *bufP = _bt_getroot(rel, BT_READ); + return (_bt_searchr(rel, keysz, scankey, bufP, (BTStack) NULL)); +} + +/* + * _bt_searchr() -- Search the tree recursively for a particular scankey. + */ +static BTStack +_bt_searchr(Relation rel, + int keysz, + ScanKey scankey, + Buffer *bufP, + BTStack stack_in) +{ + BTStack stack; + OffsetNumber offnum; + Page page; + BTPageOpaque opaque; + BlockNumber par_blkno; + BlockNumber blkno; + ItemId itemid; + BTItem btitem; + BTItem item_save; + int item_nbytes; + IndexTuple itup; + + /* if this is a leaf page, we're done */ + page = BufferGetPage(*bufP); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + if (opaque->btpo_flags & BTP_LEAF) + return (stack_in); + + /* + * Find the appropriate item on the internal page, and get the child + * page that it points to. + */ + + par_blkno = BufferGetBlockNumber(*bufP); + offnum = _bt_binsrch(rel, *bufP, keysz, scankey, BT_DESCENT); + itemid = PageGetItemId(page, offnum); + btitem = (BTItem) PageGetItem(page, itemid); + itup = &(btitem->bti_itup); + blkno = ItemPointerGetBlockNumber(&(itup->t_tid)); + + /* + * We need to save the bit image of the index entry we chose in the + * parent page on a stack. In case we split the tree, we'll use this + * bit image to figure out what our real parent page is, in case the + * parent splits while we're working lower in the tree. See the paper + * by Lehman and Yao for how this is detected and handled. (We use + * unique OIDs to disambiguate duplicate keys in the index -- Lehman + * and Yao disallow duplicate keys). + */ + + item_nbytes = ItemIdGetLength(itemid); + item_save = (BTItem) palloc(item_nbytes); + memmove((char *) item_save, (char *) btitem, item_nbytes); + stack = (BTStack) palloc(sizeof(BTStackData)); + stack->bts_blkno = par_blkno; + stack->bts_offset = offnum; + stack->bts_btitem = item_save; + stack->bts_parent = stack_in; + + /* drop the read lock on the parent page and acquire one on the child */ + _bt_relbuf(rel, *bufP, BT_READ); + *bufP = _bt_getbuf(rel, blkno, BT_READ); + + /* + * Race -- the page we just grabbed may have split since we read its + * pointer in the parent. If it has, we may need to move right to its + * new sibling. Do that. + */ + + *bufP = _bt_moveright(rel, *bufP, keysz, scankey, BT_READ); + + /* okay, all set to move down a level */ + return (_bt_searchr(rel, keysz, scankey, bufP, stack)); +} + +/* + * _bt_moveright() -- move right in the btree if necessary. + * + * When we drop and reacquire a pointer to a page, it is possible that + * the page has changed in the meanwhile. If this happens, we're + * guaranteed that the page has "split right" -- that is, that any + * data that appeared on the page originally is either on the page + * or strictly to the right of it. + * + * This routine decides whether or not we need to move right in the + * tree by examining the high key entry on the page. If that entry + * is strictly less than one we expect to be on the page, then our + * picture of the page is incorrect and we need to move right. + * + * On entry, we have the buffer pinned and a lock of the proper type. + * If we move right, we release the buffer and lock and acquire the + * same on the right sibling. + */ +Buffer +_bt_moveright(Relation rel, + Buffer buf, + int keysz, + ScanKey scankey, + int access) +{ + Page page; + BTPageOpaque opaque; + ItemId hikey; + ItemId itemid; + BlockNumber rblkno; + + page = BufferGetPage(buf); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + + /* if we're on a rightmost page, we don't need to move right */ + if (P_RIGHTMOST(opaque)) + return (buf); + + /* by convention, item 0 on non-rightmost pages is the high key */ + hikey = PageGetItemId(page, P_HIKEY); + + /* + * If the scan key that brought us to this page is >= the high key + * stored on the page, then the page has split and we need to move + * right. + */ + + if (_bt_skeycmp(rel, keysz, scankey, page, hikey, + BTGreaterEqualStrategyNumber)) { + + /* move right as long as we need to */ + do { + /* + * If this page consists of all duplicate keys (hikey and first + * key on the page have the same value), then we don't need to + * step right. + */ + if (PageGetMaxOffsetNumber(page) > P_HIKEY) { + itemid = PageGetItemId(page, P_FIRSTKEY); + if (_bt_skeycmp(rel, keysz, scankey, page, itemid, + BTEqualStrategyNumber)) { + /* break is for the "move right" while loop */ + break; + } + } + + /* step right one page */ + rblkno = opaque->btpo_next; + _bt_relbuf(rel, buf, access); + buf = _bt_getbuf(rel, rblkno, access); + page = BufferGetPage(buf); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + hikey = PageGetItemId(page, P_HIKEY); + + } while (! P_RIGHTMOST(opaque) + && _bt_skeycmp(rel, keysz, scankey, page, hikey, + BTGreaterEqualStrategyNumber)); + } + return (buf); +} + +/* + * _bt_skeycmp() -- compare a scan key to a particular item on a page using + * a requested strategy (<, <=, =, >=, >). + * + * We ignore the unique OIDs stored in the btree item here. Those + * numbers are intended for use internally only, in repositioning a + * scan after a page split. They do not impose any meaningful ordering. + * + * The comparison is A <op> B, where A is the scan key and B is the + * tuple pointed at by itemid on page. + */ +bool +_bt_skeycmp(Relation rel, + Size keysz, + ScanKey scankey, + Page page, + ItemId itemid, + StrategyNumber strat) +{ + BTItem item; + IndexTuple indexTuple; + TupleDesc tupDes; + ScanKey entry; + int i; + Datum attrDatum; + Datum keyDatum; + bool compare; + bool isNull; + + item = (BTItem) PageGetItem(page, itemid); + indexTuple = &(item->bti_itup); + + tupDes = RelationGetTupleDescriptor(rel); + + /* see if the comparison is true for all of the key attributes */ + for (i=1; i <= keysz; i++) { + + entry = &scankey[i-1]; + attrDatum = index_getattr(indexTuple, + entry->sk_attno, + tupDes, + &isNull); + keyDatum = entry->sk_argument; + + compare = _bt_invokestrat(rel, i, strat, keyDatum, attrDatum); + if (!compare) + return (false); + } + + return (true); +} + +/* + * _bt_binsrch() -- Do a binary search for a key on a particular page. + * + * The scankey we get has the compare function stored in the procedure + * entry of each data struct. We invoke this regproc to do the + * comparison for every key in the scankey. _bt_binsrch() returns + * the OffsetNumber of the first matching key on the page, or the + * OffsetNumber at which the matching key would appear if it were + * on this page. + * + * By the time this procedure is called, we're sure we're looking + * at the right page -- don't need to walk right. _bt_binsrch() has + * no lock or refcount side effects on the buffer. + */ +OffsetNumber +_bt_binsrch(Relation rel, + Buffer buf, + int keysz, + ScanKey scankey, + int srchtype) +{ + TupleDesc itupdesc; + Page page; + BTPageOpaque opaque; + OffsetNumber low, mid, high; + bool match; + int result; + + page = BufferGetPage(buf); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + + /* by convention, item 0 on any non-rightmost page is the high key */ + low = P_RIGHTMOST(opaque) ? P_HIKEY : P_FIRSTKEY; + + high = PageGetMaxOffsetNumber(page); + + /* + * Since for non-rightmost pages, the zeroeth item on the page is the + * high key, there are two notions of emptiness. One is if nothing + * appears on the page. The other is if nothing but the high key does. + * The reason we test high <= low, rather than high == low, is that + * after vacuuming there may be nothing *but* the high key on a page. + * In that case, given the scheme above, low = 1 and high = 0. + */ + + if (PageIsEmpty(page) || (! P_RIGHTMOST(opaque) && high <= low)) + return (low); + + itupdesc = RelationGetTupleDescriptor(rel); + match = false; + + while ((high - low) > 1) { + mid = low + ((high - low) / 2); + result = _bt_compare(rel, itupdesc, page, keysz, scankey, mid); + + if (result > 0) + low = mid; + else if (result < 0) + high = mid - 1; + else { + match = true; + break; + } + } + + /* if we found a match, we want to find the first one on the page */ + if (match) { + return (_bt_firsteq(rel, itupdesc, page, keysz, scankey, mid)); + } else { + + /* + * We terminated because the endpoints got too close together. There + * are two cases to take care of. + * + * For non-insertion searches on internal pages, we want to point at + * the last key <, or first key =, the scankey on the page. This + * guarantees that we'll descend the tree correctly. + * + * For all other cases, we want to point at the first key >= + * the scankey on the page. This guarantees that scans and + * insertions will happen correctly. + */ + + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + if (!(opaque->btpo_flags & BTP_LEAF) && srchtype == BT_DESCENT) { + + /* + * We want the last key <, or first key ==, the scan key. + */ + + result = _bt_compare(rel, itupdesc, page, keysz, scankey, high); + + if (result == 0) { + return (_bt_firsteq(rel, itupdesc, page, keysz, scankey, high)); + } else if (result > 0) { + return (high); + } else { + return (low); + } + } else { + + /* we want the first key >= the scan key */ + result = _bt_compare(rel, itupdesc, page, keysz, scankey, low); + if (result <= 0) { + return (low); + } else { + if (low == high) + return (OffsetNumberNext(low)); + + result = _bt_compare(rel, itupdesc, page, keysz, scankey, high); + if (result <= 0) + return (high); + else + return (OffsetNumberNext(high)); + } + } + } +} + +static OffsetNumber +_bt_firsteq(Relation rel, + TupleDesc itupdesc, + Page page, + Size keysz, + ScanKey scankey, + OffsetNumber offnum) +{ + BTPageOpaque opaque; + OffsetNumber limit; + + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + + /* skip the high key, if any */ + limit = P_RIGHTMOST(opaque) ? P_HIKEY : P_FIRSTKEY; + + /* walk backwards looking for the first key in the chain of duplicates */ + while (offnum > limit + && _bt_compare(rel, itupdesc, page, + keysz, scankey, OffsetNumberPrev(offnum)) == 0) { + offnum = OffsetNumberPrev(offnum); + } + + return (offnum); +} + +/* + * _bt_compare() -- Compare scankey to a particular tuple on the page. + * + * This routine returns: + * -1 if scankey < tuple at offnum; + * 0 if scankey == tuple at offnum; + * +1 if scankey > tuple at offnum. + * + * In order to avoid having to propagate changes up the tree any time + * a new minimal key is inserted, the leftmost entry on the leftmost + * page is less than all possible keys, by definition. + */ +static int +_bt_compare(Relation rel, + TupleDesc itupdesc, + Page page, + int keysz, + ScanKey scankey, + OffsetNumber offnum) +{ + Datum datum; + BTItem btitem; + ItemId itemid; + IndexTuple itup; + BTPageOpaque opaque; + ScanKey entry; + AttrNumber attno; + int result; + int i; + bool null; + + /* + * If this is a leftmost internal page, and if our comparison is + * with the first key on the page, then the item at that position is + * by definition less than the scan key. + */ + + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + if (!(opaque->btpo_flags & BTP_LEAF) + && P_LEFTMOST(opaque) + && offnum == P_HIKEY) { + itemid = PageGetItemId(page, offnum); + + /* + * we just have to believe that this will only be called with + * offnum == P_HIKEY when P_HIKEY is the OffsetNumber of the + * first actual data key (i.e., this is also a rightmost + * page). there doesn't seem to be any code that implies + * that the leftmost page is normally missing a high key as + * well as the rightmost page. but that implies that this + * code path only applies to the root -- which seems + * unlikely.. + */ + if (! P_RIGHTMOST(opaque)) { + elog(WARN, "_bt_compare: invalid comparison to high key"); + } + + /* + * If the item on the page is equal to the scankey, that's + * okay to admit. We just can't claim that the first key on + * the page is greater than anything. + */ + + if (_bt_skeycmp(rel, keysz, scankey, page, itemid, + BTEqualStrategyNumber)) { + return (0); + } + return (1); + } + + btitem = (BTItem) PageGetItem(page, PageGetItemId(page, offnum)); + itup = &(btitem->bti_itup); + + /* + * The scan key is set up with the attribute number associated with each + * term in the key. It is important that, if the index is multi-key, + * the scan contain the first k key attributes, and that they be in + * order. If you think about how multi-key ordering works, you'll + * understand why this is. + * + * We don't test for violation of this condition here. + */ + + for (i = 1; i <= keysz; i++) { + long tmpres; + + entry = &scankey[i - 1]; + attno = entry->sk_attno; + datum = index_getattr(itup, attno, itupdesc, &null); + tmpres = (long) FMGR_PTR2(entry->sk_func, entry->sk_procedure, + entry->sk_argument, datum); + result = tmpres; + + /* if the keys are unequal, return the difference */ + if (result != 0) + return (result); + } + + /* by here, the keys are equal */ + return (0); +} + +/* + * _bt_next() -- Get the next item in a scan. + * + * On entry, we have a valid currentItemData in the scan, and a + * read lock on the page that contains that item. We do not have + * the page pinned. We return the next item in the scan. On + * exit, we have the page containing the next item locked but not + * pinned. + */ +RetrieveIndexResult +_bt_next(IndexScanDesc scan, ScanDirection dir) +{ + Relation rel; + Buffer buf; + Page page; + OffsetNumber offnum; + RetrieveIndexResult res; + BlockNumber blkno; + ItemPointer current; + ItemPointer iptr; + BTItem btitem; + IndexTuple itup; + BTScanOpaque so; + + rel = scan->relation; + so = (BTScanOpaque) scan->opaque; + current = &(scan->currentItemData); + + /* + * XXX 10 may 91: somewhere there's a bug in our management of the + * cached buffer for this scan. wei discovered it. the following + * is a workaround so he can work until i figure out what's going on. + */ + + if (!BufferIsValid(so->btso_curbuf)) + so->btso_curbuf = _bt_getbuf(rel, ItemPointerGetBlockNumber(current), + BT_READ); + + /* we still have the buffer pinned and locked */ + buf = so->btso_curbuf; + blkno = BufferGetBlockNumber(buf); + + /* step one tuple in the appropriate direction */ + if (!_bt_step(scan, &buf, dir)) + return ((RetrieveIndexResult) NULL); + + /* by here, current is the tuple we want to return */ + offnum = ItemPointerGetOffsetNumber(current); + page = BufferGetPage(buf); + btitem = (BTItem) PageGetItem(page, PageGetItemId(page, offnum)); + itup = &btitem->bti_itup; + + if (_bt_checkqual(scan, itup)) { + iptr = (ItemPointer) palloc(sizeof(ItemPointerData)); + memmove((char *) iptr, (char *) &(itup->t_tid), + sizeof(ItemPointerData)); + res = FormRetrieveIndexResult(current, iptr); + + /* remember which buffer we have pinned and locked */ + so->btso_curbuf = buf; + } else { + ItemPointerSetInvalid(current); + so->btso_curbuf = InvalidBuffer; + _bt_relbuf(rel, buf, BT_READ); + res = (RetrieveIndexResult) NULL; + } + + return (res); +} + +/* + * _bt_first() -- Find the first item in a scan. + * + * We need to be clever about the type of scan, the operation it's + * performing, and the tree ordering. We return the RetrieveIndexResult + * of the first item in the tree that satisfies the qualification + * associated with the scan descriptor. On exit, the page containing + * the current index tuple is read locked and pinned, and the scan's + * opaque data entry is updated to include the buffer. + */ +RetrieveIndexResult +_bt_first(IndexScanDesc scan, ScanDirection dir) +{ + Relation rel; + TupleDesc itupdesc; + Buffer buf; + Page page; + BTStack stack; + OffsetNumber offnum, maxoff; + BTItem btitem; + IndexTuple itup; + ItemPointer current; + ItemPointer iptr; + BlockNumber blkno; + StrategyNumber strat; + RetrieveIndexResult res; + RegProcedure proc; + int result; + BTScanOpaque so; + ScanKeyData skdata; + + /* if we just need to walk down one edge of the tree, do that */ + if (scan->scanFromEnd) + return (_bt_endpoint(scan, dir)); + + rel = scan->relation; + itupdesc = RelationGetTupleDescriptor(scan->relation); + current = &(scan->currentItemData); + so = (BTScanOpaque) scan->opaque; + + /* + * Okay, we want something more complicated. What we'll do is use + * the first item in the scan key passed in (which has been correctly + * ordered to take advantage of index ordering) to position ourselves + * at the right place in the scan. + */ + + /* + * XXX -- The attribute number stored in the scan key is the attno + * in the heap relation. We need to transmogrify this into + * the index relation attno here. For the moment, we have + * hardwired attno == 1. + */ + proc = index_getprocid(rel, 1, BTORDER_PROC); + ScanKeyEntryInitialize(&skdata, 0x0, 1, proc, + scan->keyData[0].sk_argument); + + stack = _bt_search(rel, 1, &skdata, &buf); + _bt_freestack(stack); + + /* find the nearest match to the manufactured scan key on the page */ + offnum = _bt_binsrch(rel, buf, 1, &skdata, BT_DESCENT); + page = BufferGetPage(buf); + + /* + * This will happen if the tree we're searching is entirely empty, + * or if we're doing a search for a key that would appear on an + * entirely empty internal page. In either case, there are no + * matching tuples in the index. + */ + + if (PageIsEmpty(page)) { + ItemPointerSetInvalid(current); + so->btso_curbuf = InvalidBuffer; + _bt_relbuf(rel, buf, BT_READ); + return ((RetrieveIndexResult) NULL); + } + + maxoff = PageGetMaxOffsetNumber(page); + + if (offnum > maxoff) + offnum = maxoff; + + blkno = BufferGetBlockNumber(buf); + ItemPointerSet(current, blkno, offnum); + + /* + * Now find the right place to start the scan. Result is the + * value we're looking for minus the value we're looking at + * in the index. + */ + + result = _bt_compare(rel, itupdesc, page, 1, &skdata, offnum); + strat = _bt_getstrat(rel, 1, scan->keyData[0].sk_procedure); + + switch (strat) { + case BTLessStrategyNumber: + if (result <= 0) { + do { + if (!_bt_twostep(scan, &buf, BackwardScanDirection)) + break; + + offnum = ItemPointerGetOffsetNumber(current); + page = BufferGetPage(buf); + result = _bt_compare(rel, itupdesc, page, 1, &skdata, offnum); + } while (result <= 0); + + /* if this is true, the key we just looked at is gone */ + if (result > 0) + (void) _bt_twostep(scan, &buf, ForwardScanDirection); + } + break; + + case BTLessEqualStrategyNumber: + if (result >= 0) { + do { + if (!_bt_twostep(scan, &buf, ForwardScanDirection)) + break; + + offnum = ItemPointerGetOffsetNumber(current); + page = BufferGetPage(buf); + result = _bt_compare(rel, itupdesc, page, 1, &skdata, offnum); + } while (result >= 0); + + if (result < 0) + (void) _bt_twostep(scan, &buf, BackwardScanDirection); + } + break; + + case BTEqualStrategyNumber: + if (result != 0) { + _bt_relbuf(scan->relation, buf, BT_READ); + so->btso_curbuf = InvalidBuffer; + ItemPointerSetInvalid(&(scan->currentItemData)); + return ((RetrieveIndexResult) NULL); + } + break; + + case BTGreaterEqualStrategyNumber: + if (result < 0) { + do { + if (!_bt_twostep(scan, &buf, BackwardScanDirection)) + break; + + page = BufferGetPage(buf); + offnum = ItemPointerGetOffsetNumber(current); + result = _bt_compare(rel, itupdesc, page, 1, &skdata, offnum); + } while (result < 0); + + if (result > 0) + (void) _bt_twostep(scan, &buf, ForwardScanDirection); + } + break; + + case BTGreaterStrategyNumber: + if (result >= 0) { + do { + if (!_bt_twostep(scan, &buf, ForwardScanDirection)) + break; + + offnum = ItemPointerGetOffsetNumber(current); + page = BufferGetPage(buf); + result = _bt_compare(rel, itupdesc, page, 1, &skdata, offnum); + } while (result >= 0); + } + break; + } + + /* okay, current item pointer for the scan is right */ + offnum = ItemPointerGetOffsetNumber(current); + page = BufferGetPage(buf); + btitem = (BTItem) PageGetItem(page, PageGetItemId(page, offnum)); + itup = &btitem->bti_itup; + + if (_bt_checkqual(scan, itup)) { + iptr = (ItemPointer) palloc(sizeof(ItemPointerData)); + memmove((char *) iptr, (char *) &(itup->t_tid), + sizeof(ItemPointerData)); + res = FormRetrieveIndexResult(current, iptr); + pfree(iptr); + + /* remember which buffer we have pinned */ + so->btso_curbuf = buf; + } else { + ItemPointerSetInvalid(current); + so->btso_curbuf = InvalidBuffer; + _bt_relbuf(rel, buf, BT_READ); + res = (RetrieveIndexResult) NULL; + } + + return (res); +} + +/* + * _bt_step() -- Step one item in the requested direction in a scan on + * the tree. + * + * If no adjacent record exists in the requested direction, return + * false. Else, return true and set the currentItemData for the + * scan to the right thing. + */ +bool +_bt_step(IndexScanDesc scan, Buffer *bufP, ScanDirection dir) +{ + Page page; + BTPageOpaque opaque; + OffsetNumber offnum, maxoff; + OffsetNumber start; + BlockNumber blkno; + BlockNumber obknum; + BTScanOpaque so; + ItemPointer current; + Relation rel; + + rel = scan->relation; + current = &(scan->currentItemData); + offnum = ItemPointerGetOffsetNumber(current); + page = BufferGetPage(*bufP); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + so = (BTScanOpaque) scan->opaque; + maxoff = PageGetMaxOffsetNumber(page); + + /* get the next tuple */ + if (ScanDirectionIsForward(dir)) { + if (!PageIsEmpty(page) && offnum < maxoff) { + offnum = OffsetNumberNext(offnum); + } else { + + /* if we're at end of scan, release the buffer and return */ + blkno = opaque->btpo_next; + if (P_RIGHTMOST(opaque)) { + _bt_relbuf(rel, *bufP, BT_READ); + ItemPointerSetInvalid(current); + *bufP = so->btso_curbuf = InvalidBuffer; + return (false); + } else { + + /* walk right to the next page with data */ + _bt_relbuf(rel, *bufP, BT_READ); + for (;;) { + *bufP = _bt_getbuf(rel, blkno, BT_READ); + page = BufferGetPage(*bufP); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + maxoff = PageGetMaxOffsetNumber(page); + start = P_RIGHTMOST(opaque) ? P_HIKEY : P_FIRSTKEY; + + if (!PageIsEmpty(page) && start <= maxoff) { + break; + } else { + blkno = opaque->btpo_next; + _bt_relbuf(rel, *bufP, BT_READ); + if (blkno == P_NONE) { + *bufP = so->btso_curbuf = InvalidBuffer; + ItemPointerSetInvalid(current); + return (false); + } + } + } + offnum = start; + } + } + } else if (ScanDirectionIsBackward(dir)) { + + /* remember that high key is item zero on non-rightmost pages */ + start = P_RIGHTMOST(opaque) ? P_HIKEY : P_FIRSTKEY; + + if (offnum > start) { + offnum = OffsetNumberPrev(offnum); + } else { + + /* if we're at end of scan, release the buffer and return */ + blkno = opaque->btpo_prev; + if (P_LEFTMOST(opaque)) { + _bt_relbuf(rel, *bufP, BT_READ); + *bufP = so->btso_curbuf = InvalidBuffer; + ItemPointerSetInvalid(current); + return (false); + } else { + + obknum = BufferGetBlockNumber(*bufP); + + /* walk right to the next page with data */ + _bt_relbuf(rel, *bufP, BT_READ); + for (;;) { + *bufP = _bt_getbuf(rel, blkno, BT_READ); + page = BufferGetPage(*bufP); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + maxoff = PageGetMaxOffsetNumber(page); + + /* + * If the adjacent page just split, then we may have the + * wrong block. Handle this case. Because pages only + * split right, we don't have to worry about this failing + * to terminate. + */ + + while (opaque->btpo_next != obknum) { + blkno = opaque->btpo_next; + _bt_relbuf(rel, *bufP, BT_READ); + *bufP = _bt_getbuf(rel, blkno, BT_READ); + page = BufferGetPage(*bufP); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + maxoff = PageGetMaxOffsetNumber(page); + } + + /* don't consider the high key */ + start = P_RIGHTMOST(opaque) ? P_HIKEY : P_FIRSTKEY; + + /* anything to look at here? */ + if (!PageIsEmpty(page) && maxoff >= start) { + break; + } else { + blkno = opaque->btpo_prev; + obknum = BufferGetBlockNumber(*bufP); + _bt_relbuf(rel, *bufP, BT_READ); + if (blkno == P_NONE) { + *bufP = so->btso_curbuf = InvalidBuffer; + ItemPointerSetInvalid(current); + return (false); + } + } + } + offnum = maxoff; /* XXX PageIsEmpty? */ + } + } + } + blkno = BufferGetBlockNumber(*bufP); + so->btso_curbuf = *bufP; + ItemPointerSet(current, blkno, offnum); + + return (true); +} + +/* + * _bt_twostep() -- Move to an adjacent record in a scan on the tree, + * if an adjacent record exists. + * + * This is like _bt_step, except that if no adjacent record exists + * it restores us to where we were before trying the step. This is + * only hairy when you cross page boundaries, since the page you cross + * from could have records inserted or deleted, or could even split. + * This is unlikely, but we try to handle it correctly here anyway. + * + * This routine contains the only case in which our changes to Lehman + * and Yao's algorithm. + * + * Like step, this routine leaves the scan's currentItemData in the + * proper state and acquires a lock and pin on *bufP. If the twostep + * succeeded, we return true; otherwise, we return false. + */ +static bool +_bt_twostep(IndexScanDesc scan, Buffer *bufP, ScanDirection dir) +{ + Page page; + BTPageOpaque opaque; + OffsetNumber offnum, maxoff; + OffsetNumber start; + ItemPointer current; + ItemId itemid; + int itemsz; + BTItem btitem; + BTItem svitem; + BlockNumber blkno; + + blkno = BufferGetBlockNumber(*bufP); + page = BufferGetPage(*bufP); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + maxoff = PageGetMaxOffsetNumber(page); + current = &(scan->currentItemData); + offnum = ItemPointerGetOffsetNumber(current); + + start = P_RIGHTMOST(opaque) ? P_HIKEY : P_FIRSTKEY; + + /* if we're safe, just do it */ + if (ScanDirectionIsForward(dir) && offnum < maxoff) { /* XXX PageIsEmpty? */ + ItemPointerSet(current, blkno, OffsetNumberNext(offnum)); + return (true); + } else if (ScanDirectionIsBackward(dir) && offnum > start) { + ItemPointerSet(current, blkno, OffsetNumberPrev(offnum)); + return (true); + } + + /* if we've hit end of scan we don't have to do any work */ + if (ScanDirectionIsForward(dir) && P_RIGHTMOST(opaque)) { + return (false); + } else if (ScanDirectionIsBackward(dir) && P_LEFTMOST(opaque)) { + return (false); + } + + /* + * Okay, it's off the page; let _bt_step() do the hard work, and we'll + * try to remember where we were. This is not guaranteed to work; this + * is the only place in the code where concurrency can screw us up, + * and it's because we want to be able to move in two directions in + * the scan. + */ + + itemid = PageGetItemId(page, offnum); + itemsz = ItemIdGetLength(itemid); + btitem = (BTItem) PageGetItem(page, itemid); + svitem = (BTItem) palloc(itemsz); + memmove((char *) svitem, (char *) btitem, itemsz); + + if (_bt_step(scan, bufP, dir)) { + pfree(svitem); + return (true); + } + + /* try to find our place again */ + *bufP = _bt_getbuf(scan->relation, blkno, BT_READ); + page = BufferGetPage(*bufP); + maxoff = PageGetMaxOffsetNumber(page); + + while (offnum <= maxoff) { + itemid = PageGetItemId(page, offnum); + btitem = (BTItem) PageGetItem(page, itemid); + if (btitem->bti_oid == svitem->bti_oid) { + pfree(svitem); + ItemPointerSet(current, blkno, offnum); + return (false); + } + } + + /* + * XXX crash and burn -- can't find our place. We can be a little + * smarter -- walk to the next page to the right, for example, since + * that's the only direction that splits happen in. Deletions screw + * us up less often since they're only done by the vacuum daemon. + */ + + elog(WARN, "btree synchronization error: concurrent update botched scan"); + + return (false); +} + +/* + * _bt_endpoint() -- Find the first or last key in the index. + */ +static RetrieveIndexResult +_bt_endpoint(IndexScanDesc scan, ScanDirection dir) +{ + Relation rel; + Buffer buf; + Page page; + BTPageOpaque opaque; + ItemPointer current; + ItemPointer iptr; + OffsetNumber offnum, maxoff; + OffsetNumber start; + BlockNumber blkno; + BTItem btitem; + IndexTuple itup; + BTScanOpaque so; + RetrieveIndexResult res; + + rel = scan->relation; + current = &(scan->currentItemData); + + buf = _bt_getroot(rel, BT_READ); + blkno = BufferGetBlockNumber(buf); + page = BufferGetPage(buf); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + + for (;;) { + if (opaque->btpo_flags & BTP_LEAF) + break; + + if (ScanDirectionIsForward(dir)) { + offnum = P_RIGHTMOST(opaque) ? P_HIKEY : P_FIRSTKEY; + } else { + offnum = PageGetMaxOffsetNumber(page); + } + + btitem = (BTItem) PageGetItem(page, PageGetItemId(page, offnum)); + itup = &(btitem->bti_itup); + + blkno = ItemPointerGetBlockNumber(&(itup->t_tid)); + + _bt_relbuf(rel, buf, BT_READ); + buf = _bt_getbuf(rel, blkno, BT_READ); + page = BufferGetPage(buf); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + + /* + * Race condition: If the child page we just stepped onto is + * in the process of being split, we need to make sure we're + * all the way at the right edge of the tree. See the paper + * by Lehman and Yao. + */ + + if (ScanDirectionIsBackward(dir) && ! P_RIGHTMOST(opaque)) { + do { + blkno = opaque->btpo_next; + _bt_relbuf(rel, buf, BT_READ); + buf = _bt_getbuf(rel, blkno, BT_READ); + page = BufferGetPage(buf); + opaque = (BTPageOpaque) PageGetSpecialPointer(page); + } while (! P_RIGHTMOST(opaque)); + } + } + + /* okay, we've got the {left,right}-most page in the tree */ + maxoff = PageGetMaxOffsetNumber(page); + + if (ScanDirectionIsForward(dir)) { + if (PageIsEmpty(page)) { + maxoff = FirstOffsetNumber; + } else { + maxoff = PageGetMaxOffsetNumber(page); + } + start = P_RIGHTMOST(opaque) ? P_HIKEY : P_FIRSTKEY; + + if (PageIsEmpty(page) || start > maxoff) { + ItemPointerSet(current, blkno, maxoff); + if (!_bt_step(scan, &buf, BackwardScanDirection)) + return ((RetrieveIndexResult) NULL); + + start = ItemPointerGetOffsetNumber(current); + page = BufferGetPage(buf); + } else { + ItemPointerSet(current, blkno, start); + } + } else if (ScanDirectionIsBackward(dir)) { + if (PageIsEmpty(page)) { + ItemPointerSet(current, blkno, FirstOffsetNumber); + if (!_bt_step(scan, &buf, ForwardScanDirection)) + return ((RetrieveIndexResult) NULL); + + start = ItemPointerGetOffsetNumber(current); + page = BufferGetPage(buf); + } else { + start = PageGetMaxOffsetNumber(page); + ItemPointerSet(current, blkno, start); + } + } else { + elog(WARN, "Illegal scan direction %d", dir); + } + + btitem = (BTItem) PageGetItem(page, PageGetItemId(page, start)); + itup = &(btitem->bti_itup); + + /* see if we picked a winner */ + if (_bt_checkqual(scan, itup)) { + iptr = (ItemPointer) palloc(sizeof(ItemPointerData)); + memmove((char *) iptr, (char *) &(itup->t_tid), + sizeof(ItemPointerData)); + res = FormRetrieveIndexResult(current, iptr); + + /* remember which buffer we have pinned */ + so = (BTScanOpaque) scan->opaque; + so->btso_curbuf = buf; + } else { + _bt_relbuf(rel, buf, BT_READ); + res = (RetrieveIndexResult) NULL; + } + + return (res); +} diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c new file mode 100644 index 00000000000..3d2676324a0 --- /dev/null +++ b/src/backend/access/nbtree/nbtsort.c @@ -0,0 +1,1196 @@ +/*------------------------------------------------------------------------- + * btsort.c-- + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Id: nbtsort.c,v 1.1.1.1 1996/07/09 06:21:12 scrappy Exp $ + * + * NOTES + * + * what we do is: + * - generate a set of initial one-block runs, distributed round-robin + * between the output tapes. + * - for each pass, + * - swap input and output tape sets, rewinding both and truncating + * the output tapes. + * - merge the current run in each input tape to the current output + * tape. + * - when each input run has been exhausted, switch to another output + * tape and start processing another run. + * - when we have fewer runs than tapes, we know we are ready to start + * merging into the btree leaf pages. + * - every time we complete a level of the btree, we can construct the + * next level up. when we have only one page on a level, it can be + * attached to the btree metapage and we are done. + * + * conventions: + * - external interface routines take in and return "void *" for their + * opaque handles. this is for modularity reasons (i prefer not to + * export these structures without good reason). + * + * this code is moderately slow (~10% slower) compared to the regular + * btree (insertion) build code on sorted or well-clustered data. on + * random data, however, the insertion build code is unusable -- the + * difference on a 60MB heap is a factor of 15 because the random + * probes into the btree thrash the buffer pool. + * + * this code currently packs the pages to 100% of capacity. this is + * not wise, since *any* insertion will cause splitting. filling to + * something like the standard 70% steady-state load factor for btrees + * would probably be better. + * + * somebody desperately needs to figure out how to do a better job of + * balancing the merge passes -- the fan-in on the final merges can be + * pretty poor, which is bad for performance. + *------------------------------------------------------------------------- + */ + +#include <stdio.h> + +#include "c.h" + +#include "access/nbtree.h" + +#include "storage/bufmgr.h" +#include "storage/fd.h" +#include "utils/rel.h" +#include "utils/palloc.h" +#include "utils/elog.h" + +/*#define FASTBUILD_DEBUG*/ /* turn on debugging output */ + +#define FASTBUILD + +#ifdef FASTBUILD + +#define MAXTAPES (7) +#define TAPEBLCKSZ (BLCKSZ << 2) +#define TAPETEMP "pg_btsortXXXXXX" + + +/*------------------------------------------------------------------------- + * sorting comparison routine - returns {-1,0,1} depending on whether + * the key in the left BTItem is {<,=,>} the key in the right BTItem. + * + * we want to use _bt_isortcmp as a comparison function for qsort(3), + * but it needs extra arguments, so we "pass them in" as global + * variables. ick. fortunately, they are the same throughout the + * build, so we need do this only once. this is why you must call + * _bt_isortcmpinit before the call to qsort(3). + * + * a NULL BTItem is always assumed to be greater than any actual + * value; our heap routines (see below) assume that the smallest + * element in the heap is returned. that way, NULL values from the + * exhausted tapes can sift down to the bottom of the heap. in point + * of fact we just don't replace the elements of exhausted tapes, but + * what the heck. + * *------------------------------------------------------------------------- + */ +static Relation _bt_sortrel; + +static void +_bt_isortcmpinit(Relation index) +{ + _bt_sortrel = index; +} + +static int +_bt_isortcmp(BTItem *bti1p, BTItem *bti2p) +{ + BTItem bti1 = *bti1p; + BTItem bti2 = *bti2p; + + if (bti1 == (BTItem) NULL) { + if (bti2 == (BTItem) NULL) { + return(0); /* 1 = 2 */ + } + return(1); /* 1 > 2 */ + } else if (bti2 == (BTItem) NULL) { + return(-1); /* 1 < 2 */ + } else if (_bt_itemcmp(_bt_sortrel, 1, bti1, bti2, + BTGreaterStrategyNumber)) { + return(1); /* 1 > 2 */ + } else if (_bt_itemcmp(_bt_sortrel, 1, bti2, bti1, + BTGreaterStrategyNumber)) { + return(-1); /* 1 < 2 */ + } + return(0); /* 1 = 2 */ +} + +/*------------------------------------------------------------------------- + * priority queue methods + * + * these were more-or-less lifted from the heap section of the 1984 + * edition of gonnet's book on algorithms and data structures. they + * are coded so that the smallest element in the heap is returned (we + * use them for merging sorted runs). + * + * XXX these probably ought to be generic library functions. + *------------------------------------------------------------------------- + */ + +typedef struct { + int btpqe_tape; /* tape identifier */ + BTItem btpqe_item; /* pointer to BTItem in tape buffer */ +} BTPriQueueElem; + +#define MAXELEM MAXTAPES +typedef struct { + int btpq_nelem; + BTPriQueueElem btpq_queue[MAXELEM]; + Relation btpq_rel; +} BTPriQueue; + +/* be sure to call _bt_isortcmpinit first */ +#define GREATER(a, b) \ + (_bt_isortcmp(&((a)->btpqe_item), &((b)->btpqe_item)) > 0) + +static void +_bt_pqsift(BTPriQueue *q, int parent) +{ + int child; + BTPriQueueElem e; + + for (child = parent * 2 + 1; + child < q->btpq_nelem; + child = parent * 2 + 1) { + if (child < q->btpq_nelem - 1) { + if (GREATER(&(q->btpq_queue[child]), &(q->btpq_queue[child+1]))) { + ++child; + } + } + if (GREATER(&(q->btpq_queue[parent]), &(q->btpq_queue[child]))) { + e = q->btpq_queue[child]; /* struct = */ + q->btpq_queue[child] = q->btpq_queue[parent]; /* struct = */ + q->btpq_queue[parent] = e; /* struct = */ + parent = child; + } else { + parent = child + 1; + } + } +} + +static int +_bt_pqnext(BTPriQueue *q, BTPriQueueElem *e) +{ + if (q->btpq_nelem < 1) { /* already empty */ + return(-1); + } + *e = q->btpq_queue[0]; /* struct = */ + + if (--q->btpq_nelem < 1) { /* now empty, don't sift */ + return(0); + } + q->btpq_queue[0] = q->btpq_queue[q->btpq_nelem]; /* struct = */ + _bt_pqsift(q, 0); + return(0); +} + +static void +_bt_pqadd(BTPriQueue *q, BTPriQueueElem *e) +{ + int child, parent; + + if (q->btpq_nelem >= MAXELEM) { + elog(WARN, "_bt_pqadd: queue overflow"); + } + + child = q->btpq_nelem++; + while (child > 0) { + parent = child / 2; + if (GREATER(e, &(q->btpq_queue[parent]))) { + break; + } else { + q->btpq_queue[child] = q->btpq_queue[parent]; /* struct = */ + child = parent; + } + } + + q->btpq_queue[child] = *e; /* struct = */ +} + +/*------------------------------------------------------------------------- + * tape methods + *------------------------------------------------------------------------- + */ + +#define BTITEMSZ(btitem) \ + ((btitem) ? \ + (IndexTupleDSize((btitem)->bti_itup) + \ + (sizeof(BTItemData) - sizeof(IndexTupleData))) : \ + 0) +#define SPCLEFT(tape) \ + (sizeof((tape)->bttb_data) - (tape)->bttb_top) +#define EMPTYTAPE(tape) \ + ((tape)->bttb_ntup <= 0) +#define BTTAPEMAGIC 0x19660226 + +/* + * this is what we use to shovel BTItems in and out of memory. it's + * bigger than a standard block because we are doing a lot of strictly + * sequential i/o. this is obviously something of a tradeoff since we + * are potentially reading a bunch of zeroes off of disk in many + * cases. + * + * BTItems are packed in and DOUBLEALIGN'd. + * + * the fd should not be going out to disk, strictly speaking, but it's + * the only thing like that so i'm not going to worry about wasting a + * few bytes. + */ +typedef struct { + int bttb_magic; /* magic number */ + int bttb_fd; /* file descriptor */ + int bttb_top; /* top of free space within bttb_data */ + short bttb_ntup; /* number of tuples in this block */ + short bttb_eor; /* End-Of-Run marker */ + char bttb_data[TAPEBLCKSZ - 2 * sizeof(double)]; +} BTTapeBlock; + + +/* + * reset the tape header for its next use without doing anything to + * the physical tape file. (setting bttb_top to 0 makes the block + * empty.) + */ +static void +_bt_tapereset(BTTapeBlock *tape) +{ + tape->bttb_eor = 0; + tape->bttb_top = 0; + tape->bttb_ntup = 0; +} + +/* + * rewind the physical tape file. + */ +static void +_bt_taperewind(BTTapeBlock *tape) +{ + (void) FileSeek(tape->bttb_fd, 0, SEEK_SET); +} + +/* + * destroy the contents of the physical tape file without destroying + * the tape data structure or removing the physical tape file. + * + * we use the VFD version of ftruncate(2) to do this rather than + * unlinking and recreating the file. you still have to wait while + * the OS frees up all of the file system blocks and stuff, but at + * least you don't have to delete and reinsert the directory entries. + */ +static void +_bt_tapeclear(BTTapeBlock *tape) +{ + /* blow away the contents of the old file */ + _bt_taperewind(tape); +#if 0 + FileSync(tape->bttb_fd); +#endif + FileTruncate(tape->bttb_fd, 0); + + /* reset the buffer */ + _bt_tapereset(tape); +} + +/* + * create a new BTTapeBlock, allocating memory for the data structure + * as well as opening a physical tape file. + */ +static BTTapeBlock * +_bt_tapecreate(char *fname) +{ + BTTapeBlock *tape = (BTTapeBlock *) palloc(sizeof(BTTapeBlock)); + + if (tape == (BTTapeBlock *) NULL) { + elog(WARN, "_bt_tapecreate: out of memory"); + } + + tape->bttb_magic = BTTAPEMAGIC; + + tape->bttb_fd = FileNameOpenFile(fname, O_RDWR|O_CREAT|O_TRUNC, 0600); + Assert(tape->bttb_fd >= 0); + + /* initialize the buffer */ + _bt_tapereset(tape); + + return(tape); +} + +/* + * destroy the BTTapeBlock structure and its physical tape file. + */ +static void +_bt_tapedestroy(BTTapeBlock *tape) +{ + FileUnlink(tape->bttb_fd); + pfree((void *) tape); +} + +/* + * flush the tape block to the file, marking End-Of-Run if requested. + */ +static void +_bt_tapewrite(BTTapeBlock *tape, int eor) +{ + tape->bttb_eor = eor; + FileWrite(tape->bttb_fd, (char*)tape, TAPEBLCKSZ); + _bt_tapereset(tape); +} + +/* + * read a tape block from the file, overwriting the current contents + * of the buffer. + * + * returns: + * - 0 if there are no more blocks in the tape or in this run (call + * _bt_tapereset to clear the End-Of-Run marker) + * - 1 if a valid block was read + */ +static int +_bt_taperead(BTTapeBlock *tape) +{ + int fd; + int nread; + + if (tape->bttb_eor) { + return(0); /* we are at End-Of-Run */ + } + + /* + * we're clobbering the old tape block, but we do need to save the + * VFD (the one in the block we're reading is bogus). + */ + fd = tape->bttb_fd; + nread = FileRead(fd, (char*) tape, TAPEBLCKSZ); + tape->bttb_fd = fd; + + if (nread != TAPEBLCKSZ) { + Assert(nread == 0); /* we are at EOF */ + return(0); + } + Assert(tape->bttb_magic == BTTAPEMAGIC); + return(1); +} + +/* + * get the next BTItem from a tape block. + * + * returns: + * - NULL if we have run out of BTItems + * - a pointer to the BTItemData in the block otherwise + * + * side effects: + * - sets 'pos' to the current position within the block. + */ +static BTItem +_bt_tapenext(BTTapeBlock *tape, char **pos) +{ + Size itemsz; + BTItem bti; + + if (*pos >= tape->bttb_data + tape->bttb_top) { + return((BTItem) NULL); + } + bti = (BTItem) *pos; + itemsz = BTITEMSZ(bti); + *pos += DOUBLEALIGN(itemsz); + return(bti); +} + +/* + * copy a BTItem into a tape block. + * + * assumes that we have already checked to see if the block has enough + * space for the item. + * + * side effects: + * + * - advances the 'top' pointer in the tape block header to point to + * the beginning of free space. + */ +static void +_bt_tapeadd(BTTapeBlock *tape, BTItem item, int itemsz) +{ + (void) memcpy(tape->bttb_data + tape->bttb_top, item, itemsz); + ++tape->bttb_ntup; + tape->bttb_top += DOUBLEALIGN(itemsz); +} + +/*------------------------------------------------------------------------- + * spool methods + *------------------------------------------------------------------------- + */ + +/* + * this structure holds the bookkeeping for a simple balanced multiway + * merge. (polyphase merging is hairier than i want to get into right + * now, and i don't see why i have to care how many "tapes" i use + * right now. though if psort was in a condition that i could hack it + * to do this, you bet i would.) + */ +typedef struct { + int bts_ntapes; + int bts_tape; + BTTapeBlock **bts_itape; /* input tape blocks */ + BTTapeBlock **bts_otape; /* output tape blocks */ +} BTSpool; + +/* + * create and initialize a spool structure, including the underlying + * files. + */ +void * +_bt_spoolinit(Relation index, int ntapes) +{ + char *mktemp(); + + BTSpool *btspool = (BTSpool *) palloc(sizeof(BTSpool)); + int i; + char *fname = (char *) palloc(sizeof(TAPETEMP) + 1); + + if (btspool == (BTSpool *) NULL || fname == (char *) NULL) { + elog(WARN, "_bt_spoolinit: out of memory"); + } + (void) memset((char *) btspool, 0, sizeof(BTSpool)); + btspool->bts_ntapes = ntapes; + btspool->bts_tape = 0; + + btspool->bts_itape = + (BTTapeBlock **) palloc(sizeof(BTTapeBlock *) * ntapes); + btspool->bts_otape = + (BTTapeBlock **) palloc(sizeof(BTTapeBlock *) * ntapes); + if (btspool->bts_itape == (BTTapeBlock **) NULL || + btspool->bts_otape == (BTTapeBlock **) NULL) { + elog(WARN, "_bt_spoolinit: out of memory"); + } + + for (i = 0; i < ntapes; ++i) { + btspool->bts_itape[i] = + _bt_tapecreate(mktemp(strcpy(fname, TAPETEMP))); + btspool->bts_otape[i] = + _bt_tapecreate(mktemp(strcpy(fname, TAPETEMP))); + } + pfree((void *) fname); + + _bt_isortcmpinit(index); + + return((void *) btspool); +} + +/* + * clean up a spool structure and its substructures. + */ +void +_bt_spooldestroy(void *spool) +{ + BTSpool *btspool = (BTSpool *) spool; + int i; + + for (i = 0; i < btspool->bts_ntapes; ++i) { + _bt_tapedestroy(btspool->bts_otape[i]); + _bt_tapedestroy(btspool->bts_itape[i]); + } + pfree((void *) btspool); +} + +/* + * flush out any dirty output tape blocks + */ +static void +_bt_spoolflush(BTSpool *btspool) +{ + int i; + + for (i = 0; i < btspool->bts_ntapes; ++i) { + if (!EMPTYTAPE(btspool->bts_otape[i])) { + _bt_tapewrite(btspool->bts_otape[i], 1); + } + } +} + +/* + * swap input tapes and output tapes by swapping their file + * descriptors. additional preparation for the next merge pass + * includes rewinding the new input tapes and clearing out the new + * output tapes. + */ +static void +_bt_spoolswap(BTSpool *btspool) +{ + File tmpfd; + BTTapeBlock *itape; + BTTapeBlock *otape; + int i; + + for (i = 0; i < btspool->bts_ntapes; ++i) { + itape = btspool->bts_itape[i]; + otape = btspool->bts_otape[i]; + + /* + * swap the input and output VFDs. + */ + tmpfd = itape->bttb_fd; + itape->bttb_fd = otape->bttb_fd; + otape->bttb_fd = tmpfd; + + /* + * rewind the new input tape. + */ + _bt_taperewind(itape); + _bt_tapereset(itape); + + /* + * clear the new output tape -- it's ok to throw away the old + * inputs. + */ + _bt_tapeclear(otape); + } +} + +/*------------------------------------------------------------------------- + * sorting routines + *------------------------------------------------------------------------- + */ + +/* + * spool 'btitem' into an initial run. as tape blocks are filled, the + * block BTItems are qsorted and written into some output tape (it + * doesn't matter which; we go round-robin for simplicity). the + * initial runs are therefore always just one block. + */ +void +_bt_spool(Relation index, BTItem btitem, void *spool) +{ + BTSpool *btspool = (BTSpool *) spool; + BTTapeBlock *itape; + Size itemsz; + + itape = btspool->bts_itape[btspool->bts_tape]; + itemsz = BTITEMSZ(btitem); + itemsz = DOUBLEALIGN(itemsz); + + /* + * if this buffer is too full for this BTItemData, or if we have + * run out of BTItems, we need to sort the buffer and write it + * out. in this case, the BTItemData will go into the next tape's + * buffer. + */ + if (btitem == (BTItem) NULL || SPCLEFT(itape) < itemsz) { + BTItem *parray; + BTTapeBlock *otape; + BTItem bti; + char *pos; + int btisz; + int i; + + /* + * build an array of pointers to the BTItemDatas on the input + * block. + */ + parray = (BTItem *) palloc(itape->bttb_ntup * sizeof(BTItem)); + if (parray == (BTItem *) NULL) { + elog(WARN, "_bt_spool: out of memory"); + } + pos = itape->bttb_data; + for (i = 0; i < itape->bttb_ntup; ++i) { + parray[i] = _bt_tapenext(itape, &pos); + } + + /* + * qsort the pointer array. + */ + _bt_isortcmpinit(index); + qsort((void *) parray, itape->bttb_ntup, sizeof(BTItem), _bt_isortcmp); + + /* + * write the spooled run into the output tape. we copy the + * BTItemDatas in the order dictated by the sorted array of + * BTItems, not the original order. + * + * (since everything was DOUBLEALIGN'd and is all on a single + * page, everything had *better* still fit on one page..) + */ + otape = btspool->bts_otape[btspool->bts_tape]; + for (i = 0; i < itape->bttb_ntup; ++i) { + bti = parray[i]; + btisz = BTITEMSZ(bti); + btisz = DOUBLEALIGN(btisz); + _bt_tapeadd(otape, bti, btisz); +#ifdef FASTBUILD_DEBUG + { + bool isnull; + Datum d = index_getattr(&(bti->bti_itup), 1, + RelationGetTupleDescriptor(index), + &isnull); + printf("_bt_spool: inserted <%x> into output tape %d\n", + d, btspool->bts_tape); + } +#endif /* FASTBUILD_DEBUG */ + } + + /* + * the initial runs are always single tape blocks. flush the + * output block, marking End-Of-Run. + */ + _bt_tapewrite(otape, 1); + + /* + * reset the input buffer for the next run. we don't have to + * write it out or anything -- we only use it to hold the + * unsorted BTItemDatas, the output tape contains all the + * sorted stuff. + * + * changing bts_tape changes the output tape and input tape; + * we change itape for the code below. + */ + _bt_tapereset(itape); + btspool->bts_tape = (btspool->bts_tape + 1) % btspool->bts_ntapes; + itape = btspool->bts_itape[btspool->bts_tape]; + + /* + * destroy the pointer array. + */ + pfree((void *) parray); + } + + /* insert this item into the current buffer */ + if (btitem != (BTItem) NULL) { + _bt_tapeadd(itape, btitem, itemsz); + } +} + +/* + * allocate a new, clean btree page, not linked to any siblings. + */ +static void +_bt_blnewpage(Relation index, Buffer *buf, Page *page, int flags) +{ + BTPageOpaque opaque; + + *buf = _bt_getbuf(index, P_NEW, BT_WRITE); + *page = BufferGetPage(*buf); + _bt_pageinit(*page, BufferGetPageSize(*buf)); + opaque = (BTPageOpaque) PageGetSpecialPointer(*page); + opaque->btpo_prev = opaque->btpo_next = P_NONE; + opaque->btpo_flags = flags; +} + +/* + * slide an array of ItemIds back one slot (from P_FIRSTKEY to + * P_HIKEY). we need to do this when we discover that we have built + * an ItemId array in what has turned out to be a P_RIGHTMOST page. + */ +static void +_bt_slideleft(Relation index, Buffer buf, Page page) +{ + OffsetNumber off; + OffsetNumber maxoff; + ItemId previi; + ItemId thisii; + + maxoff = PageGetMaxOffsetNumber(page); + previi = PageGetItemId(page, P_HIKEY); + for (off = P_FIRSTKEY; off <= maxoff; off = OffsetNumberNext(off)) { + thisii = PageGetItemId(page, off); + *previi = *thisii; + previi = thisii; + } + ((PageHeader) page)->pd_lower -= sizeof(ItemIdData); +} + +typedef struct { + Buffer btps_buf; + Page btps_page; + BTItem btps_lastbti; + OffsetNumber btps_lastoff; + OffsetNumber btps_firstoff; +} BTPageState; + +/* + * add an item to a disk page from a merge tape block. + * + * we must be careful to observe the following restrictions, placed + * upon us by the conventions in nbtsearch.c: + * - rightmost pages start data items at P_HIKEY instead of at + * P_FIRSTKEY. + * - duplicates cannot be split among pages unless the chain of + * duplicates starts at the first data item. + * + * a leaf page being built looks like: + * + * +----------------+---------------------------------+ + * | PageHeaderData | linp0 linp1 linp2 ... | + * +-----------+----+---------------------------------+ + * | ... linpN | ^ first | + * +-----------+--------------------------------------+ + * | ^ last | + * | | + * | v last | + * +-------------+------------------------------------+ + * | | itemN ... | + * +-------------+------------------+-----------------+ + * | ... item3 item2 item1 | "special space" | + * +--------------------------------+-----------------+ + * ^ first + * + * contrast this with the diagram in bufpage.h; note the mismatch + * between linps and items. this is because we reserve linp0 as a + * placeholder for the pointer to the "high key" item; when we have + * filled up the page, we will set linp0 to point to itemN and clear + * linpN. + * + * 'last' pointers indicate the last offset/item added to the page. + * 'first' pointers indicate the first offset/item that is part of a + * chain of duplicates extending from 'first' to 'last'. + * + * if all keys are unique, 'first' will always be the same as 'last'. + */ +static void +_bt_buildadd(Relation index, BTPageState *state, BTItem bti, int flags) +{ + Buffer nbuf; + Page npage; + BTItem last_bti; + OffsetNumber first_off; + OffsetNumber last_off; + OffsetNumber off; + Size pgspc; + Size btisz; + + nbuf = state->btps_buf; + npage = state->btps_page; + first_off = state->btps_firstoff; + last_off = state->btps_lastoff; + last_bti = state->btps_lastbti; + + pgspc = PageGetFreeSpace(npage); + btisz = BTITEMSZ(bti); + btisz = DOUBLEALIGN(btisz); + if (pgspc < btisz) { + Buffer obuf = nbuf; + Page opage = npage; + OffsetNumber o, n; + ItemId ii; + ItemId hii; + + _bt_blnewpage(index, &nbuf, &npage, flags); + + /* + * if 'last' is part of a chain of duplicates that does not + * start at the beginning of the old page, the entire chain is + * copied to the new page; we delete all of the duplicates + * from the old page except the first, which becomes the high + * key item of the old page. + * + * if the chain starts at the beginning of the page or there + * is no chain ('first' == 'last'), we need only copy 'last' + * to the new page. again, 'first' (== 'last') becomes the + * high key of the old page. + * + * note that in either case, we copy at least one item to the + * new page, so 'last_bti' will always be valid. 'bti' will + * never be the first data item on the new page. + */ + if (first_off == P_FIRSTKEY) { + Assert(last_off != P_FIRSTKEY); + first_off = last_off; + } + for (o = first_off, n = P_FIRSTKEY; + o <= last_off; + o = OffsetNumberNext(o), n = OffsetNumberNext(n)) { + ii = PageGetItemId(opage, o); + (void) PageAddItem(npage, PageGetItem(opage, ii), + ii->lp_len, n, LP_USED); +#ifdef FASTBUILD_DEBUG + { + bool isnull; + BTItem tmpbti = + (BTItem) PageGetItem(npage, PageGetItemId(npage, n)); + Datum d = index_getattr(&(tmpbti->bti_itup), 1, + RelationGetTupleDescriptor(index), + &isnull); + printf("_bt_buildadd: moved <%x> to offset %d\n", + d, n); + } +#endif /* FASTBUILD_DEBUG */ + } + for (o = last_off; o > first_off; o = OffsetNumberPrev(o)) { + PageIndexTupleDelete(opage, o); + } + hii = PageGetItemId(opage, P_HIKEY); + ii = PageGetItemId(opage, first_off); + *hii = *ii; + ii->lp_flags &= ~LP_USED; + ((PageHeader) opage)->pd_lower -= sizeof(ItemIdData); + + first_off = P_FIRSTKEY; + last_off = PageGetMaxOffsetNumber(npage); + last_bti = (BTItem) PageGetItem(npage, PageGetItemId(npage, last_off)); + + /* + * set the page (side link) pointers. + */ + { + BTPageOpaque oopaque = (BTPageOpaque) PageGetSpecialPointer(opage); + BTPageOpaque nopaque = (BTPageOpaque) PageGetSpecialPointer(npage); + + oopaque->btpo_next = BufferGetBlockNumber(nbuf); + nopaque->btpo_prev = BufferGetBlockNumber(obuf); + nopaque->btpo_next = P_NONE; + } + + /* + * write out the old stuff. we never want to see it again, so + * we can give up our lock (if we had one; BuildingBtree is + * set, so we aren't locking). + */ + _bt_wrtbuf(index, obuf); + } + + /* + * if this item is different from the last item added, we start a + * new chain of duplicates. + */ + off = OffsetNumberNext(last_off); + (void) PageAddItem(npage, (Item) bti, btisz, off, LP_USED); +#ifdef FASTBUILD_DEBUG + { + bool isnull; + Datum d = index_getattr(&(bti->bti_itup), 1, + RelationGetTupleDescriptor(index), + &isnull); + printf("_bt_buildadd: inserted <%x> at offset %d\n", + d, off); + } +#endif /* FASTBUILD_DEBUG */ + if (last_bti == (BTItem) NULL) { + first_off = P_FIRSTKEY; + } else if (!_bt_itemcmp(index, 1, bti, last_bti, BTEqualStrategyNumber)) { + first_off = off; + } + last_off = off; + last_bti = (BTItem) PageGetItem(npage, PageGetItemId(npage, off)); + + state->btps_buf = nbuf; + state->btps_page = npage; + state->btps_lastbti = last_bti; + state->btps_lastoff = last_off; + state->btps_firstoff = first_off; +} + +/* + * take the input tapes stored by 'btspool' and perform successive + * merging passes until at most one run is left in each tape. at that + * point, merge the final tape runs into a set of btree leaves. + * + * XXX three nested loops? gross. cut me up into smaller routines. + */ +static BlockNumber +_bt_merge(Relation index, BTSpool *btspool) +{ + BTPageState state; + BlockNumber firstblk; + BTPriQueue q; + BTPriQueueElem e; + BTItem bti; + BTTapeBlock *itape; + BTTapeBlock *otape; + char *tapepos[MAXTAPES]; + int tapedone[MAXTAPES]; + int t; + int goodtapes; + int nruns; + Size btisz; + bool doleaf = false; + + /* + * initialize state needed for the merge into the btree leaf pages. + */ + (void) memset((char *) &state, 0, sizeof(BTPageState)); + _bt_blnewpage(index, &(state.btps_buf), &(state.btps_page), BTP_LEAF); + state.btps_lastoff = P_HIKEY; + state.btps_lastbti = (BTItem) NULL; + firstblk = BufferGetBlockNumber(state.btps_buf); + + do { /* pass */ + /* + * each pass starts by flushing the previous outputs and + * swapping inputs and outputs. this process also clears the + * new output tapes and rewinds the new input tapes. + */ + btspool->bts_tape = btspool->bts_ntapes - 1; + _bt_spoolflush(btspool); + _bt_spoolswap(btspool); + + nruns = 0; + + for (;;) { /* run */ + /* + * each run starts by selecting a new output tape. the + * merged results of a given run are always sent to this + * one tape. + */ + btspool->bts_tape = (btspool->bts_tape + 1) % btspool->bts_ntapes; + otape = btspool->bts_otape[btspool->bts_tape]; + + /* + * initialize the priority queue by loading it with the + * first element of the given run in each tape. since we + * are starting a new run, we reset the tape (clearing the + * End-Of-Run marker) before reading it. this means that + * _bt_taperead will return 0 only if the tape is actually + * at EOF. + */ + (void) memset((char *) &q, 0, sizeof(BTPriQueue)); + goodtapes = 0; + for (t = 0; t < btspool->bts_ntapes; ++t) { + itape = btspool->bts_itape[t]; + tapepos[t] = itape->bttb_data; + _bt_tapereset(itape); + if (_bt_taperead(itape) == 0) { + tapedone[t] = 1; + } else { + ++goodtapes; + tapedone[t] = 0; + e.btpqe_tape = t; + e.btpqe_item = _bt_tapenext(itape, &tapepos[t]); + if (e.btpqe_item != (BTItem) NULL) { + _bt_pqadd(&q, &e); + } + } + } + /* + * if we don't have any tapes with any input (i.e., they + * are all at EOF), we must be done with this pass. + */ + if (goodtapes == 0) { + break; /* for */ + } + ++nruns; + + /* + * output the smallest element from the queue until there are no + * more. + */ + while (_bt_pqnext(&q, &e) >= 0) { /* item */ + /* + * replace the element taken from priority queue, + * fetching a new block if needed. a tape can run out + * if it hits either End-Of-Run or EOF. + */ + t = e.btpqe_tape; + bti = e.btpqe_item; + if (bti != (BTItem) NULL) { + btisz = BTITEMSZ(bti); + btisz = DOUBLEALIGN(btisz); + if (doleaf) { + _bt_buildadd(index, &state, bti, BTP_LEAF); +#ifdef FASTBUILD_DEBUG + { + bool isnull; + Datum d = index_getattr(&(bti->bti_itup), 1, + RelationGetTupleDescriptor(index), + &isnull); + printf("_bt_merge: inserted <%x> into block %d\n", + d, BufferGetBlockNumber(state.btps_buf)); + } +#endif /* FASTBUILD_DEBUG */ + } else { + if (SPCLEFT(otape) < btisz) { + /* + * if it's full, write it out and add the + * item to the next block. (since we know + * there will be at least one more block, + * we know we do *not* want to set + * End-Of-Run here!) + */ + _bt_tapewrite(otape, 0); + } + _bt_tapeadd(otape, bti, btisz); +#ifdef FASTBUILD_DEBUG + { + bool isnull; + Datum d = index_getattr(&(bti->bti_itup), 1, + RelationGetTupleDescriptor(index), &isnull); + printf("_bt_merge: inserted <%x> into tape %d\n", + d, btspool->bts_tape); + } +#endif /* FASTBUILD_DEBUG */ + } + } +#ifdef FASTBUILD_DEBUG + { + bool isnull; + Datum d = index_getattr(&(bti->bti_itup), 1, + RelationGetTupleDescriptor(index), + &isnull); + printf("_bt_merge: got <%x> from tape %d\n", d, t); + } +#endif /* FASTBUILD_DEBUG */ + + itape = btspool->bts_itape[t]; + if (!tapedone[t]) { + BTItem newbti = _bt_tapenext(itape, &tapepos[t]); + + if (newbti == (BTItem) NULL) { + if (_bt_taperead(itape) == 0) { + tapedone[t] = 1; + } else { + tapepos[t] = itape->bttb_data; + newbti = _bt_tapenext(itape, &tapepos[t]); + } + } + if (newbti != (BTItem) NULL) { + BTPriQueueElem nexte; + + nexte.btpqe_tape = t; + nexte.btpqe_item = newbti; + _bt_pqadd(&q, &nexte); + } + } + } /* item */ + } /* run */ + + /* + * we are here because we ran out of input on all of the input + * tapes. + * + * if this pass did not generate more actual output runs than + * we have tapes, we know we have at most one run in each + * tape. this means that we are ready to merge into the final + * btree leaf pages instead of merging into a tape file. + */ + if (nruns <= btspool->bts_ntapes) { + doleaf = true; + } + } while (nruns > 0); /* pass */ + + /* + * this is the rightmost page, so the ItemId array needs to be + * slid back one slot. + */ + _bt_slideleft(index, state.btps_buf, state.btps_page); + _bt_wrtbuf(index, state.btps_buf); + + return(firstblk); +} + + +/* + * given the block number 'blk' of the first page of a set of linked + * siblings (i.e., the start of an entire level of the btree), + * construct the corresponding next level of the btree. we do this by + * placing minimum keys from each page into this page. the format of + * the internal pages is otherwise the same as for leaf pages. + */ +void +_bt_upperbuild(Relation index, BlockNumber blk, int level) +{ + Buffer rbuf; + Page rpage; + BTPageOpaque ropaque; + BTPageState state; + BlockNumber firstblk; + BTItem bti; + BTItem nbti; + OffsetNumber off; + + rbuf = _bt_getbuf(index, blk, BT_WRITE); + rpage = BufferGetPage(rbuf); + ropaque = (BTPageOpaque) PageGetSpecialPointer(rpage); + + /* + * if we only have one page on a level, we can just make it the + * root. + */ + if (P_RIGHTMOST(ropaque)) { + ropaque->btpo_flags |= BTP_ROOT; + _bt_wrtbuf(index, rbuf); + _bt_metaproot(index, blk); + return; + } + _bt_relbuf(index, rbuf, BT_WRITE); + + (void) memset((char *) &state, 0, sizeof(BTPageState)); + _bt_blnewpage(index, &(state.btps_buf), &(state.btps_page), 0); + state.btps_lastoff = P_HIKEY; + state.btps_lastbti = (BTItem) NULL; + firstblk = BufferGetBlockNumber(state.btps_buf); + + /* for each page... */ + do { + rbuf = _bt_getbuf(index, blk, BT_READ); + rpage = BufferGetPage(rbuf); + ropaque = (BTPageOpaque) PageGetSpecialPointer(rpage); + + /* for each item... */ + if (!PageIsEmpty(rpage)) { + /* + * form a new index tuple corresponding to the minimum key + * of the lower page and insert it into a page at this + * level. + */ + off = P_RIGHTMOST(ropaque) ? P_HIKEY : P_FIRSTKEY; + bti = (BTItem) PageGetItem(rpage, PageGetItemId(rpage, off)); + nbti = _bt_formitem(&(bti->bti_itup)); + ItemPointerSet(&(nbti->bti_itup.t_tid), blk, P_HIKEY); +#ifdef FASTBUILD_DEBUG + { + bool isnull; + Datum d = index_getattr(&(nbti->bti_itup), 1, + RelationGetTupleDescriptor(index), + &isnull); + printf("_bt_upperbuild: inserting <%x> at %d\n", + d, level); + } +#endif /* FASTBUILD_DEBUG */ + _bt_buildadd(index, &state, nbti, 0); + pfree((void *) nbti); + } + blk = ropaque->btpo_next; + _bt_relbuf(index, rbuf, BT_READ); + } while (blk != P_NONE); + + /* + * this is the rightmost page, so the ItemId array needs to be + * slid back one slot. + */ + _bt_slideleft(index, state.btps_buf, state.btps_page); + _bt_wrtbuf(index, state.btps_buf); + + _bt_upperbuild(index, firstblk, level + 1); +} + +/* + * given a spool loading by successive calls to _bt_spool, create an + * entire btree. + */ +void +_bt_leafbuild(Relation index, void *spool) +{ + BTSpool *btspool = (BTSpool *) spool; + BlockNumber firstblk; + + /* + * merge the runs into btree leaf pages. + */ + firstblk = _bt_merge(index, btspool); + + /* + * build the upper levels of the btree. + */ + _bt_upperbuild(index, firstblk, 0); +} + +#else /* !FASTBUILD */ + +void *_bt_spoolinit(Relation index, int ntapes) { return((void *) NULL); } +void _bt_spooldestroy(void *spool) { } +void _bt_spool(Relation index, BTItem btitem, void *spool) { } +void _bt_upperbuild(Relation index, BlockNumber blk, int level) { } +void _bt_leafbuild(Relation index, void *spool) { } + +#endif /* !FASTBUILD */ diff --git a/src/backend/access/nbtree/nbtstrat.c b/src/backend/access/nbtree/nbtstrat.c new file mode 100644 index 00000000000..2214c60950d --- /dev/null +++ b/src/backend/access/nbtree/nbtstrat.c @@ -0,0 +1,134 @@ +/*------------------------------------------------------------------------- + * + * btstrat.c-- + * Srategy map entries for the btree indexed access method + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/nbtree/Attic/nbtstrat.c,v 1.1.1.1 1996/07/09 06:21:12 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "access/genam.h" +#include "access/nbtree.h" + +/* + * Note: + * StrategyNegate, StrategyCommute, and StrategyNegateCommute + * assume <, <=, ==, >=, > ordering. + */ +static StrategyNumber BTNegate[5] = { + BTGreaterEqualStrategyNumber, + BTGreaterStrategyNumber, + InvalidStrategy, + BTLessStrategyNumber, + BTLessEqualStrategyNumber +}; + +static StrategyNumber BTCommute[5] = { + BTGreaterStrategyNumber, + BTGreaterEqualStrategyNumber, + InvalidStrategy, + BTLessEqualStrategyNumber, + BTLessStrategyNumber +}; + +static StrategyNumber BTNegateCommute[5] = { + BTLessEqualStrategyNumber, + BTLessStrategyNumber, + InvalidStrategy, + BTGreaterStrategyNumber, + BTGreaterEqualStrategyNumber +}; + +static uint16 BTLessTermData[] = { /* XXX type clash */ + 2, + BTLessStrategyNumber, + SK_NEGATE, + BTLessStrategyNumber, + SK_NEGATE | SK_COMMUTE +}; + +static uint16 BTLessEqualTermData[] = { /* XXX type clash */ + 2, + BTLessEqualStrategyNumber, + 0x0, + BTLessEqualStrategyNumber, + SK_COMMUTE +}; + +static uint16 BTGreaterEqualTermData[] = { /* XXX type clash */ + 2, + BTGreaterEqualStrategyNumber, + 0x0, + BTGreaterEqualStrategyNumber, + SK_COMMUTE + }; + +static uint16 BTGreaterTermData[] = { /* XXX type clash */ + 2, + BTGreaterStrategyNumber, + SK_NEGATE, + BTGreaterStrategyNumber, + SK_NEGATE | SK_COMMUTE +}; + +static StrategyTerm BTEqualExpressionData[] = { + (StrategyTerm)BTLessTermData, /* XXX */ + (StrategyTerm)BTLessEqualTermData, /* XXX */ + (StrategyTerm)BTGreaterEqualTermData, /* XXX */ + (StrategyTerm)BTGreaterTermData, /* XXX */ + NULL +}; + +static StrategyEvaluationData BTEvaluationData = { + /* XXX static for simplicity */ + + BTMaxStrategyNumber, + (StrategyTransformMap)BTNegate, /* XXX */ + (StrategyTransformMap)BTCommute, /* XXX */ + (StrategyTransformMap)BTNegateCommute, /* XXX */ + + { NULL, NULL, (StrategyExpression)BTEqualExpressionData, NULL, NULL, + NULL,NULL,NULL,NULL,NULL,NULL,NULL} +}; + +/* ---------------------------------------------------------------- + * RelationGetBTStrategy + * ---------------------------------------------------------------- + */ + +StrategyNumber +_bt_getstrat(Relation rel, + AttrNumber attno, + RegProcedure proc) +{ + StrategyNumber strat; + + strat = RelationGetStrategy(rel, attno, &BTEvaluationData, proc); + + Assert(StrategyNumberIsValid(strat)); + + return (strat); +} + +bool +_bt_invokestrat(Relation rel, + AttrNumber attno, + StrategyNumber strat, + Datum left, + Datum right) +{ + return (RelationInvokeStrategy(rel, &BTEvaluationData, attno, strat, + left, right)); +} diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c new file mode 100644 index 00000000000..695a2b637c8 --- /dev/null +++ b/src/backend/access/nbtree/nbtutils.c @@ -0,0 +1,239 @@ +/*------------------------------------------------------------------------- + * + * btutils.c-- + * Utility code for Postgres btree implementation. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtutils.c,v 1.1.1.1 1996/07/09 06:21:12 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "fmgr.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/excid.h" +#include "utils/datum.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/iqual.h" +#include "access/nbtree.h" + +ScanKey +_bt_mkscankey(Relation rel, IndexTuple itup) +{ + ScanKey skey; + TupleDesc itupdesc; + int natts; + int i; + Datum arg; + RegProcedure proc; + bool null; + + natts = rel->rd_rel->relnatts; + itupdesc = RelationGetTupleDescriptor(rel); + + skey = (ScanKey) palloc(natts * sizeof(ScanKeyData)); + + for (i = 0; i < natts; i++) { + arg = index_getattr(itup, i + 1, itupdesc, &null); + proc = index_getprocid(rel, i + 1, BTORDER_PROC); + ScanKeyEntryInitialize(&skey[i], + 0x0, (AttrNumber) (i + 1), proc, arg); + } + + return (skey); +} + +void +_bt_freeskey(ScanKey skey) +{ + pfree(skey); +} + +void +_bt_freestack(BTStack stack) +{ + BTStack ostack; + + while (stack != (BTStack) NULL) { + ostack = stack; + stack = stack->bts_parent; + pfree(ostack->bts_btitem); + pfree(ostack); + } +} + +/* + * _bt_orderkeys() -- Put keys in a sensible order for conjunctive quals. + * + * The order of the keys in the qual match the ordering imposed by + * the index. This routine only needs to be called if there are + * more than one qual clauses using this index. + */ +void +_bt_orderkeys(Relation relation, uint16 *numberOfKeys, ScanKey key) +{ + ScanKey xform; + ScanKeyData *cur; + StrategyMap map; + int nbytes; + long test; + int i, j; + int init[BTMaxStrategyNumber+1]; + + /* haven't looked at any strategies yet */ + for (i = 0; i <= BTMaxStrategyNumber; i++) + init[i] = 0; + + /* get space for the modified array of keys */ + nbytes = BTMaxStrategyNumber * sizeof(ScanKeyData); + xform = (ScanKey) palloc(nbytes); + memset(xform, 0, nbytes); + + + /* get the strategy map for this index/attribute pair */ + /* + * XXX + * When we support multiple keys in a single index, this is what + * we'll want to do. At present, the planner is hosed, so we + * hard-wire the attribute number below. Postgres only does single- + * key indices... + * map = IndexStrategyGetStrategyMap(RelationGetIndexStrategy(relation), + * BTMaxStrategyNumber, + * key->data[0].attributeNumber); + */ + map = IndexStrategyGetStrategyMap(RelationGetIndexStrategy(relation), + BTMaxStrategyNumber, + 1 /* XXX */ ); + + /* check each key passed in */ + for (i = *numberOfKeys; --i >= 0; ) { + cur = &key[i]; + for (j = BTMaxStrategyNumber; --j >= 0; ) { + if (cur->sk_procedure == map->entry[j].sk_procedure) + break; + } + + /* have we seen one of these before? */ + if (init[j]) { + /* yup, use the appropriate value */ + test = + (long) FMGR_PTR2(cur->sk_func, cur->sk_procedure, + cur->sk_argument, xform[j].sk_argument); + if (test) + xform[j].sk_argument = cur->sk_argument; + } else { + /* nope, use this value */ + memmove(&xform[j], cur, sizeof(*cur)); + + init[j] = 1; + } + } + + /* if = has been specified, no other key will be used */ + if (init[BTEqualStrategyNumber - 1]) { + init[BTLessStrategyNumber - 1] = 0; + init[BTLessEqualStrategyNumber - 1] = 0; + init[BTGreaterEqualStrategyNumber - 1] = 0; + init[BTGreaterStrategyNumber - 1] = 0; + } + + /* only one of <, <= */ + if (init[BTLessStrategyNumber - 1] + && init[BTLessEqualStrategyNumber - 1]) { + + ScanKeyData *lt, *le; + + lt = &xform[BTLessStrategyNumber - 1]; + le = &xform[BTLessEqualStrategyNumber - 1]; + + /* + * DO NOT use the cached function stuff here -- this is key + * ordering, happens only when the user expresses a hokey + * qualification, and gets executed only once, anyway. The + * transform maps are hard-coded, and can't be initialized + * in the correct way. + */ + + test = (long) fmgr(le->sk_procedure, le->sk_argument, lt->sk_argument); + + if (test) + init[BTLessEqualStrategyNumber - 1] = 0; + else + init[BTLessStrategyNumber - 1] = 0; + } + + /* only one of >, >= */ + if (init[BTGreaterStrategyNumber - 1] + && init[BTGreaterEqualStrategyNumber - 1]) { + + ScanKeyData *gt, *ge; + + gt = &xform[BTGreaterStrategyNumber - 1]; + ge = &xform[BTGreaterEqualStrategyNumber - 1]; + + /* see note above on function cache */ + test = (long) fmgr(ge->sk_procedure, gt->sk_argument, gt->sk_argument); + + if (test) + init[BTGreaterStrategyNumber - 1] = 0; + else + init[BTGreaterEqualStrategyNumber - 1] = 0; + } + + /* okay, reorder and count */ + j = 0; + + for (i = BTMaxStrategyNumber; --i >= 0; ) + if (init[i]) + key[j++] = xform[i]; + + *numberOfKeys = j; + + pfree(xform); +} + +bool +_bt_checkqual(IndexScanDesc scan, IndexTuple itup) +{ + if (scan->numberOfKeys > 0) + return (index_keytest(itup, RelationGetTupleDescriptor(scan->relation), + scan->numberOfKeys, scan->keyData)); + else + return (true); +} + +BTItem +_bt_formitem(IndexTuple itup) +{ + int nbytes_btitem; + BTItem btitem; + Size tuplen; + extern Oid newoid(); + + /* disallow nulls in btree keys */ + if (itup->t_info & INDEX_NULL_MASK) + elog(WARN, "btree indices cannot include null keys"); + + /* make a copy of the index tuple with room for the sequence number */ + tuplen = IndexTupleSize(itup); + nbytes_btitem = tuplen + + (sizeof(BTItemData) - sizeof(IndexTupleData)); + + btitem = (BTItem) palloc(nbytes_btitem); + memmove((char *) &(btitem->bti_itup), (char *) itup, tuplen); + + btitem->bti_oid = newoid(); + return (btitem); +} diff --git a/src/backend/access/printtup.h b/src/backend/access/printtup.h new file mode 100644 index 00000000000..b5843daf7e0 --- /dev/null +++ b/src/backend/access/printtup.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * printtup.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: printtup.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef PRINTTUP_H +#define PRINTTUP_H + +#include "access/htup.h" +#include "access/tupdesc.h" + +extern Oid typtoout(Oid type); +extern void printtup(HeapTuple tuple, TupleDesc typeinfo); +extern void showatts(char *name, TupleDesc attinfo); +extern void debugtup(HeapTuple tuple, TupleDesc typeinfo); +extern void printtup_internal(HeapTuple tuple, TupleDesc typeinfo); +extern Oid gettypelem(Oid type); + +#endif /* PRINTTUP_H */ diff --git a/src/backend/access/relscan.h b/src/backend/access/relscan.h new file mode 100644 index 00000000000..7899e9d945f --- /dev/null +++ b/src/backend/access/relscan.h @@ -0,0 +1,87 @@ +/*------------------------------------------------------------------------- + * + * relscan.h-- + * POSTGRES internal relation scan descriptor definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: relscan.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef RELSCAN_H +#define RELSCAN_H + +#include "c.h" + +#include "access/skey.h" +#include "storage/buf.h" +#include "access/htup.h" +#include "storage/itemptr.h" + +#include "utils/tqual.h" +#include "utils/rel.h" + + +typedef ItemPointerData MarkData; + +typedef struct HeapScanDescData { + Relation rs_rd; /* pointer to relation descriptor */ + HeapTuple rs_ptup; /* previous tuple in scan */ + HeapTuple rs_ctup; /* current tuple in scan */ + HeapTuple rs_ntup; /* next tuple in scan */ + Buffer rs_pbuf; /* previous buffer in scan */ + Buffer rs_cbuf; /* current buffer in scan */ + Buffer rs_nbuf; /* next buffer in scan */ + ItemPointerData rs_mptid; /* marked previous tid */ + ItemPointerData rs_mctid; /* marked current tid */ + ItemPointerData rs_mntid; /* marked next tid */ + ItemPointerData rs_mcd; /* marked current delta XXX ??? */ + bool rs_atend; /* restart scan at end? */ + TimeQual rs_tr; /* time qualification */ + uint16 rs_cdelta; /* current delta in chain */ + uint16 rs_nkeys; /* number of attributes in keys */ + ScanKey rs_key; /* key descriptors */ +} HeapScanDescData; + +typedef HeapScanDescData *HeapScanDesc; + +typedef struct IndexScanDescData { + Relation relation; /* relation descriptor */ + void *opaque; /* am-specific slot */ + ItemPointerData previousItemData; /* previous index pointer */ + ItemPointerData currentItemData; /* current index pointer */ + ItemPointerData nextItemData; /* next index pointer */ + MarkData previousMarkData; /* marked previous pointer */ + MarkData currentMarkData; /* marked current pointer */ + MarkData nextMarkData; /* marked next pointer */ + uint8 flags; /* scan position flags */ + bool scanFromEnd; /* restart scan at end? */ + uint16 numberOfKeys; /* number of key attributes */ + ScanKey keyData; /* key descriptor */ +} IndexScanDescData; + +typedef IndexScanDescData *IndexScanDesc; + +/* ---------------- + * IndexScanDescPtr is used in the executor where we have to + * keep track of several index scans when using several indices + * - cim 9/10/89 + * ---------------- + */ +typedef IndexScanDesc *IndexScanDescPtr; + +/* + * HeapScanIsValid -- + * True iff the heap scan is valid. + */ +#define HeapScanIsValid(scan) PointerIsValid(scan) + +/* + * IndexScanIsValid -- + * True iff the index scan is valid. + */ +#define IndexScanIsValid(scan) PointerIsValid(scan) + +#endif /* RELSCAN_H */ diff --git a/src/backend/access/rtree.h b/src/backend/access/rtree.h new file mode 100644 index 00000000000..79f1622e48b --- /dev/null +++ b/src/backend/access/rtree.h @@ -0,0 +1,98 @@ +/*------------------------------------------------------------------------- + * + * rtree.h-- + * common declarations for the rtree access method code. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: rtree.h,v 1.1.1.1 1996/07/09 06:21:08 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef RTREE_H +#define RTREE_H + +/* see rtstrat.c for what all this is about */ +#define RTNStrategies 8 +#define RTLeftStrategyNumber 1 +#define RTOverLeftStrategyNumber 2 +#define RTOverlapStrategyNumber 3 +#define RTOverRightStrategyNumber 4 +#define RTRightStrategyNumber 5 +#define RTSameStrategyNumber 6 +#define RTContainsStrategyNumber 7 +#define RTContainedByStrategyNumber 8 + +#define RTNProcs 3 +#define RT_UNION_PROC 1 +#define RT_INTER_PROC 2 +#define RT_SIZE_PROC 3 + +#define F_LEAF (1 << 0) + +typedef struct RTreePageOpaqueData { + uint32 flags; +} RTreePageOpaqueData; + +typedef RTreePageOpaqueData *RTreePageOpaque; + +/* + * When we descend a tree, we keep a stack of parent pointers. + */ + +typedef struct RTSTACK { + struct RTSTACK *rts_parent; + OffsetNumber rts_child; + BlockNumber rts_blk; +} RTSTACK; + +/* + * When we're doing a scan, we need to keep track of the parent stack + * for the marked and current items. Also, rtrees have the following + * property: if you're looking for the box (1,1,2,2), on the internal + * nodes you have to search for all boxes that *contain* (1,1,2,2), and + * not the ones that match it. We have a private scan key for internal + * nodes in the opaque structure for rtrees for this reason. See + * access/index-rtree/rtscan.c and rtstrat.c for how it gets initialized. + */ + +typedef struct RTreeScanOpaqueData { + struct RTSTACK *s_stack; + struct RTSTACK *s_markstk; + uint16 s_flags; + uint16 s_internalNKey; + ScanKey s_internalKey; +} RTreeScanOpaqueData; + +typedef RTreeScanOpaqueData *RTreeScanOpaque; + +/* + * When we're doing a scan and updating a tree at the same time, the + * updates may affect the scan. We use the flags entry of the scan's + * opaque space to record our actual position in response to updates + * that we can't handle simply by adjusting pointers. + */ + +#define RTS_CURBEFORE ((uint16) (1 << 0)) +#define RTS_MRKBEFORE ((uint16) (1 << 1)) + +/* root page of an rtree */ +#define P_ROOT 0 + +/* + * When we update a relation on which we're doing a scan, we need to + * check the scan and fix it if the update affected any of the pages it + * touches. Otherwise, we can miss records that we should see. The only + * times we need to do this are for deletions and splits. See the code in + * rtscan.c for how the scan is fixed. These two contants tell us what sort + * of operation changed the index. + */ + +#define RTOP_DEL 0 +#define RTOP_SPLIT 1 + +/* defined in rtree.c */ +extern void freestack(RTSTACK *s); + +#endif /* RTREE_H */ diff --git a/src/backend/access/rtree/Makefile.inc b/src/backend/access/rtree/Makefile.inc new file mode 100644 index 00000000000..a93a5e53290 --- /dev/null +++ b/src/backend/access/rtree/Makefile.inc @@ -0,0 +1,14 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for access/rtree (R-Tree access method) +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/access/rtree/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:12 scrappy Exp $ +# +#------------------------------------------------------------------------- + +SUBSRCS+= rtget.c rtproc.c rtree.c rtscan.c rtstrat.c diff --git a/src/backend/access/rtree/rtget.c b/src/backend/access/rtree/rtget.c new file mode 100644 index 00000000000..fb2e169297d --- /dev/null +++ b/src/backend/access/rtree/rtget.c @@ -0,0 +1,320 @@ +/*------------------------------------------------------------------------- + * + * rtget.c-- + * fetch tuples from an rtree scan. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/rtree/Attic/rtget.c,v 1.1.1.1 1996/07/09 06:21:13 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/iqual.h" +#include "access/rtree.h" +#include "access/sdir.h" + +static OffsetNumber findnext(IndexScanDesc s, Page p, OffsetNumber n, + ScanDirection dir); +static RetrieveIndexResult rtscancache(IndexScanDesc s, ScanDirection dir); +static RetrieveIndexResult rtfirst(IndexScanDesc s, ScanDirection dir); +static RetrieveIndexResult rtnext(IndexScanDesc s, ScanDirection dir); +static ItemPointer rtheapptr(Relation r, ItemPointer itemp); + + +RetrieveIndexResult +rtgettuple(IndexScanDesc s, ScanDirection dir) +{ + RetrieveIndexResult res; + + /* if we have it cached in the scan desc, just return the value */ + if ((res = rtscancache(s, dir)) != (RetrieveIndexResult) NULL) + return (res); + + /* not cached, so we'll have to do some work */ + if (ItemPointerIsValid(&(s->currentItemData))) { + res = rtnext(s, dir); + } else { + res = rtfirst(s, dir); + } + return (res); +} + +static RetrieveIndexResult +rtfirst(IndexScanDesc s, ScanDirection dir) +{ + Buffer b; + Page p; + OffsetNumber n; + OffsetNumber maxoff; + RetrieveIndexResult res; + RTreePageOpaque po; + RTreeScanOpaque so; + RTSTACK *stk; + BlockNumber blk; + IndexTuple it; + ItemPointer ip; + + b = ReadBuffer(s->relation, P_ROOT); + p = BufferGetPage(b); + po = (RTreePageOpaque) PageGetSpecialPointer(p); + so = (RTreeScanOpaque) s->opaque; + + for (;;) { + maxoff = PageGetMaxOffsetNumber(p); + if (ScanDirectionIsBackward(dir)) + n = findnext(s, p, maxoff, dir); + else + n = findnext(s, p, FirstOffsetNumber, dir); + + while (n < FirstOffsetNumber || n > maxoff) { + + ReleaseBuffer(b); + if (so->s_stack == (RTSTACK *) NULL) + return ((RetrieveIndexResult) NULL); + + stk = so->s_stack; + b = ReadBuffer(s->relation, stk->rts_blk); + p = BufferGetPage(b); + po = (RTreePageOpaque) PageGetSpecialPointer(p); + maxoff = PageGetMaxOffsetNumber(p); + + if (ScanDirectionIsBackward(dir)) { + n = OffsetNumberPrev(stk->rts_child); + } else { + n = OffsetNumberNext(stk->rts_child); + } + so->s_stack = stk->rts_parent; + pfree(stk); + + n = findnext(s, p, n, dir); + } + if (po->flags & F_LEAF) { + ItemPointerSet(&(s->currentItemData), BufferGetBlockNumber(b), n); + + it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n)); + ip = (ItemPointer) palloc(sizeof(ItemPointerData)); + memmove((char *) ip, (char *) &(it->t_tid), + sizeof(ItemPointerData)); + ReleaseBuffer(b); + + res = FormRetrieveIndexResult(&(s->currentItemData), ip); + + return (res); + } else { + stk = (RTSTACK *) palloc(sizeof(RTSTACK)); + stk->rts_child = n; + stk->rts_blk = BufferGetBlockNumber(b); + stk->rts_parent = so->s_stack; + so->s_stack = stk; + + it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n)); + blk = ItemPointerGetBlockNumber(&(it->t_tid)); + + ReleaseBuffer(b); + b = ReadBuffer(s->relation, blk); + p = BufferGetPage(b); + po = (RTreePageOpaque) PageGetSpecialPointer(p); + } + } +} + +static RetrieveIndexResult +rtnext(IndexScanDesc s, ScanDirection dir) +{ + Buffer b; + Page p; + OffsetNumber n; + OffsetNumber maxoff; + RetrieveIndexResult res; + RTreePageOpaque po; + RTreeScanOpaque so; + RTSTACK *stk; + BlockNumber blk; + IndexTuple it; + ItemPointer ip; + + blk = ItemPointerGetBlockNumber(&(s->currentItemData)); + n = ItemPointerGetOffsetNumber(&(s->currentItemData)); + + if (ScanDirectionIsForward(dir)) { + n = OffsetNumberNext(n); + } else { + n = OffsetNumberPrev(n); + } + + b = ReadBuffer(s->relation, blk); + p = BufferGetPage(b); + po = (RTreePageOpaque) PageGetSpecialPointer(p); + so = (RTreeScanOpaque) s->opaque; + + for (;;) { + maxoff = PageGetMaxOffsetNumber(p); + n = findnext(s, p, n, dir); + + while (n < FirstOffsetNumber || n > maxoff) { + + ReleaseBuffer(b); + if (so->s_stack == (RTSTACK *) NULL) + return ((RetrieveIndexResult) NULL); + + stk = so->s_stack; + b = ReadBuffer(s->relation, stk->rts_blk); + p = BufferGetPage(b); + maxoff = PageGetMaxOffsetNumber(p); + po = (RTreePageOpaque) PageGetSpecialPointer(p); + + if (ScanDirectionIsBackward(dir)) { + n = OffsetNumberPrev(stk->rts_child); + } else { + n = OffsetNumberNext(stk->rts_child); + } + so->s_stack = stk->rts_parent; + pfree(stk); + + n = findnext(s, p, n, dir); + } + if (po->flags & F_LEAF) { + ItemPointerSet(&(s->currentItemData), BufferGetBlockNumber(b), n); + + it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n)); + ip = (ItemPointer) palloc(sizeof(ItemPointerData)); + memmove((char *) ip, (char *) &(it->t_tid), + sizeof(ItemPointerData)); + ReleaseBuffer(b); + + res = FormRetrieveIndexResult(&(s->currentItemData), ip); + + return (res); + } else { + stk = (RTSTACK *) palloc(sizeof(RTSTACK)); + stk->rts_child = n; + stk->rts_blk = BufferGetBlockNumber(b); + stk->rts_parent = so->s_stack; + so->s_stack = stk; + + it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n)); + blk = ItemPointerGetBlockNumber(&(it->t_tid)); + + ReleaseBuffer(b); + b = ReadBuffer(s->relation, blk); + p = BufferGetPage(b); + po = (RTreePageOpaque) PageGetSpecialPointer(p); + + if (ScanDirectionIsBackward(dir)) { + n = PageGetMaxOffsetNumber(p); + } else { + n = FirstOffsetNumber; + } + } + } +} + +static OffsetNumber +findnext(IndexScanDesc s, Page p, OffsetNumber n, ScanDirection dir) +{ + OffsetNumber maxoff; + IndexTuple it; + RTreePageOpaque po; + RTreeScanOpaque so; + + maxoff = PageGetMaxOffsetNumber(p); + po = (RTreePageOpaque) PageGetSpecialPointer(p); + so = (RTreeScanOpaque) s->opaque; + + /* + * If we modified the index during the scan, we may have a pointer to + * a ghost tuple, before the scan. If this is the case, back up one. + */ + + if (so->s_flags & RTS_CURBEFORE) { + so->s_flags &= ~RTS_CURBEFORE; + n = OffsetNumberPrev(n); + } + + while (n >= FirstOffsetNumber && n <= maxoff) { + it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n)); + if (po->flags & F_LEAF) { + if (index_keytest(it, + RelationGetTupleDescriptor(s->relation), + s->numberOfKeys, s->keyData)) + break; + } else { + if (index_keytest(it, + RelationGetTupleDescriptor(s->relation), + so->s_internalNKey, so->s_internalKey)) + break; + } + + if (ScanDirectionIsBackward(dir)) { + n = OffsetNumberPrev(n); + } else { + n = OffsetNumberNext(n); + } + } + + return (n); +} + +static RetrieveIndexResult +rtscancache(IndexScanDesc s, ScanDirection dir) +{ + RetrieveIndexResult res; + ItemPointer ip; + + if (!(ScanDirectionIsNoMovement(dir) + && ItemPointerIsValid(&(s->currentItemData)))) { + + return ((RetrieveIndexResult) NULL); + } + + ip = rtheapptr(s->relation, &(s->currentItemData)); + + if (ItemPointerIsValid(ip)) + res = FormRetrieveIndexResult(&(s->currentItemData), ip); + else + res = (RetrieveIndexResult) NULL; + + return (res); +} + +/* + * rtheapptr returns the item pointer to the tuple in the heap relation + * for which itemp is the index relation item pointer. + */ +static ItemPointer +rtheapptr(Relation r, ItemPointer itemp) +{ + Buffer b; + Page p; + IndexTuple it; + ItemPointer ip; + OffsetNumber n; + + ip = (ItemPointer) palloc(sizeof(ItemPointerData)); + if (ItemPointerIsValid(itemp)) { + b = ReadBuffer(r, ItemPointerGetBlockNumber(itemp)); + p = BufferGetPage(b); + n = ItemPointerGetOffsetNumber(itemp); + it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n)); + memmove((char *) ip, (char *) &(it->t_tid), + sizeof(ItemPointerData)); + ReleaseBuffer(b); + } else { + ItemPointerSetInvalid(ip); + } + + return (ip); +} diff --git a/src/backend/access/rtree/rtproc.c b/src/backend/access/rtree/rtproc.c new file mode 100644 index 00000000000..a2f7bef46b4 --- /dev/null +++ b/src/backend/access/rtree/rtproc.c @@ -0,0 +1,150 @@ +/*------------------------------------------------------------------------- + * + * rtproc.c-- + * pg_amproc entries for rtrees. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/rtree/Attic/rtproc.c,v 1.1.1.1 1996/07/09 06:21:13 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <math.h> +#include <string.h> + +#include "postgres.h" + +#include "utils/elog.h" +#include "utils/geo-decls.h" +#include "utils/palloc.h" + +BOX +*rt_box_union(BOX *a, BOX *b) +{ + BOX *n; + + if ((n = (BOX *) palloc(sizeof (*n))) == (BOX *) NULL) + elog(WARN, "Cannot allocate box for union"); + + n->xh = Max(a->xh, b->xh); + n->yh = Max(a->yh, b->yh); + n->xl = Min(a->xl, b->xl); + n->yl = Min(a->yl, b->yl); + + return (n); +} + +BOX * +rt_box_inter(BOX *a, BOX *b) +{ + BOX *n; + + if ((n = (BOX *) palloc(sizeof (*n))) == (BOX *) NULL) + elog(WARN, "Cannot allocate box for union"); + + n->xh = Min(a->xh, b->xh); + n->yh = Min(a->yh, b->yh); + n->xl = Max(a->xl, b->xl); + n->yl = Max(a->yl, b->yl); + + if (n->xh < n->xl || n->yh < n->yl) { + pfree(n); + return ((BOX *) NULL); + } + + return (n); +} + +void +rt_box_size(BOX *a, float *size) +{ + if (a == (BOX *) NULL || a->xh <= a->xl || a->yh <= a->yl) + *size = 0.0; + else + *size = (float) ((a->xh - a->xl) * (a->yh - a->yl)); + + return; +} + +/* + * rt_bigbox_size() -- Compute a size for big boxes. + * + * In an earlier release of the system, this routine did something + * different from rt_box_size. We now use floats, rather than ints, + * as the return type for the size routine, so we no longer need to + * have a special return type for big boxes. + */ +void +rt_bigbox_size(BOX *a, float *size) +{ + rt_box_size(a, size); +} + +POLYGON * +rt_poly_union(POLYGON *a, POLYGON *b) +{ + POLYGON *p; + + p = (POLYGON *)PALLOCTYPE(POLYGON); + + if (!PointerIsValid(p)) + elog(WARN, "Cannot allocate polygon for union"); + + memset((char *) p, 0, sizeof(POLYGON)); /* zero any holes */ + p->size = sizeof(POLYGON); + p->npts = 0; + p->boundbox.xh = Max(a->boundbox.xh, b->boundbox.xh); + p->boundbox.yh = Max(a->boundbox.yh, b->boundbox.yh); + p->boundbox.xl = Min(a->boundbox.xl, b->boundbox.xl); + p->boundbox.yl = Min(a->boundbox.yl, b->boundbox.yl); + return p; +} + +void +rt_poly_size(POLYGON *a, float *size) +{ + double xdim, ydim; + + size = (float *) palloc(sizeof(float)); + if (a == (POLYGON *) NULL || + a->boundbox.xh <= a->boundbox.xl || + a->boundbox.yh <= a->boundbox.yl) + *size = 0.0; + else { + xdim = (a->boundbox.xh - a->boundbox.xl); + ydim = (a->boundbox.yh - a->boundbox.yl); + + *size = (float) (xdim * ydim); + } + + return; +} + +POLYGON * +rt_poly_inter(POLYGON *a, POLYGON *b) +{ + POLYGON *p; + + p = (POLYGON *) PALLOCTYPE(POLYGON); + + if (!PointerIsValid(p)) + elog(WARN, "Cannot allocate polygon for intersection"); + + memset((char *) p, 0, sizeof(POLYGON)); /* zero any holes */ + p->size = sizeof(POLYGON); + p->npts = 0; + p->boundbox.xh = Min(a->boundbox.xh, b->boundbox.xh); + p->boundbox.yh = Min(a->boundbox.yh, b->boundbox.yh); + p->boundbox.xl = Max(a->boundbox.xl, b->boundbox.xl); + p->boundbox.yl = Max(a->boundbox.yl, b->boundbox.yl); + + if (p->boundbox.xh < p->boundbox.xl || p->boundbox.yh < p->boundbox.yl) + { + pfree(p); + return ((POLYGON *) NULL); + } + + return (p); +} diff --git a/src/backend/access/rtree/rtree.c b/src/backend/access/rtree/rtree.c new file mode 100644 index 00000000000..96efc3bc90b --- /dev/null +++ b/src/backend/access/rtree/rtree.c @@ -0,0 +1,955 @@ +/*------------------------------------------------------------------------- + * + * rtree.c-- + * interface routines for the postgres rtree indexed access method. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/rtree/Attic/rtree.c,v 1.1.1.1 1996/07/09 06:21:13 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/excid.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/rtree.h" +#include "access/rtscan.h" +#include "access/funcindex.h" +#include "access/tupdesc.h" + +#include "nodes/execnodes.h" +#include "nodes/plannodes.h" + +#include "executor/executor.h" +#include "executor/tuptable.h" + +#include "catalog/index.h" + +typedef struct SPLITVEC { + OffsetNumber *spl_left; + int spl_nleft; + char *spl_ldatum; + OffsetNumber *spl_right; + int spl_nright; + char *spl_rdatum; +} SPLITVEC; + +typedef struct RTSTATE { + func_ptr unionFn; /* union function */ + func_ptr sizeFn; /* size function */ + func_ptr interFn; /* intersection function */ +} RTSTATE; + +/* non-export function prototypes */ +static InsertIndexResult rtdoinsert(Relation r, IndexTuple itup, + RTSTATE *rtstate); +static void rttighten(Relation r, RTSTACK *stk, char *datum, int att_size, + RTSTATE *rtstate); +static InsertIndexResult dosplit(Relation r, Buffer buffer, RTSTACK *stack, + IndexTuple itup, RTSTATE *rtstate); +static void rtintinsert(Relation r, RTSTACK *stk, IndexTuple ltup, + IndexTuple rtup, RTSTATE *rtstate); +static void rtnewroot(Relation r, IndexTuple lt, IndexTuple rt); +static void picksplit(Relation r, Page page, SPLITVEC *v, IndexTuple itup, + RTSTATE *rtstate); +static void RTInitBuffer(Buffer b, uint32 f); +static OffsetNumber choose(Relation r, Page p, IndexTuple it, + RTSTATE *rtstate); +static int nospace(Page p, IndexTuple it); +static void initRtstate(RTSTATE *rtstate, Relation index); + + +void +rtbuild(Relation heap, + Relation index, + int natts, + AttrNumber *attnum, + IndexStrategy istrat, + uint16 pcount, + Datum *params, + FuncIndexInfo *finfo, + PredInfo *predInfo) +{ + HeapScanDesc scan; + Buffer buffer; + AttrNumber i; + HeapTuple htup; + IndexTuple itup; + TupleDesc hd, id; + InsertIndexResult res; + Datum *d; + bool *nulls; + int nb, nh, ni; + ExprContext *econtext; + TupleTable tupleTable; + TupleTableSlot *slot; + Oid hrelid, irelid; + Node *pred, *oldPred; + RTSTATE rtState; + + initRtstate(&rtState, index); + + /* rtrees only know how to do stupid locking now */ + RelationSetLockForWrite(index); + + pred = predInfo->pred; + oldPred = predInfo->oldPred; + + /* + * We expect to be called exactly once for any index relation. + * If that's not the case, big trouble's what we have. + */ + + if (oldPred == NULL && (nb = RelationGetNumberOfBlocks(index)) != 0) + elog(WARN, "%s already contains data", index->rd_rel->relname.data); + + /* initialize the root page (if this is a new index) */ + if (oldPred == NULL) { + buffer = ReadBuffer(index, P_NEW); + RTInitBuffer(buffer, F_LEAF); + WriteBuffer(buffer); + } + + /* init the tuple descriptors and get set for a heap scan */ + hd = RelationGetTupleDescriptor(heap); + id = RelationGetTupleDescriptor(index); + d = (Datum *)palloc(natts * sizeof (*d)); + nulls = (bool *)palloc(natts * sizeof (*nulls)); + + /* + * If this is a predicate (partial) index, we will need to evaluate the + * predicate using ExecQual, which requires the current tuple to be in a + * slot of a TupleTable. In addition, ExecQual must have an ExprContext + * referring to that slot. Here, we initialize dummy TupleTable and + * ExprContext objects for this purpose. --Nels, Feb '92 + */ +#ifndef OMIT_PARTIAL_INDEX + if (pred != NULL || oldPred != NULL) { + tupleTable = ExecCreateTupleTable(1); + slot = ExecAllocTableSlot(tupleTable); + econtext = makeNode(ExprContext); + FillDummyExprContext(econtext, slot, hd, buffer); + } +#endif /* OMIT_PARTIAL_INDEX */ + scan = heap_beginscan(heap, 0, NowTimeQual, 0, (ScanKey) NULL); + htup = heap_getnext(scan, 0, &buffer); + + /* count the tuples as we insert them */ + nh = ni = 0; + + for (; HeapTupleIsValid(htup); htup = heap_getnext(scan, 0, &buffer)) { + + nh++; + + /* + * If oldPred != NULL, this is an EXTEND INDEX command, so skip + * this tuple if it was already in the existing partial index + */ + if (oldPred != NULL) { +#ifndef OMIT_PARTIAL_INDEX + /*SetSlotContents(slot, htup); */ + slot->val = htup; + if (ExecQual((List*)oldPred, econtext) == true) { + ni++; + continue; + } +#endif /* OMIT_PARTIAL_INDEX */ + } + + /* Skip this tuple if it doesn't satisfy the partial-index predicate */ + if (pred != NULL) { +#ifndef OMIT_PARTIAL_INDEX + /*SetSlotContents(slot, htup); */ + slot->val = htup; + if (ExecQual((List*)pred, econtext) == false) + continue; +#endif /* OMIT_PARTIAL_INDEX */ + } + + ni++; + + /* + * For the current heap tuple, extract all the attributes + * we use in this index, and note which are null. + */ + + for (i = 1; i <= natts; i++) { + int attoff; + bool attnull; + + /* + * Offsets are from the start of the tuple, and are + * zero-based; indices are one-based. The next call + * returns i - 1. That's data hiding for you. + */ + + attoff = AttrNumberGetAttrOffset(i); + /* + d[attoff] = HeapTupleGetAttributeValue(htup, buffer, + */ + d[attoff] = GetIndexValue(htup, + hd, + attoff, + attnum, + finfo, + &attnull, + buffer); + nulls[attoff] = (attnull ? 'n' : ' '); + } + + /* form an index tuple and point it at the heap tuple */ + itup = index_formtuple(id, &d[0], nulls); + itup->t_tid = htup->t_ctid; + + /* + * Since we already have the index relation locked, we + * call rtdoinsert directly. Normal access method calls + * dispatch through rtinsert, which locks the relation + * for write. This is the right thing to do if you're + * inserting single tups, but not when you're initializing + * the whole index at once. + */ + + res = rtdoinsert(index, itup, &rtState); + pfree(itup); + pfree(res); + } + + /* okay, all heap tuples are indexed */ + heap_endscan(scan); + RelationUnsetLockForWrite(index); + + if (pred != NULL || oldPred != NULL) { +#ifndef OMIT_PARTIAL_INDEX + ExecDestroyTupleTable(tupleTable, true); + pfree(econtext); +#endif /* OMIT_PARTIAL_INDEX */ + } + + /* + * Since we just counted the tuples in the heap, we update its + * stats in pg_relation to guarantee that the planner takes + * advantage of the index we just created. UpdateStats() does a + * CommandCounterIncrement(), which flushes changed entries from + * the system relcache. The act of constructing an index changes + * these heap and index tuples in the system catalogs, so they + * need to be flushed. We close them to guarantee that they + * will be. + */ + + hrelid = heap->rd_id; + irelid = index->rd_id; + heap_close(heap); + index_close(index); + + UpdateStats(hrelid, nh, true); + UpdateStats(irelid, ni, false); + + if (oldPred != NULL) { + if (ni == nh) pred = NULL; + UpdateIndexPredicate(irelid, oldPred, pred); + } + + /* be tidy */ + pfree(nulls); + pfree(d); +} + +/* + * rtinsert -- wrapper for rtree tuple insertion. + * + * This is the public interface routine for tuple insertion in rtrees. + * It doesn't do any work; just locks the relation and passes the buck. + */ +InsertIndexResult +rtinsert(Relation r, IndexTuple itup) +{ + InsertIndexResult res; + RTSTATE rtState; + + initRtstate(&rtState, r); + + RelationSetLockForWrite(r); + res = rtdoinsert(r, itup, &rtState); + + /* XXX two-phase locking -- don't unlock the relation until EOT */ + return (res); +} + +static InsertIndexResult +rtdoinsert(Relation r, IndexTuple itup, RTSTATE *rtstate) +{ + Page page; + Buffer buffer; + BlockNumber blk; + IndexTuple which; + OffsetNumber l; + RTSTACK *stack; + InsertIndexResult res; + RTreePageOpaque opaque; + char *datum; + + blk = P_ROOT; + buffer = InvalidBuffer; + stack = (RTSTACK *) NULL; + + do { + /* let go of current buffer before getting next */ + if (buffer != InvalidBuffer) + ReleaseBuffer(buffer); + + /* get next buffer */ + buffer = ReadBuffer(r, blk); + page = (Page) BufferGetPage(buffer); + + opaque = (RTreePageOpaque) PageGetSpecialPointer(page); + if (!(opaque->flags & F_LEAF)) { + RTSTACK *n; + ItemId iid; + + n = (RTSTACK *) palloc(sizeof(RTSTACK)); + n->rts_parent = stack; + n->rts_blk = blk; + n->rts_child = choose(r, page, itup, rtstate); + stack = n; + + iid = PageGetItemId(page, n->rts_child); + which = (IndexTuple) PageGetItem(page, iid); + blk = ItemPointerGetBlockNumber(&(which->t_tid)); + } + } while (!(opaque->flags & F_LEAF)); + + if (nospace(page, itup)) { + /* need to do a split */ + res = dosplit(r, buffer, stack, itup, rtstate); + freestack(stack); + WriteBuffer(buffer); /* don't forget to release buffer! */ + return (res); + } + + /* add the item and write the buffer */ + if (PageIsEmpty(page)) { + l = PageAddItem(page, (Item) itup, IndexTupleSize(itup), + FirstOffsetNumber, + LP_USED); + } else { + l = PageAddItem(page, (Item) itup, IndexTupleSize(itup), + OffsetNumberNext(PageGetMaxOffsetNumber(page)), + LP_USED); + } + + WriteBuffer(buffer); + + datum = (((char *) itup) + sizeof(IndexTupleData)); + + /* now expand the page boundary in the parent to include the new child */ + rttighten(r, stack, datum, + (IndexTupleSize(itup) - sizeof(IndexTupleData)), rtstate); + freestack(stack); + + /* build and return an InsertIndexResult for this insertion */ + res = (InsertIndexResult) palloc(sizeof(InsertIndexResultData)); + ItemPointerSet(&(res->pointerData), blk, l); + + return (res); +} + +static void +rttighten(Relation r, + RTSTACK *stk, + char *datum, + int att_size, + RTSTATE *rtstate) +{ + char *oldud; + char *tdatum; + Page p; + float old_size, newd_size; + Buffer b; + + if (stk == (RTSTACK *) NULL) + return; + + b = ReadBuffer(r, stk->rts_blk); + p = BufferGetPage(b); + + oldud = (char *) PageGetItem(p, PageGetItemId(p, stk->rts_child)); + oldud += sizeof(IndexTupleData); + + (*rtstate->sizeFn)(oldud, &old_size); + datum = (char *) (*rtstate->unionFn)(oldud, datum); + + (*rtstate->sizeFn)(datum, &newd_size); + + if (newd_size != old_size) { + TupleDesc td = RelationGetTupleDescriptor(r); + + if (td->attrs[0]->attlen < 0) { + /* + * This is an internal page, so 'oldud' had better be a + * union (constant-length) key, too. (See comment below.) + */ + Assert(VARSIZE(datum) == VARSIZE(oldud)); + memmove(oldud, datum, VARSIZE(datum)); + } else { + memmove(oldud, datum, att_size); + } + WriteBuffer(b); + + /* + * The user may be defining an index on variable-sized data (like + * polygons). If so, we need to get a constant-sized datum for + * insertion on the internal page. We do this by calling the union + * proc, which is guaranteed to return a rectangle. + */ + + tdatum = (char *) (*rtstate->unionFn)(datum, datum); + rttighten(r, stk->rts_parent, tdatum, att_size, rtstate); + pfree(tdatum); + } else { + ReleaseBuffer(b); + } + pfree(datum); +} + +/* + * dosplit -- split a page in the tree. + * + * This is the quadratic-cost split algorithm Guttman describes in + * his paper. The reason we chose it is that you can implement this + * with less information about the data types on which you're operating. + */ +static InsertIndexResult +dosplit(Relation r, + Buffer buffer, + RTSTACK *stack, + IndexTuple itup, + RTSTATE *rtstate) +{ + Page p; + Buffer leftbuf, rightbuf; + Page left, right; + ItemId itemid; + IndexTuple item; + IndexTuple ltup, rtup; + OffsetNumber maxoff; + OffsetNumber i; + OffsetNumber leftoff, rightoff; + BlockNumber lbknum, rbknum; + BlockNumber bufblock; + RTreePageOpaque opaque; + int blank; + InsertIndexResult res; + char *isnull; + SPLITVEC v; + TupleDesc tupDesc; + + isnull = (char *) palloc(r->rd_rel->relnatts); + for (blank = 0; blank < r->rd_rel->relnatts; blank++) + isnull[blank] = ' '; + p = (Page) BufferGetPage(buffer); + opaque = (RTreePageOpaque) PageGetSpecialPointer(p); + + /* + * The root of the tree is the first block in the relation. If + * we're about to split the root, we need to do some hocus-pocus + * to enforce this guarantee. + */ + + if (BufferGetBlockNumber(buffer) == P_ROOT) { + leftbuf = ReadBuffer(r, P_NEW); + RTInitBuffer(leftbuf, opaque->flags); + lbknum = BufferGetBlockNumber(leftbuf); + left = (Page) BufferGetPage(leftbuf); + } else { + leftbuf = buffer; + IncrBufferRefCount(buffer); + lbknum = BufferGetBlockNumber(buffer); + left = (Page) PageGetTempPage(p, sizeof(RTreePageOpaqueData)); + } + + rightbuf = ReadBuffer(r, P_NEW); + RTInitBuffer(rightbuf, opaque->flags); + rbknum = BufferGetBlockNumber(rightbuf); + right = (Page) BufferGetPage(rightbuf); + + picksplit(r, p, &v, itup, rtstate); + + leftoff = rightoff = FirstOffsetNumber; + maxoff = PageGetMaxOffsetNumber(p); + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) { + itemid = PageGetItemId(p, i); + item = (IndexTuple) PageGetItem(p, itemid); + + if (i == *(v.spl_left)) { + (void) PageAddItem(left, (Item) item, IndexTupleSize(item), + leftoff, LP_USED); + leftoff = OffsetNumberNext(leftoff); + v.spl_left++; /* advance in left split vector */ + } else { + (void) PageAddItem(right, (Item) item, IndexTupleSize(item), + rightoff, LP_USED); + rightoff = OffsetNumberNext(rightoff); + v.spl_right++; /* advance in right split vector */ + } + } + + /* build an InsertIndexResult for this insertion */ + res = (InsertIndexResult) palloc(sizeof(InsertIndexResultData)); + + /* now insert the new index tuple */ + if (*(v.spl_left) != FirstOffsetNumber) { + (void) PageAddItem(left, (Item) itup, IndexTupleSize(itup), + leftoff, LP_USED); + leftoff = OffsetNumberNext(leftoff); + ItemPointerSet(&(res->pointerData), lbknum, leftoff); + } else { + (void) PageAddItem(right, (Item) itup, IndexTupleSize(itup), + rightoff, LP_USED); + rightoff = OffsetNumberNext(rightoff); + ItemPointerSet(&(res->pointerData), rbknum, rightoff); + } + + if ((bufblock = BufferGetBlockNumber(buffer)) != P_ROOT) { + PageRestoreTempPage(left, p); + } + WriteBuffer(leftbuf); + WriteBuffer(rightbuf); + + /* + * Okay, the page is split. We have three things left to do: + * + * 1) Adjust any active scans on this index to cope with changes + * we introduced in its structure by splitting this page. + * + * 2) "Tighten" the bounding box of the pointer to the left + * page in the parent node in the tree, if any. Since we + * moved a bunch of stuff off the left page, we expect it + * to get smaller. This happens in the internal insertion + * routine. + * + * 3) Insert a pointer to the right page in the parent. This + * may cause the parent to split. If it does, we need to + * repeat steps one and two for each split node in the tree. + */ + + /* adjust active scans */ + rtadjscans(r, RTOP_SPLIT, bufblock, FirstOffsetNumber); + + tupDesc = r->rd_att; + ltup = (IndexTuple) index_formtuple(tupDesc, + (Datum *) &(v.spl_ldatum), isnull); + rtup = (IndexTuple) index_formtuple(tupDesc, + (Datum *) &(v.spl_rdatum), isnull); + pfree(isnull); + + /* set pointers to new child pages in the internal index tuples */ + ItemPointerSet(&(ltup->t_tid), lbknum, 1); + ItemPointerSet(&(rtup->t_tid), rbknum, 1); + + rtintinsert(r, stack, ltup, rtup, rtstate); + + pfree(ltup); + pfree(rtup); + + return (res); +} + +static void +rtintinsert(Relation r, + RTSTACK *stk, + IndexTuple ltup, + IndexTuple rtup, + RTSTATE *rtstate) +{ + IndexTuple old; + Buffer b; + Page p; + char *ldatum, *rdatum, *newdatum; + InsertIndexResult res; + + if (stk == (RTSTACK *) NULL) { + rtnewroot(r, ltup, rtup); + return; + } + + b = ReadBuffer(r, stk->rts_blk); + p = BufferGetPage(b); + old = (IndexTuple) PageGetItem(p, PageGetItemId(p, stk->rts_child)); + + /* + * This is a hack. Right now, we force rtree keys to be constant size. + * To fix this, need delete the old key and add both left and right + * for the two new pages. The insertion of left may force a split if + * the new left key is bigger than the old key. + */ + + if (IndexTupleSize(old) != IndexTupleSize(ltup)) + elog(WARN, "Variable-length rtree keys are not supported."); + + /* install pointer to left child */ + memmove(old, ltup,IndexTupleSize(ltup)); + + if (nospace(p, rtup)) { + newdatum = (((char *) ltup) + sizeof(IndexTupleData)); + rttighten(r, stk->rts_parent, newdatum, + (IndexTupleSize(ltup) - sizeof(IndexTupleData)), rtstate); + res = dosplit(r, b, stk->rts_parent, rtup, rtstate); + WriteBuffer(b); /* don't forget to release buffer! - 01/31/94 */ + pfree(res); + } else { + (void) PageAddItem(p, (Item) rtup, IndexTupleSize(rtup), + PageGetMaxOffsetNumber(p), LP_USED); + WriteBuffer(b); + ldatum = (((char *) ltup) + sizeof(IndexTupleData)); + rdatum = (((char *) rtup) + sizeof(IndexTupleData)); + newdatum = (char *) (*rtstate->unionFn)(ldatum, rdatum); + + rttighten(r, stk->rts_parent, newdatum, + (IndexTupleSize(rtup) - sizeof(IndexTupleData)), rtstate); + + pfree(newdatum); + } +} + +static void +rtnewroot(Relation r, IndexTuple lt, IndexTuple rt) +{ + Buffer b; + Page p; + + b = ReadBuffer(r, P_ROOT); + RTInitBuffer(b, 0); + p = BufferGetPage(b); + (void) PageAddItem(p, (Item) lt, IndexTupleSize(lt), + FirstOffsetNumber, LP_USED); + (void) PageAddItem(p, (Item) rt, IndexTupleSize(rt), + OffsetNumberNext(FirstOffsetNumber), LP_USED); + WriteBuffer(b); +} + +static void +picksplit(Relation r, + Page page, + SPLITVEC *v, + IndexTuple itup, + RTSTATE *rtstate) +{ + OffsetNumber maxoff; + OffsetNumber i, j; + IndexTuple item_1, item_2; + char *datum_alpha, *datum_beta; + char *datum_l, *datum_r; + char *union_d, *union_dl, *union_dr; + char *inter_d; + bool firsttime; + float size_alpha, size_beta, size_union, size_inter; + float size_waste, waste; + float size_l, size_r; + int nbytes; + OffsetNumber seed_1 = 0, seed_2 = 0; + OffsetNumber *left, *right; + + maxoff = PageGetMaxOffsetNumber(page); + + nbytes = (maxoff + 2) * sizeof(OffsetNumber); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + + firsttime = true; + waste = 0.0; + + for (i = FirstOffsetNumber; i < maxoff; i = OffsetNumberNext(i)) { + item_1 = (IndexTuple) PageGetItem(page, PageGetItemId(page, i)); + datum_alpha = ((char *) item_1) + sizeof(IndexTupleData); + for (j = OffsetNumberNext(i); j <= maxoff; j = OffsetNumberNext(j)) { + item_2 = (IndexTuple) PageGetItem(page, PageGetItemId(page, j)); + datum_beta = ((char *) item_2) + sizeof(IndexTupleData); + + /* compute the wasted space by unioning these guys */ + union_d = (char *)(rtstate->unionFn)(datum_alpha, datum_beta); + (rtstate->sizeFn)(union_d, &size_union); + inter_d = (char *)(rtstate->interFn)(datum_alpha, datum_beta); + (rtstate->sizeFn)(inter_d, &size_inter); + size_waste = size_union - size_inter; + + pfree(union_d); + + if (inter_d != (char *) NULL) + pfree(inter_d); + + /* + * are these a more promising split that what we've + * already seen? + */ + + if (size_waste > waste || firsttime) { + waste = size_waste; + seed_1 = i; + seed_2 = j; + firsttime = false; + } + } + } + + left = v->spl_left; + v->spl_nleft = 0; + right = v->spl_right; + v->spl_nright = 0; + + item_1 = (IndexTuple) PageGetItem(page, PageGetItemId(page, seed_1)); + datum_alpha = ((char *) item_1) + sizeof(IndexTupleData); + datum_l = (char *)(*rtstate->unionFn)(datum_alpha, datum_alpha); + (*rtstate->sizeFn)(datum_l, &size_l); + item_2 = (IndexTuple) PageGetItem(page, PageGetItemId(page, seed_2)); + datum_beta = ((char *) item_2) + sizeof(IndexTupleData); + datum_r = (char *)(*rtstate->unionFn)(datum_beta, datum_beta); + (*rtstate->sizeFn)(datum_r, &size_r); + + /* + * Now split up the regions between the two seeds. An important + * property of this split algorithm is that the split vector v + * has the indices of items to be split in order in its left and + * right vectors. We exploit this property by doing a merge in + * the code that actually splits the page. + * + * For efficiency, we also place the new index tuple in this loop. + * This is handled at the very end, when we have placed all the + * existing tuples and i == maxoff + 1. + */ + + maxoff = OffsetNumberNext(maxoff); + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) { + + /* + * If we've already decided where to place this item, just + * put it on the right list. Otherwise, we need to figure + * out which page needs the least enlargement in order to + * store the item. + */ + + if (i == seed_1) { + *left++ = i; + v->spl_nleft++; + continue; + } else if (i == seed_2) { + *right++ = i; + v->spl_nright++; + continue; + } + + /* okay, which page needs least enlargement? */ + if (i == maxoff) { + item_1 = itup; + } else { + item_1 = (IndexTuple) PageGetItem(page, PageGetItemId(page, i)); + } + + datum_alpha = ((char *) item_1) + sizeof(IndexTupleData); + union_dl = (char *)(*rtstate->unionFn)(datum_l, datum_alpha); + union_dr = (char *)(*rtstate->unionFn)(datum_r, datum_alpha); + (*rtstate->sizeFn)(union_dl, &size_alpha); + (*rtstate->sizeFn)(union_dr, &size_beta); + + /* pick which page to add it to */ + if (size_alpha - size_l < size_beta - size_r) { + pfree(datum_l); + pfree(union_dr); + datum_l = union_dl; + size_l = size_alpha; + *left++ = i; + v->spl_nleft++; + } else { + pfree(datum_r); + pfree(union_dl); + datum_r = union_dr; + size_r = size_alpha; + *right++ = i; + v->spl_nright++; + } + } + *left = *right = FirstOffsetNumber; /* sentinel value, see dosplit() */ + + v->spl_ldatum = datum_l; + v->spl_rdatum = datum_r; +} + +static void +RTInitBuffer(Buffer b, uint32 f) +{ + RTreePageOpaque opaque; + Page page; + Size pageSize; + + pageSize = BufferGetPageSize(b); + + page = BufferGetPage(b); + memset(page, 0, (int) pageSize); + PageInit(page, pageSize, sizeof(RTreePageOpaqueData)); + + opaque = (RTreePageOpaque) PageGetSpecialPointer(page); + opaque->flags = f; +} + +static OffsetNumber +choose(Relation r, Page p, IndexTuple it, RTSTATE *rtstate) +{ + OffsetNumber maxoff; + OffsetNumber i; + char *ud, *id; + char *datum; + float usize, dsize; + OffsetNumber which; + float which_grow; + + id = ((char *) it) + sizeof(IndexTupleData); + maxoff = PageGetMaxOffsetNumber(p); + which_grow = -1.0; + which = -1; + + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) { + datum = (char *) PageGetItem(p, PageGetItemId(p, i)); + datum += sizeof(IndexTupleData); + (*rtstate->sizeFn)(datum, &dsize); + ud = (char *) (*rtstate->unionFn)(datum, id); + (*rtstate->sizeFn)(ud, &usize); + pfree(ud); + if (which_grow < 0 || usize - dsize < which_grow) { + which = i; + which_grow = usize - dsize; + if (which_grow == 0) + break; + } + } + + return (which); +} + +static int +nospace(Page p, IndexTuple it) +{ + return (PageGetFreeSpace(p) < IndexTupleSize(it)); +} + +void +freestack(RTSTACK *s) +{ + RTSTACK *p; + + while (s != (RTSTACK *) NULL) { + p = s->rts_parent; + pfree(s); + s = p; + } +} + +char * +rtdelete(Relation r, ItemPointer tid) +{ + BlockNumber blkno; + OffsetNumber offnum; + Buffer buf; + Page page; + + /* must write-lock on delete */ + RelationSetLockForWrite(r); + + blkno = ItemPointerGetBlockNumber(tid); + offnum = ItemPointerGetOffsetNumber(tid); + + /* adjust any scans that will be affected by this deletion */ + rtadjscans(r, RTOP_DEL, blkno, offnum); + + /* delete the index tuple */ + buf = ReadBuffer(r, blkno); + page = BufferGetPage(buf); + + PageIndexTupleDelete(page, offnum); + + WriteBuffer(buf); + + /* XXX -- two-phase locking, don't release the write lock */ + return ((char *) NULL); +} + +static void initRtstate(RTSTATE *rtstate, Relation index) +{ + RegProcedure union_proc, size_proc, inter_proc; + func_ptr user_fn; + int pronargs; + + union_proc = index_getprocid(index, 1, RT_UNION_PROC); + size_proc = index_getprocid(index, 1, RT_SIZE_PROC); + inter_proc = index_getprocid(index, 1, RT_INTER_PROC); + fmgr_info(union_proc, &user_fn, &pronargs); + rtstate->unionFn = user_fn; + fmgr_info(size_proc, &user_fn, &pronargs); + rtstate->sizeFn = user_fn; + fmgr_info(inter_proc, &user_fn, &pronargs); + rtstate->interFn = user_fn; + return; +} + +#define RTDEBUG +#ifdef RTDEBUG +#include "utils/geo-decls.h" + +void +_rtdump(Relation r) +{ + Buffer buf; + Page page; + OffsetNumber offnum, maxoff; + BlockNumber blkno; + BlockNumber nblocks; + RTreePageOpaque po; + IndexTuple itup; + BlockNumber itblkno; + OffsetNumber itoffno; + char *datum; + char *itkey; + + nblocks = RelationGetNumberOfBlocks(r); + for (blkno = 0; blkno < nblocks; blkno++) { + buf = ReadBuffer(r, blkno); + page = BufferGetPage(buf); + po = (RTreePageOpaque) PageGetSpecialPointer(page); + maxoff = PageGetMaxOffsetNumber(page); + printf("Page %d maxoff %d <%s>\n", blkno, maxoff, + (po->flags & F_LEAF ? "LEAF" : "INTERNAL")); + + if (PageIsEmpty(page)) { + ReleaseBuffer(buf); + continue; + } + + for (offnum = FirstOffsetNumber; + offnum <= maxoff; + offnum = OffsetNumberNext(offnum)) { + itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum)); + itblkno = ItemPointerGetBlockNumber(&(itup->t_tid)); + itoffno = ItemPointerGetOffsetNumber(&(itup->t_tid)); + datum = ((char *) itup); + datum += sizeof(IndexTupleData); + itkey = (char *) box_out((BOX *) datum); + printf("\t[%d] size %d heap <%d,%d> key:%s\n", + offnum, IndexTupleSize(itup), itblkno, itoffno, itkey); + pfree(itkey); + } + + ReleaseBuffer(buf); + } +} +#endif /* defined RTDEBUG */ + diff --git a/src/backend/access/rtree/rtscan.c b/src/backend/access/rtree/rtscan.c new file mode 100644 index 00000000000..aa68f0db70b --- /dev/null +++ b/src/backend/access/rtree/rtscan.c @@ -0,0 +1,392 @@ +/*------------------------------------------------------------------------- + * + * rtscan.c-- + * routines to manage scans on index relations + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/rtree/Attic/rtscan.c,v 1.1.1.1 1996/07/09 06:21:13 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "c.h" +#include "postgres.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/rel.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/rtree.h" +#include "access/rtstrat.h" + +/* routines defined and used here */ +static void rtregscan(IndexScanDesc s); +static void rtdropscan(IndexScanDesc s); +static void rtadjone(IndexScanDesc s, int op, BlockNumber blkno, + OffsetNumber offnum); +static void adjuststack(RTSTACK *stk, BlockNumber blkno, + OffsetNumber offnum); +static void adjustiptr(IndexScanDesc s, ItemPointer iptr, + int op, BlockNumber blkno, OffsetNumber offnum); + +/* + * Whenever we start an rtree scan in a backend, we register it in private + * space. Then if the rtree index gets updated, we check all registered + * scans and adjust them if the tuple they point at got moved by the + * update. We only need to do this in private space, because when we update + * an rtree we have a write lock on the tree, so no other process can have + * any locks at all on it. A single transaction can have write and read + * locks on the same object, so that's why we need to handle this case. + */ + +typedef struct RTScanListData { + IndexScanDesc rtsl_scan; + struct RTScanListData *rtsl_next; +} RTScanListData; + +typedef RTScanListData *RTScanList; + +/* pointer to list of local scans on rtrees */ +static RTScanList RTScans = (RTScanList) NULL; + +IndexScanDesc +rtbeginscan(Relation r, + bool fromEnd, + uint16 nkeys, + ScanKey key) +{ + IndexScanDesc s; + + RelationSetLockForRead(r); + s = RelationGetIndexScan(r, fromEnd, nkeys, key); + rtregscan(s); + + return (s); +} + +void +rtrescan(IndexScanDesc s, bool fromEnd, ScanKey key) +{ + RTreeScanOpaque p; + RegProcedure internal_proc; + int i; + + if (!IndexScanIsValid(s)) { + elog(WARN, "rtrescan: invalid scan."); + return; + } + + /* + * Clear all the pointers. + */ + + ItemPointerSetInvalid(&s->previousItemData); + ItemPointerSetInvalid(&s->currentItemData); + ItemPointerSetInvalid(&s->nextItemData); + ItemPointerSetInvalid(&s->previousMarkData); + ItemPointerSetInvalid(&s->currentMarkData); + ItemPointerSetInvalid(&s->nextMarkData); + + /* + * Set flags. + */ + if (RelationGetNumberOfBlocks(s->relation) == 0) { + s->flags = ScanUnmarked; + } else if (fromEnd) { + s->flags = ScanUnmarked | ScanUncheckedPrevious; + } else { + s->flags = ScanUnmarked | ScanUncheckedNext; + } + + s->scanFromEnd = fromEnd; + + if (s->numberOfKeys > 0) { + memmove(s->keyData, + key, + s->numberOfKeys * sizeof(ScanKeyData)); + } + + p = (RTreeScanOpaque) s->opaque; + if (p != (RTreeScanOpaque) NULL) { + freestack(p->s_stack); + freestack(p->s_markstk); + p->s_stack = p->s_markstk = (RTSTACK *) NULL; + p->s_flags = 0x0; + } else { + /* initialize opaque data */ + p = (RTreeScanOpaque) palloc(sizeof(RTreeScanOpaqueData)); + p->s_internalKey = + (ScanKey) palloc(sizeof(ScanKeyData) * s->numberOfKeys); + p->s_stack = p->s_markstk = (RTSTACK *) NULL; + p->s_internalNKey = s->numberOfKeys; + p->s_flags = 0x0; + for (i = 0; i < s->numberOfKeys; i++) + p->s_internalKey[i].sk_argument = s->keyData[i].sk_argument; + s->opaque = p; + if (s->numberOfKeys > 0) { + + /* + * Scans on internal pages use different operators than they + * do on leaf pages. For example, if the user wants all boxes + * that exactly match (x1,y1,x2,y2), then on internal pages + * we need to find all boxes that contain (x1,y1,x2,y2). + */ + + for (i = 0; i < s->numberOfKeys; i++) { + internal_proc = RTMapOperator(s->relation, + s->keyData[i].sk_attno, + s->keyData[i].sk_procedure); + ScanKeyEntryInitialize(&(p->s_internalKey[i]), + s->keyData[i].sk_flags, + s->keyData[i].sk_attno, + internal_proc, + s->keyData[i].sk_argument); + } + } + } +} + +void +rtmarkpos(IndexScanDesc s) +{ + RTreeScanOpaque p; + RTSTACK *o, *n, *tmp; + + s->currentMarkData = s->currentItemData; + p = (RTreeScanOpaque) s->opaque; + if (p->s_flags & RTS_CURBEFORE) + p->s_flags |= RTS_MRKBEFORE; + else + p->s_flags &= ~RTS_MRKBEFORE; + + o = (RTSTACK *) NULL; + n = p->s_stack; + + /* copy the parent stack from the current item data */ + while (n != (RTSTACK *) NULL) { + tmp = (RTSTACK *) palloc(sizeof(RTSTACK)); + tmp->rts_child = n->rts_child; + tmp->rts_blk = n->rts_blk; + tmp->rts_parent = o; + o = tmp; + n = n->rts_parent; + } + + freestack(p->s_markstk); + p->s_markstk = o; +} + +void +rtrestrpos(IndexScanDesc s) +{ + RTreeScanOpaque p; + RTSTACK *o, *n, *tmp; + + s->currentItemData = s->currentMarkData; + p = (RTreeScanOpaque) s->opaque; + if (p->s_flags & RTS_MRKBEFORE) + p->s_flags |= RTS_CURBEFORE; + else + p->s_flags &= ~RTS_CURBEFORE; + + o = (RTSTACK *) NULL; + n = p->s_markstk; + + /* copy the parent stack from the current item data */ + while (n != (RTSTACK *) NULL) { + tmp = (RTSTACK *) palloc(sizeof(RTSTACK)); + tmp->rts_child = n->rts_child; + tmp->rts_blk = n->rts_blk; + tmp->rts_parent = o; + o = tmp; + n = n->rts_parent; + } + + freestack(p->s_stack); + p->s_stack = o; +} + +void +rtendscan(IndexScanDesc s) +{ + RTreeScanOpaque p; + + p = (RTreeScanOpaque) s->opaque; + + if (p != (RTreeScanOpaque) NULL) { + freestack(p->s_stack); + freestack(p->s_markstk); + } + + rtdropscan(s); + /* XXX don't unset read lock -- two-phase locking */ +} + +static void +rtregscan(IndexScanDesc s) +{ + RTScanList l; + + l = (RTScanList) palloc(sizeof(RTScanListData)); + l->rtsl_scan = s; + l->rtsl_next = RTScans; + RTScans = l; +} + +static void +rtdropscan(IndexScanDesc s) +{ + RTScanList l; + RTScanList prev; + + prev = (RTScanList) NULL; + + for (l = RTScans; + l != (RTScanList) NULL && l->rtsl_scan != s; + l = l->rtsl_next) { + prev = l; + } + + if (l == (RTScanList) NULL) + elog(WARN, "rtree scan list corrupted -- cannot find 0x%lx", s); + + if (prev == (RTScanList) NULL) + RTScans = l->rtsl_next; + else + prev->rtsl_next = l->rtsl_next; + + pfree(l); +} + +void +rtadjscans(Relation r, int op, BlockNumber blkno, OffsetNumber offnum) +{ + RTScanList l; + Oid relid; + + relid = r->rd_id; + for (l = RTScans; l != (RTScanList) NULL; l = l->rtsl_next) { + if (l->rtsl_scan->relation->rd_id == relid) + rtadjone(l->rtsl_scan, op, blkno, offnum); + } +} + +/* + * rtadjone() -- adjust one scan for update. + * + * By here, the scan passed in is on a modified relation. Op tells + * us what the modification is, and blkno and offind tell us what + * block and offset index were affected. This routine checks the + * current and marked positions, and the current and marked stacks, + * to see if any stored location needs to be changed because of the + * update. If so, we make the change here. + */ +static void +rtadjone(IndexScanDesc s, + int op, + BlockNumber blkno, + OffsetNumber offnum) +{ + RTreeScanOpaque so; + + adjustiptr(s, &(s->currentItemData), op, blkno, offnum); + adjustiptr(s, &(s->currentMarkData), op, blkno, offnum); + + so = (RTreeScanOpaque) s->opaque; + + if (op == RTOP_SPLIT) { + adjuststack(so->s_stack, blkno, offnum); + adjuststack(so->s_markstk, blkno, offnum); + } +} + +/* + * adjustiptr() -- adjust current and marked item pointers in the scan + * + * Depending on the type of update and the place it happened, we + * need to do nothing, to back up one record, or to start over on + * the same page. + */ +static void +adjustiptr(IndexScanDesc s, + ItemPointer iptr, + int op, + BlockNumber blkno, + OffsetNumber offnum) +{ + OffsetNumber curoff; + RTreeScanOpaque so; + + if (ItemPointerIsValid(iptr)) { + if (ItemPointerGetBlockNumber(iptr) == blkno) { + curoff = ItemPointerGetOffsetNumber(iptr); + so = (RTreeScanOpaque) s->opaque; + + switch (op) { + case RTOP_DEL: + /* back up one if we need to */ + if (curoff >= offnum) { + + if (curoff > FirstOffsetNumber) { + /* just adjust the item pointer */ + ItemPointerSet(iptr, blkno, OffsetNumberPrev(curoff)); + } else { + /* remember that we're before the current tuple */ + ItemPointerSet(iptr, blkno, FirstOffsetNumber); + if (iptr == &(s->currentItemData)) + so->s_flags |= RTS_CURBEFORE; + else + so->s_flags |= RTS_MRKBEFORE; + } + } + break; + + case RTOP_SPLIT: + /* back to start of page on split */ + ItemPointerSet(iptr, blkno, FirstOffsetNumber); + if (iptr == &(s->currentItemData)) + so->s_flags &= ~RTS_CURBEFORE; + else + so->s_flags &= ~RTS_MRKBEFORE; + break; + + default: + elog(WARN, "Bad operation in rtree scan adjust: %d", op); + } + } + } +} + +/* + * adjuststack() -- adjust the supplied stack for a split on a page in + * the index we're scanning. + * + * If a page on our parent stack has split, we need to back up to the + * beginning of the page and rescan it. The reason for this is that + * the split algorithm for rtrees doesn't order tuples in any useful + * way on a single page. This means on that a split, we may wind up + * looking at some heap tuples more than once. This is handled in the + * access method update code for heaps; if we've modified the tuple we + * are looking at already in this transaction, we ignore the update + * request. + */ +/*ARGSUSED*/ +static void +adjuststack(RTSTACK *stk, + BlockNumber blkno, + OffsetNumber offnum) +{ + while (stk != (RTSTACK *) NULL) { + if (stk->rts_blk == blkno) + stk->rts_child = FirstOffsetNumber; + + stk = stk->rts_parent; + } +} diff --git a/src/backend/access/rtree/rtstrat.c b/src/backend/access/rtree/rtstrat.c new file mode 100644 index 00000000000..c5d934a22a2 --- /dev/null +++ b/src/backend/access/rtree/rtstrat.c @@ -0,0 +1,239 @@ +/*------------------------------------------------------------------------- + * + * rtstrat.c-- + * strategy map data for rtrees. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/rtree/Attic/rtstrat.c,v 1.1.1.1 1996/07/09 06:21:13 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "c.h" + +#include "utils/rel.h" + +#include "storage/bufmgr.h" +#include "storage/bufpage.h" + +#include "access/istrat.h" +#include "access/rtree.h" + +/* + * Note: negate, commute, and negatecommute all assume that operators are + * ordered as follows in the strategy map: + * + * left, left-or-overlap, overlap, right-or-overlap, right, same, + * contains, contained-by + * + * The negate, commute, and negatecommute arrays are used by the planner + * to plan indexed scans over data that appears in the qualificiation in + * a boolean negation, or whose operands appear in the wrong order. For + * example, if the operator "<%" means "contains", and the user says + * + * where not rel.box <% "(10,10,20,20)"::box + * + * the planner can plan an index scan by noting that rtree indices have + * an operator in their operator class for negating <%. + * + * Similarly, if the user says something like + * + * where "(10,10,20,20)"::box <% rel.box + * + * the planner can see that the rtree index on rel.box has an operator in + * its opclass for commuting <%, and plan the scan using that operator. + * This added complexity in the access methods makes the planner a lot easier + * to write. + */ + +/* if a op b, what operator tells us if (not a op b)? */ +static StrategyNumber RTNegate[RTNStrategies] = { + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy + }; + +/* if a op_1 b, what is the operator op_2 such that b op_2 a? */ +static StrategyNumber RTCommute[RTNStrategies] = { + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy + }; + +/* if a op_1 b, what is the operator op_2 such that (b !op_2 a)? */ +static StrategyNumber RTNegateCommute[RTNStrategies] = { + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy, + InvalidStrategy + }; + +/* + * Now do the TermData arrays. These exist in case the user doesn't give + * us a full set of operators for a particular operator class. The idea + * is that by making multiple comparisons using any one of the supplied + * operators, we can decide whether two n-dimensional polygons are equal. + * For example, if a contains b and b contains a, we may conclude that + * a and b are equal. + * + * The presence of the TermData arrays in all this is a historical accident. + * Early in the development of the POSTGRES access methods, it was believed + * that writing functions was harder than writing arrays. This is wrong; + * TermData is hard to understand and hard to get right. In general, when + * someone populates a new operator class, the populate it completely. If + * Mike Hirohama had forced Cimarron Taylor to populate the strategy map + * for btree int2_ops completely in 1988, you wouldn't have to deal with + * all this now. Too bad for you. + * + * Since you can't necessarily do this in all cases (for example, you can't + * do it given only "intersects" or "disjoint"), TermData arrays for some + * operators don't appear below. + * + * Note that if you DO supply all the operators required in a given opclass + * by inserting them into the pg_opclass system catalog, you can get away + * without doing all this TermData stuff. Since the rtree code is intended + * to be a reference for access method implementors, I'm doing TermData + * correctly here. + * + * Note on style: these are all actually of type StrategyTermData, but + * since those have variable-length data at the end of the struct we can't + * properly initialize them if we declare them to be what they are. + */ + +/* if you only have "contained-by", how do you determine equality? */ +static uint16 RTContainedByTermData[] = { + 2, /* make two comparisons */ + RTContainedByStrategyNumber, /* use "a contained-by b" */ + 0x0, /* without any magic */ + RTContainedByStrategyNumber, /* then use contained-by, */ + SK_COMMUTE /* swapping a and b */ + }; + +/* if you only have "contains", how do you determine equality? */ +static uint16 RTContainsTermData[] = { + 2, /* make two comparisons */ + RTContainsStrategyNumber, /* use "a contains b" */ + 0x0, /* without any magic */ + RTContainsStrategyNumber, /* then use contains again, */ + SK_COMMUTE /* swapping a and b */ + }; + +/* now put all that together in one place for the planner */ +static StrategyTerm RTEqualExpressionData[] = { + (StrategyTerm) RTContainedByTermData, + (StrategyTerm) RTContainsTermData, + NULL + }; + +/* + * If you were sufficiently attentive to detail, you would go through + * the ExpressionData pain above for every one of the seven strategies + * we defined. I am not. Now we declare the StrategyEvaluationData + * structure that gets shipped around to help the planner and the access + * method decide what sort of scan it should do, based on (a) what the + * user asked for, (b) what operators are defined for a particular opclass, + * and (c) the reams of information we supplied above. + * + * The idea of all of this initialized data is to make life easier on the + * user when he defines a new operator class to use this access method. + * By filling in all the data, we let him get away with leaving holes in his + * operator class, and still let him use the index. The added complexity + * in the access methods just isn't worth the trouble, though. + */ + +static StrategyEvaluationData RTEvaluationData = { + RTNStrategies, /* # of strategies */ + (StrategyTransformMap) RTNegate, /* how to do (not qual) */ + (StrategyTransformMap) RTCommute, /* how to swap operands */ + (StrategyTransformMap) RTNegateCommute, /* how to do both */ + { + NULL, /* express left */ + NULL, /* express overleft */ + NULL, /* express over */ + NULL, /* express overright */ + NULL, /* express right */ + (StrategyExpression) RTEqualExpressionData, /* express same */ + NULL, /* express contains */ + NULL, /* express contained-by */ + NULL, + NULL, + NULL + } +}; + +/* + * Okay, now something peculiar to rtrees that doesn't apply to most other + * indexing structures: When we're searching a tree for a given value, we + * can't do the same sorts of comparisons on internal node entries as we + * do at leaves. The reason is that if we're looking for (say) all boxes + * that are the same as (0,0,10,10), then we need to find all leaf pages + * that overlap that region. So internally we search for overlap, and at + * the leaf we search for equality. + * + * This array maps leaf search operators to the internal search operators. + * We assume the normal ordering on operators: + * + * left, left-or-overlap, overlap, right-or-overlap, right, same, + * contains, contained-by + */ +static StrategyNumber RTOperMap[RTNStrategies] = { + RTOverLeftStrategyNumber, + RTOverLeftStrategyNumber, + RTOverlapStrategyNumber, + RTOverRightStrategyNumber, + RTOverRightStrategyNumber, + RTContainsStrategyNumber, + RTContainsStrategyNumber, + RTOverlapStrategyNumber + }; + +StrategyNumber +RelationGetRTStrategy(Relation r, + AttrNumber attnum, + RegProcedure proc) +{ + return (RelationGetStrategy(r, attnum, &RTEvaluationData, proc)); +} + +bool +RelationInvokeRTStrategy(Relation r, + AttrNumber attnum, + StrategyNumber s, + Datum left, + Datum right) +{ + return (RelationInvokeStrategy(r, &RTEvaluationData, attnum, s, + left, right)); +} + +RegProcedure +RTMapOperator(Relation r, + AttrNumber attnum, + RegProcedure proc) +{ + StrategyNumber procstrat; + StrategyMap strategyMap; + + procstrat = RelationGetRTStrategy(r, attnum, proc); + strategyMap = IndexStrategyGetStrategyMap(RelationGetIndexStrategy(r), + RTNStrategies, + attnum); + + return (strategyMap->entry[RTOperMap[procstrat - 1] - 1].sk_procedure); +} diff --git a/src/backend/access/rtscan.h b/src/backend/access/rtscan.h new file mode 100644 index 00000000000..a928303f3f3 --- /dev/null +++ b/src/backend/access/rtscan.h @@ -0,0 +1,17 @@ +/*------------------------------------------------------------------------- + * + * rtscan.h-- + * routines defined in access/rtree/rtscan.c + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: rtscan.h,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef RTSCAN_H + +void rtadjscans(Relation r, int op, BlockNumber blkno, OffsetNumber offnum); + +#endif /* RTSCAN_H */ diff --git a/src/backend/access/rtstrat.h b/src/backend/access/rtstrat.h new file mode 100644 index 00000000000..5b439e7b338 --- /dev/null +++ b/src/backend/access/rtstrat.h @@ -0,0 +1,18 @@ +/*------------------------------------------------------------------------- + * + * rtstrat.h-- + * routines defined in access/rtree/rtstrat.c + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: rtstrat.h,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef RTSTRAT_H + +extern RegProcedure RTMapOperator(Relation r, AttrNumber attnum, + RegProcedure proc); + +#endif /* RTSTRAT_H */ diff --git a/src/backend/access/sdir.h b/src/backend/access/sdir.h new file mode 100644 index 00000000000..030007d39c9 --- /dev/null +++ b/src/backend/access/sdir.h @@ -0,0 +1,57 @@ +/*------------------------------------------------------------------------- + * + * sdir.h-- + * POSTGRES scan direction definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: sdir.h,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef SDIR_H +#define SDIR_H + +#include "c.h" + +/* + * ScanDirection was an int8 for no apparent reason. I kept the original + * values because I'm not sure if I'll break anything otherwise. -ay 2/95 + */ +typedef enum ScanDirection { + BackwardScanDirection = -1, + NoMovementScanDirection = 0, + ForwardScanDirection = 1 +} ScanDirection; + +/* + * ScanDirectionIsValid -- + * True iff scan direciton is valid. + */ +#define ScanDirectionIsValid(direction) \ + ((bool) (BackwardScanDirection <= direction && \ + direction <= ForwardScanDirection)) + +/* + * ScanDirectionIsBackward -- + * True iff scan direciton is backward. + */ +#define ScanDirectionIsBackward(direction) \ + ((bool) (direction == BackwardScanDirection)) + +/* + * ScanDirectionIsNoMovement -- + * True iff scan direciton indicates no movement. + */ +#define ScanDirectionIsNoMovement(direction) \ + ((bool) (direction == NoMovementScanDirection)) + +/* + * ScanDirectionIsForward -- + * True iff scan direciton is forward. + */ +#define ScanDirectionIsForward(direction) \ + ((bool) (direction == ForwardScanDirection)) + +#endif /* SDIR_H */ diff --git a/src/backend/access/skey.h b/src/backend/access/skey.h new file mode 100644 index 00000000000..3cadf348f42 --- /dev/null +++ b/src/backend/access/skey.h @@ -0,0 +1,52 @@ +/*------------------------------------------------------------------------- + * + * skey.h-- + * POSTGRES scan key definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: skey.h,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + * + * Note: + * Needs more accessor/assignment routines. + *------------------------------------------------------------------------- + */ +#ifndef SKEY_H +#define SKEY_H + +#include "postgres.h" +#include "access/attnum.h" + + +typedef struct ScanKeyData { + bits16 sk_flags; /* flags */ + AttrNumber sk_attno; /* domain number */ + RegProcedure sk_procedure; /* procedure OID */ + func_ptr sk_func; + int32 sk_nargs; + Datum sk_argument; /* data to compare */ +} ScanKeyData; + +typedef ScanKeyData *ScanKey; + + +#define SK_ISNULL 0x1 +#define SK_UNARY 0x2 +#define SK_NEGATE 0x4 +#define SK_COMMUTE 0x8 + +#define ScanUnmarked 0x01 +#define ScanUncheckedPrevious 0x02 +#define ScanUncheckedNext 0x04 + + +/* + * prototypes for functions in access/common/scankey.c + */ +extern void ScanKeyEntrySetIllegal(ScanKey entry); +extern void ScanKeyEntryInitialize(ScanKey entry, bits16 flags, + AttrNumber attributeNumber, RegProcedure procedure, Datum argument); + +#endif /* SKEY_H */ diff --git a/src/backend/access/strat.h b/src/backend/access/strat.h new file mode 100644 index 00000000000..4ddb2190d88 --- /dev/null +++ b/src/backend/access/strat.h @@ -0,0 +1,86 @@ +/*------------------------------------------------------------------------- + * + * strat.h-- + * index strategy type definitions + * (separated out from original istrat.h to avoid circular refs) + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: strat.h,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef STRAT_H +#define STRAT_H + +#include "postgres.h" +#include "access/attnum.h" +#include "access/skey.h" + +typedef uint16 StrategyNumber; + +#define InvalidStrategy 0 + +typedef struct StrategyTransformMapData { + StrategyNumber strategy[1]; /* VARIABLE LENGTH ARRAY */ +} StrategyTransformMapData; /* VARIABLE LENGTH STRUCTURE */ + +typedef StrategyTransformMapData *StrategyTransformMap; + +typedef struct StrategyOperatorData { + StrategyNumber strategy; + bits16 flags; /* scan qualification flags h/skey.h */ +} StrategyOperatorData; + +typedef StrategyOperatorData *StrategyOperator; + +typedef struct StrategyTermData { /* conjunctive term */ + uint16 degree; + StrategyOperatorData operatorData[1]; /* VARIABLE LENGTH */ +} StrategyTermData; /* VARIABLE LENGTH STRUCTURE */ + +typedef StrategyTermData *StrategyTerm; + +typedef struct StrategyExpressionData { /* disjunctive normal form */ + StrategyTerm term[1]; /* VARIABLE LENGTH ARRAY */ +} StrategyExpressionData; /* VARIABLE LENGTH STRUCTURE */ + +typedef StrategyExpressionData *StrategyExpression; + +typedef struct StrategyEvaluationData { + StrategyNumber maxStrategy; + StrategyTransformMap negateTransform; + StrategyTransformMap commuteTransform; + StrategyTransformMap negateCommuteTransform; + StrategyExpression expression[12]; /* XXX VARIABLE LENGTH */ +} StrategyEvaluationData; /* VARIABLE LENGTH STRUCTURE */ + +typedef StrategyEvaluationData *StrategyEvaluation; + +/* + * StrategyTransformMapIsValid -- + * Returns true iff strategy transformation map is valid. + */ +#define StrategyTransformMapIsValid(transform) PointerIsValid(transform) + + +#ifndef CorrectStrategies /* XXX this should be removable */ +#define AMStrategies(foo) 12 +#else /* !defined(CorrectStrategies) */ +#define AMStrategies(foo) (foo) +#endif /* !defined(CorrectStrategies) */ + +typedef struct StrategyMapData { + ScanKeyData entry[1]; /* VARIABLE LENGTH ARRAY */ +} StrategyMapData; /* VARIABLE LENGTH STRUCTURE */ + +typedef StrategyMapData *StrategyMap; + +typedef struct IndexStrategyData { + StrategyMapData strategyMapData[1]; /* VARIABLE LENGTH ARRAY */ +} IndexStrategyData; /* VARIABLE LENGTH STRUCTURE */ + +typedef IndexStrategyData *IndexStrategy; + +#endif /*STRAT_H */ diff --git a/src/backend/access/transam.h b/src/backend/access/transam.h new file mode 100644 index 00000000000..0f5a9724dc0 --- /dev/null +++ b/src/backend/access/transam.h @@ -0,0 +1,213 @@ +/*------------------------------------------------------------------------- + * + * transam.h-- + * postgres transaction access method support code header + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: transam.h,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + * NOTES + * Transaction System Version 101 now support proper oid + * generation and recording in the variable relation. + * + *------------------------------------------------------------------------- + */ +#ifndef TRANSAM_H +#define TRANSAM_H + +/* ---------------- + * transaction system version id + * + * this is stored on the first page of the log, time and variable + * relations on the first 4 bytes. This is so that if we improve + * the format of the transaction log after postgres version 2, then + * people won't have to rebuild their databases. + * + * TRANS_SYSTEM_VERSION 100 means major version 1 minor version 0. + * Two databases with the same major version should be compatible, + * even if their minor versions differ. + * ---------------- + */ +#define TRANS_SYSTEM_VERSION 101 + +/* ---------------- + * transaction id status values + * + * someday we will use "11" = 3 = XID_INVALID to mean the + * starting of run-length encoded log data. + * ---------------- + */ +#define XID_COMMIT 2 /* transaction commited */ +#define XID_ABORT 1 /* transaction aborted */ +#define XID_INPROGRESS 0 /* transaction in progress */ +#define XID_INVALID 3 /* other */ + +typedef unsigned char XidStatus; /* (2 bits) */ + +/* ---------------- + * BitIndexOf computes the index of the Nth xid on a given block + * ---------------- + */ +#define BitIndexOf(N) ((N) * 2) + +/* ---------------- + * transaction page definitions + * ---------------- + */ +#define TP_DataSize BLCKSZ +#define TP_NumXidStatusPerBlock (TP_DataSize * 4) +#define TP_NumTimePerBlock (TP_DataSize / 4) + +/* ---------------- + * LogRelationContents structure + * + * This structure describes the storage of the data in the + * first 128 bytes of the log relation. This storage is never + * used for transaction status because transaction id's begin + * their numbering at 512. + * + * The first 4 bytes of this relation store the version + * number of the transction system. + * ---------------- + */ +typedef struct LogRelationContentsData { + int TransSystemVersion; +} LogRelationContentsData; + +typedef LogRelationContentsData *LogRelationContents; + +/* ---------------- + * TimeRelationContents structure + * + * This structure describes the storage of the data in the + * first 2048 bytes of the time relation. This storage is never + * used for transaction commit times because transaction id's begin + * their numbering at 512. + * + * The first 4 bytes of this relation store the version + * number of the transction system. + * ---------------- + */ +typedef struct TimeRelationContentsData { + int TransSystemVersion; +} TimeRelationContentsData; + +typedef TimeRelationContentsData *TimeRelationContents; + +/* ---------------- + * VariableRelationContents structure + * + * The variable relation is a special "relation" which + * is used to store various system "variables" persistantly. + * Unlike other relations in the system, this relation + * is updated in place whenever the variables change. + * + * The first 4 bytes of this relation store the version + * number of the transction system. + * + * Currently, the relation has only one page and the next + * available xid, the last committed xid and the next + * available oid are stored there. + * ---------------- + */ +typedef struct VariableRelationContentsData { + int TransSystemVersion; + TransactionId nextXidData; + TransactionId lastXidData; + Oid nextOid; +} VariableRelationContentsData; + +typedef VariableRelationContentsData *VariableRelationContents; + +/* ---------------- + * extern declarations + * ---------------- + */ + +/* + * prototypes for functions in transam/transam.c + */ +extern int RecoveryCheckingEnabled(); +extern void SetRecoveryCheckingEnabled(bool state); +extern bool TransactionLogTest(TransactionId transactionId, XidStatus status); +extern void TransactionLogUpdate(TransactionId transactionId, + XidStatus status); +extern AbsoluteTime TransactionIdGetCommitTime(TransactionId transactionId); +extern void TransRecover(Relation logRelation); +extern void InitializeTransactionLog(); +extern bool TransactionIdDidCommit(TransactionId transactionId); +extern bool TransactionIdDidAbort(TransactionId transactionId); +extern bool TransactionIdIsInProgress(TransactionId transactionId); +extern void TransactionIdCommit(TransactionId transactionId); +extern void TransactionIdAbort(TransactionId transactionId); +extern void TransactionIdSetInProgress(TransactionId transactionId); + +/* in transam/transsup.c */ +extern void AmiTransactionOverride(bool flag); +extern void TransComputeBlockNumber(Relation relation, + TransactionId transactionId, BlockNumber *blockNumberOutP); +extern XidStatus TransBlockGetLastTransactionIdStatus(Block tblock, + TransactionId baseXid, TransactionId *returnXidP); +extern XidStatus TransBlockGetXidStatus(Block tblock, + TransactionId transactionId); +extern void TransBlockSetXidStatus(Block tblock, + TransactionId transactionId, XidStatus xstatus); +extern AbsoluteTime TransBlockGetCommitTime(Block tblock, + TransactionId transactionId); +extern void TransBlockSetCommitTime(Block tblock, + TransactionId transactionId, AbsoluteTime commitTime); +extern XidStatus TransBlockNumberGetXidStatus(Relation relation, + BlockNumber blockNumber, TransactionId xid, bool *failP); +extern void TransBlockNumberSetXidStatus(Relation relation, + BlockNumber blockNumber, TransactionId xid, XidStatus xstatus, + bool *failP); +extern AbsoluteTime TransBlockNumberGetCommitTime(Relation relation, + BlockNumber blockNumber, TransactionId xid, bool *failP); +extern void TransBlockNumberSetCommitTime(Relation relation, + BlockNumber blockNumber, TransactionId xid, AbsoluteTime xtime, + bool *failP); +extern void TransGetLastRecordedTransaction(Relation relation, + TransactionId xid, bool *failP); + +/* in transam/varsup.c */ +extern void VariableRelationGetNextXid(TransactionId *xidP); +extern void VariableRelationGetLastXid(TransactionId *xidP); +extern void VariableRelationPutNextXid(TransactionId xid); +extern void VariableRelationPutLastXid(TransactionId xid); +extern void VariableRelationGetNextOid(Oid *oid_return); +extern void VariableRelationPutNextOid(Oid *oidP); +extern void GetNewTransactionId(TransactionId *xid); +extern void UpdateLastCommittedXid(TransactionId xid); +extern void GetNewObjectIdBlock(Oid *oid_return, int oid_block_size); +extern void GetNewObjectId(Oid *oid_return); + +/* ---------------- + * global variable extern declarations + * ---------------- + */ + +/* in transam.c */ +extern Relation LogRelation; +extern Relation TimeRelation; +extern Relation VariableRelation; + +extern TransactionId cachedGetCommitTimeXid; +extern AbsoluteTime cachedGetCommitTime; +extern TransactionId cachedTestXid; +extern XidStatus cachedTestXidStatus; + +extern TransactionId NullTransactionId; +extern TransactionId AmiTransactionId; +extern TransactionId FirstTransactionId; + +extern int RecoveryCheckingEnableState; + +/* in transsup.c */ +extern bool AMI_OVERRIDE; + +/* in varsup.c */ +extern int OidGenLockId; + +#endif /* TRAMSAM_H */ diff --git a/src/backend/access/transam/Makefile.inc b/src/backend/access/transam/Makefile.inc new file mode 100644 index 00000000000..c4f5b95a0ae --- /dev/null +++ b/src/backend/access/transam/Makefile.inc @@ -0,0 +1,14 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for access/transam +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/access/transam/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:13 scrappy Exp $ +# +#------------------------------------------------------------------------- + +SUBSRCS+= transam.c transsup.c varsup.c xact.c xid.c diff --git a/src/backend/access/transam/transam.c b/src/backend/access/transam/transam.c new file mode 100644 index 00000000000..b3789a8c2c5 --- /dev/null +++ b/src/backend/access/transam/transam.c @@ -0,0 +1,675 @@ +/*------------------------------------------------------------------------- + * + * transam.c-- + * postgres transaction log/time interface routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/transam/transam.c,v 1.1.1.1 1996/07/09 06:21:13 scrappy Exp $ + * + * NOTES + * This file contains the high level access-method interface to the + * transaction system. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "machine.h" /* in port/ directory (needed for BLCKSZ) */ + +#include "access/heapam.h" +#include "storage/buf.h" +#include "storage/bufmgr.h" + +#include "utils/memutils.h" +#include "utils/mcxt.h" +#include "utils/rel.h" +#include "utils/elog.h" + +#include "utils/nabstime.h" +#include "catalog/catname.h" + +#include "access/transam.h" +#include "access/xact.h" +#include "commands/vacuum.h" /* for VacuumRunning */ + +/* ---------------- + * global variables holding pointers to relations used + * by the transaction system. These are initialized by + * InitializeTransactionLog(). + * ---------------- + */ + +Relation LogRelation = (Relation) NULL; +Relation TimeRelation = (Relation) NULL; +Relation VariableRelation = (Relation) NULL; + +/* ---------------- + * global variables holding cached transaction id's and statuses. + * ---------------- + */ +TransactionId cachedGetCommitTimeXid; +AbsoluteTime cachedGetCommitTime; +TransactionId cachedTestXid; +XidStatus cachedTestXidStatus; + +/* ---------------- + * transaction system constants + * ---------------- + */ +/* ---------------------------------------------------------------- + * transaction system constants + * + * read the comments for GetNewTransactionId in order to + * understand the initial values for AmiTransactionId and + * FirstTransactionId. -cim 3/23/90 + * ---------------------------------------------------------------- + */ +TransactionId NullTransactionId = (TransactionId) 0; + +TransactionId AmiTransactionId = (TransactionId) 512; + +TransactionId FirstTransactionId = (TransactionId) 514; + +/* ---------------- + * transaction recovery state variables + * + * When the transaction system is initialized, we may + * need to do recovery checking. This decision is decided + * by the postmaster or the user by supplying the backend + * with a special flag. In general, we want to do recovery + * checking whenever we are running without a postmaster + * or when the number of backends running under the postmaster + * goes from zero to one. -cim 3/21/90 + * ---------------- + */ +int RecoveryCheckingEnableState = 0; + +/* ------------------ + * spinlock for oid generation + * ----------------- + */ +extern int OidGenLockId; + +/* ---------------- + * globals that must be reset at abort + * ---------------- + */ +extern bool BuildingBtree; + + +/* ---------------- + * recovery checking accessors + * ---------------- + */ +int +RecoveryCheckingEnabled() +{ + return RecoveryCheckingEnableState; +} + +void +SetRecoveryCheckingEnabled(bool state) +{ + RecoveryCheckingEnableState = (state == true); +} + +/* ---------------------------------------------------------------- + * postgres log/time access method interface + * + * TransactionLogTest + * TransactionLogUpdate + * ======== + * these functions do work for the interface + * functions - they search/retrieve and append/update + * information in the log and time relations. + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * TransactionLogTest + * -------------------------------- + */ + +bool /* true/false: does transaction id have specified status? */ +TransactionLogTest(TransactionId transactionId, /* transaction id to test */ + XidStatus status) /* transaction status */ +{ + BlockNumber blockNumber; + XidStatus xidstatus; /* recorded status of xid */ + bool fail = false; /* success/failure */ + + /* ---------------- + * during initialization consider all transactions + * as having been committed + * ---------------- + */ + if (! RelationIsValid(LogRelation)) + return (bool) (status == XID_COMMIT); + + /* ---------------- + * before going to the buffer manager, check our single + * item cache to see if we didn't just check the transaction + * status a moment ago. + * ---------------- + */ + if (TransactionIdEquals(transactionId, cachedTestXid)) + return (bool) + (status == cachedTestXidStatus); + + /* ---------------- + * compute the item pointer corresponding to the + * page containing our transaction id. We save the item in + * our cache to speed up things if we happen to ask for the + * same xid's status more than once. + * ---------------- + */ + TransComputeBlockNumber(LogRelation, transactionId, &blockNumber); + xidstatus = TransBlockNumberGetXidStatus(LogRelation, + blockNumber, + transactionId, + &fail); + + if (! fail) { + TransactionIdStore(transactionId, &cachedTestXid); + cachedTestXidStatus = xidstatus; + return (bool) + (status == xidstatus); + } + + /* ---------------- + * here the block didn't contain the information we wanted + * ---------------- + */ + elog(WARN, "TransactionLogTest: failed to get xidstatus"); + + /* + * so lint is happy... + */ + return(false); +} + +/* -------------------------------- + * TransactionLogUpdate + * -------------------------------- + */ +void +TransactionLogUpdate(TransactionId transactionId, /* trans id to update */ + XidStatus status) /* new trans status */ +{ + BlockNumber blockNumber; + bool fail = false; /* success/failure */ + AbsoluteTime currentTime; /* time of this transaction */ + + /* ---------------- + * during initialization we don't record any updates. + * ---------------- + */ + if (! RelationIsValid(LogRelation)) + return; + + /* ---------------- + * get the transaction commit time + * ---------------- + */ + currentTime = getSystemTime(); + + /* ---------------- + * update the log relation + * ---------------- + */ + TransComputeBlockNumber(LogRelation, transactionId, &blockNumber); + TransBlockNumberSetXidStatus(LogRelation, + blockNumber, + transactionId, + status, + &fail); + + /* ---------------- + * update (invalidate) our single item TransactionLogTest cache. + * ---------------- + */ + TransactionIdStore(transactionId, &cachedTestXid); + cachedTestXidStatus = status; + + /* ---------------- + * now we update the time relation, if necessary + * (we only record commit times) + * ---------------- + */ + if (RelationIsValid(TimeRelation) && status == XID_COMMIT) { + TransComputeBlockNumber(TimeRelation, transactionId, &blockNumber); + TransBlockNumberSetCommitTime(TimeRelation, + blockNumber, + transactionId, + currentTime, + &fail); + /* ---------------- + * update (invalidate) our single item GetCommitTime cache. + * ---------------- + */ + TransactionIdStore(transactionId, &cachedGetCommitTimeXid); + cachedGetCommitTime = currentTime; + } + + /* ---------------- + * now we update the "last committed transaction" field + * in the variable relation if we are recording a commit. + * ---------------- + */ + if (RelationIsValid(VariableRelation) && status == XID_COMMIT) + UpdateLastCommittedXid(transactionId); +} + +/* -------------------------------- + * TransactionIdGetCommitTime + * -------------------------------- + */ + +AbsoluteTime /* commit time of transaction id */ +TransactionIdGetCommitTime(TransactionId transactionId) /* transaction id to test */ +{ + BlockNumber blockNumber; + AbsoluteTime commitTime; /* commit time */ + bool fail = false; /* success/failure */ + + /* ---------------- + * return invalid if we aren't running yet... + * ---------------- + */ + if (! RelationIsValid(TimeRelation)) + return INVALID_ABSTIME; + + /* ---------------- + * before going to the buffer manager, check our single + * item cache to see if we didn't just get the commit time + * a moment ago. + * ---------------- + */ + if (TransactionIdEquals(transactionId, cachedGetCommitTimeXid)) + return cachedGetCommitTime; + + /* ---------------- + * compute the item pointer corresponding to the + * page containing our transaction commit time + * ---------------- + */ + TransComputeBlockNumber(TimeRelation, transactionId, &blockNumber); + commitTime = TransBlockNumberGetCommitTime(TimeRelation, + blockNumber, + transactionId, + &fail); + + /* ---------------- + * update our cache and return the transaction commit time + * ---------------- + */ + if (! fail) { + TransactionIdStore(transactionId, &cachedGetCommitTimeXid); + cachedGetCommitTime = commitTime; + return commitTime; + } else + return INVALID_ABSTIME; +} + +/* ---------------------------------------------------------------- + * transaction recovery code + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * TransRecover + * + * preform transaction recovery checking. + * + * Note: this should only be preformed if no other backends + * are running. This is known by the postmaster and + * conveyed by the postmaster passing a "do recovery checking" + * flag to the backend. + * + * here we get the last recorded transaction from the log, + * get the "last" and "next" transactions from the variable relation + * and then preform some integrity tests: + * + * 1) No transaction may exist higher then the "next" available + * transaction recorded in the variable relation. If this is the + * case then it means either the log or the variable relation + * has become corrupted. + * + * 2) The last committed transaction may not be higher then the + * next available transaction for the same reason. + * + * 3) The last recorded transaction may not be lower then the + * last committed transaction. (the reverse is ok - it means + * that some transactions have aborted since the last commit) + * + * Here is what the proper situation looks like. The line + * represents the data stored in the log. 'c' indicates the + * transaction was recorded as committed, 'a' indicates an + * abortted transaction and '.' represents information not + * recorded. These may correspond to in progress transactions. + * + * c c a c . . a . . . . . . . . . . + * | | + * last next + * + * Since "next" is only incremented by GetNewTransactionId() which + * is called when transactions are started. Hence if there + * are commits or aborts after "next", then it means we committed + * or aborted BEFORE we started the transaction. This is the + * rational behind constraint (1). + * + * Likewise, "last" should never greater then "next" for essentially + * the same reason - it would imply we committed before we started. + * This is the reasoning for (2). + * + * (3) implies we may never have a situation such as: + * + * c c a c . . a c . . . . . . . . . + * | | + * last next + * + * where there is a 'c' greater then "last". + * + * Recovery checking is more difficult in the case where + * several backends are executing concurrently because the + * transactions may be executing in the other backends. + * So, we only do recovery stuff when the backend is explicitly + * passed a flag on the command line. + * -------------------------------- + */ +void +TransRecover(Relation logRelation) +{ +#if 0 + /* ---------------- + * first get the last recorded transaction in the log. + * ---------------- + */ + TransGetLastRecordedTransaction(logRelation, logLastXid, &fail); + if (fail == true) + elog(WARN, "TransRecover: failed TransGetLastRecordedTransaction"); + + /* ---------------- + * next get the "last" and "next" variables + * ---------------- + */ + VariableRelationGetLastXid(&varLastXid); + VariableRelationGetNextXid(&varNextXid); + + /* ---------------- + * intregity test (1) + * ---------------- + */ + if (TransactionIdIsLessThan(varNextXid, logLastXid)) + elog(WARN, "TransRecover: varNextXid < logLastXid"); + + /* ---------------- + * intregity test (2) + * ---------------- + */ + + /* ---------------- + * intregity test (3) + * ---------------- + */ + + /* ---------------- + * here we have a valid " + * + * **** RESUME HERE **** + * ---------------- + */ + varNextXid = TransactionIdDup(varLastXid); + TransactionIdIncrement(&varNextXid); + + VarPut(var, VAR_PUT_LASTXID, varLastXid); + VarPut(var, VAR_PUT_NEXTXID, varNextXid); +#endif +} + +/* ---------------------------------------------------------------- + * Interface functions + * + * InitializeTransactionLog + * ======== + * this function (called near cinit) initializes + * the transaction log, time and variable relations. + * + * TransactionId DidCommit + * TransactionId DidAbort + * TransactionId IsInProgress + * ======== + * these functions test the transaction status of + * a specified transaction id. + * + * TransactionId Commit + * TransactionId Abort + * TransactionId SetInProgress + * ======== + * these functions set the transaction status + * of the specified xid. TransactionIdCommit() also + * records the current time in the time relation + * and updates the variable relation counter. + * + * ---------------------------------------------------------------- + */ + +/* + * InitializeTransactionLog -- + * Initializes transaction logging. + */ +void +InitializeTransactionLog() +{ + Relation logRelation; + Relation timeRelation; + MemoryContext oldContext; + + /* ---------------- + * don't do anything during bootstrapping + * ---------------- + */ + if (AMI_OVERRIDE) + return; + + /* ---------------- + * disable the transaction system so the access methods + * don't interfere during initialization. + * ---------------- + */ + OverrideTransactionSystem(true); + + /* ---------------- + * make sure allocations occur within the top memory context + * so that our log management structures are protected from + * garbage collection at the end of every transaction. + * ---------------- + */ + oldContext = MemoryContextSwitchTo(TopMemoryContext); + + /* ---------------- + * first open the log and time relations + * (these are created by amiint so they are guaranteed to exist) + * ---------------- + */ + logRelation = heap_openr(LogRelationName); + timeRelation = heap_openr(TimeRelationName); + VariableRelation = heap_openr(VariableRelationName); + /* ---------------- + * XXX TransactionLogUpdate requires that LogRelation + * and TimeRelation are valid so we temporarily set + * them so we can initialize things properly. + * This could be done cleaner. + * ---------------- + */ + LogRelation = logRelation; + TimeRelation = timeRelation; + + /* ---------------- + * if we have a virgin database, we initialize the log and time + * relation by committing the AmiTransactionId (id 512) and we + * initialize the variable relation by setting the next available + * transaction id to FirstTransactionId (id 514). OID initialization + * happens as a side effect of bootstrapping in varsup.c. + * ---------------- + */ + SpinAcquire(OidGenLockId); + if (!TransactionIdDidCommit(AmiTransactionId)) { + + /* ---------------- + * SOMEDAY initialize the information stored in + * the headers of the log/time/variable relations. + * ---------------- + */ + TransactionLogUpdate(AmiTransactionId, XID_COMMIT); + VariableRelationPutNextXid(FirstTransactionId); + + } else if (RecoveryCheckingEnabled()) { + /* ---------------- + * if we have a pre-initialized database and if the + * perform recovery checking flag was passed then we + * do our database integrity checking. + * ---------------- + */ + TransRecover(logRelation); + } + LogRelation = (Relation) NULL; + TimeRelation = (Relation) NULL; + SpinRelease(OidGenLockId); + + /* ---------------- + * now re-enable the transaction system + * ---------------- + */ + OverrideTransactionSystem(false); + + /* ---------------- + * instantiate the global variables + * ---------------- + */ + LogRelation = logRelation; + TimeRelation = timeRelation; + + /* ---------------- + * restore the memory context to the previous context + * before we return from initialization. + * ---------------- + */ + MemoryContextSwitchTo(oldContext); +} + +/* -------------------------------- + * TransactionId DidCommit + * TransactionId DidAbort + * TransactionId IsInProgress + * -------------------------------- + */ + +/* + * TransactionIdDidCommit -- + * True iff transaction associated with the identifier did commit. + * + * Note: + * Assumes transaction identifier is valid. + */ +bool /* true if given transaction committed */ +TransactionIdDidCommit(TransactionId transactionId) +{ + if (AMI_OVERRIDE) + return true; + + return + TransactionLogTest(transactionId, XID_COMMIT); +} + +/* + * TransactionIdDidAborted -- + * True iff transaction associated with the identifier did abort. + * + * Note: + * Assumes transaction identifier is valid. + * XXX Is this unneeded? + */ +bool /* true if given transaction aborted */ +TransactionIdDidAbort(TransactionId transactionId) +{ + if (AMI_OVERRIDE) + return false; + + return + TransactionLogTest(transactionId, XID_ABORT); +} + +bool /* true if given transaction neither committed nor aborted */ +TransactionIdIsInProgress(TransactionId transactionId) +{ + if (AMI_OVERRIDE) + return false; + + return + TransactionLogTest(transactionId, XID_INPROGRESS); +} + +/* -------------------------------- + * TransactionId Commit + * TransactionId Abort + * TransactionId SetInProgress + * -------------------------------- + */ + +/* + * TransactionIdCommit -- + * Commits the transaction associated with the identifier. + * + * Note: + * Assumes transaction identifier is valid. + */ +void +TransactionIdCommit(TransactionId transactionId) +{ + if (AMI_OVERRIDE) + return; + + /* + * Within TransactionLogUpdate we call UpdateLastCommited() + * which assumes we have exclusive access to pg_variable. + * Therefore we need to get exclusive access before calling + * TransactionLogUpdate. -mer 18 Aug 1992 + */ + SpinAcquire(OidGenLockId); + TransactionLogUpdate(transactionId, XID_COMMIT); + SpinRelease(OidGenLockId); +} + +/* + * TransactionIdAbort -- + * Aborts the transaction associated with the identifier. + * + * Note: + * Assumes transaction identifier is valid. + */ +void +TransactionIdAbort(TransactionId transactionId) +{ + BuildingBtree = false; + + if (VacuumRunning) + vc_abort(); + + if (AMI_OVERRIDE) + return; + + TransactionLogUpdate(transactionId, XID_ABORT); +} + +void +TransactionIdSetInProgress(TransactionId transactionId) +{ + if (AMI_OVERRIDE) + return; + + TransactionLogUpdate(transactionId, XID_INPROGRESS); +} diff --git a/src/backend/access/transam/transsup.c b/src/backend/access/transam/transsup.c new file mode 100644 index 00000000000..a1e5b17ec13 --- /dev/null +++ b/src/backend/access/transam/transsup.c @@ -0,0 +1,663 @@ +/*------------------------------------------------------------------------- + * + * transsup.c-- + * postgres transaction access method support code + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/transam/Attic/transsup.c,v 1.1.1.1 1996/07/09 06:21:13 scrappy Exp $ + * + * NOTES + * This file contains support functions for the high + * level access method interface routines found in transam.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "machine.h" /* in port/ directory (needed for BLCKSZ) */ + +#include "storage/buf.h" +#include "storage/bufmgr.h" + +#include "utils/rel.h" +#include "utils/elog.h" +#include "utils/memutils.h" +#include "utils/nabstime.h" + +#include "catalog/heap.h" +#include "access/transam.h" /* where the declarations go */ +#include "access/xact.h" /* where the declarations go */ + +#include "storage/smgr.h" + +/* ---------------------------------------------------------------- + * general support routines + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * AmiTransactionOverride + * + * This function is used to manipulate the bootstrap flag. + * -------------------------------- + */ +void +AmiTransactionOverride(bool flag) +{ + AMI_OVERRIDE = flag; +} + +/* -------------------------------- + * TransComputeBlockNumber + * -------------------------------- + */ +void +TransComputeBlockNumber(Relation relation, /* relation to test */ + TransactionId transactionId, /* transaction id to test */ + BlockNumber *blockNumberOutP) +{ + long itemsPerBlock; + + /* ---------------- + * we calculate the block number of our transaction + * by dividing the transaction id by the number of + * transaction things per block. + * ---------------- + */ + if (relation == LogRelation) + itemsPerBlock = TP_NumXidStatusPerBlock; + else if (relation == TimeRelation) + itemsPerBlock = TP_NumTimePerBlock; + else + elog(WARN, "TransComputeBlockNumber: unknown relation"); + + /* ---------------- + * warning! if the transaction id's get too large + * then a BlockNumber may not be large enough to hold the results + * of our division. + * + * XXX this will all vanish soon when we implement an improved + * transaction id schema -cim 3/23/90 + * + * This has vanished now that xid's are 4 bytes (no longer 5). + * -mer 5/24/92 + * ---------------- + */ + (*blockNumberOutP) = transactionId / itemsPerBlock; +} + + +/* ---------------------------------------------------------------- + * trans block support routines + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * TransBlockGetLastTransactionIdStatus + * + * This returns the status and transaction id of the last + * transaction information recorded on the given TransBlock. + * -------------------------------- + */ + +XidStatus +TransBlockGetLastTransactionIdStatus(Block tblock, + TransactionId baseXid, + TransactionId *returnXidP) +{ + Index index; + Index maxIndex; + bits8 bit1; + bits8 bit2; + BitIndex offset; + XidStatus xstatus; + + /* ---------------- + * sanity check + * ---------------- + */ + Assert((tblock != NULL)); + + /* ---------------- + * search downward from the top of the block data, looking + * for the first Non-in progress transaction status. Since we + * are scanning backward, this will be last recorded transaction + * status on the block. + * ---------------- + */ + maxIndex = TP_NumXidStatusPerBlock; + for (index = maxIndex-1; index>=0; index--) { + offset = BitIndexOf(index); + bit1 = ((bits8) BitArrayBitIsSet((BitArray) tblock, offset++)) << 1; + bit2 = (bits8) BitArrayBitIsSet((BitArray) tblock, offset); + + xstatus = (bit1 | bit2) ; + + /* ---------------- + * here we have the status of some transaction, so test + * if the status is recorded as "in progress". If so, then + * we save the transaction id in the place specified by the caller. + * ---------------- + */ + if (xstatus != XID_INPROGRESS) { + if (returnXidP != NULL) { + TransactionIdStore(baseXid, returnXidP); + TransactionIdAdd(returnXidP, index); + } + break; + } + } + + /* ---------------- + * if we get here and index is 0 it means we couldn't find + * a non-inprogress transaction on the block. For now we just + * return this info to the user. They can check if the return + * status is "in progress" to know this condition has arisen. + * ---------------- + */ + if (index == 0) { + if (returnXidP != NULL) + TransactionIdStore(baseXid, returnXidP); + } + + /* ---------------- + * return the status to the user + * ---------------- + */ + return xstatus; +} + +/* -------------------------------- + * TransBlockGetXidStatus + * + * This returns the status of the desired transaction + * -------------------------------- + */ + +XidStatus +TransBlockGetXidStatus(Block tblock, + TransactionId transactionId) +{ + Index index; + bits8 bit1; + bits8 bit2; + BitIndex offset; + + /* ---------------- + * sanity check + * ---------------- + */ + if (tblock == NULL) { + return XID_INVALID; + } + + /* ---------------- + * calculate the index into the transaction data where + * our transaction status is located + * + * XXX this will be replaced soon when we move to the + * new transaction id scheme -cim 3/23/90 + * + * The old system has now been replaced. -mer 5/24/92 + * ---------------- + */ + index = transactionId % TP_NumXidStatusPerBlock; + + /* ---------------- + * get the data at the specified index + * ---------------- + */ + offset = BitIndexOf(index); + bit1 = ((bits8) BitArrayBitIsSet((BitArray) tblock, offset++)) << 1; + bit2 = (bits8) BitArrayBitIsSet((BitArray) tblock, offset); + + /* ---------------- + * return the transaction status to the caller + * ---------------- + */ + return (XidStatus) + (bit1 | bit2); +} + +/* -------------------------------- + * TransBlockSetXidStatus + * + * This sets the status of the desired transaction + * -------------------------------- + */ +void +TransBlockSetXidStatus(Block tblock, + TransactionId transactionId, + XidStatus xstatus) +{ + Index index; + BitIndex offset; + + /* ---------------- + * sanity check + * ---------------- + */ + if (tblock == NULL) + return; + + /* ---------------- + * calculate the index into the transaction data where + * we sould store our transaction status. + * + * XXX this will be replaced soon when we move to the + * new transaction id scheme -cim 3/23/90 + * + * The new scheme is here -mer 5/24/92 + * ---------------- + */ + index = transactionId % TP_NumXidStatusPerBlock; + + offset = BitIndexOf(index); + + /* ---------------- + * store the transaction value at the specified offset + * ---------------- + */ + switch(xstatus) { + case XID_COMMIT: /* set 10 */ + BitArraySetBit((BitArray) tblock, offset); + BitArrayClearBit((BitArray) tblock, offset + 1); + break; + case XID_ABORT: /* set 01 */ + BitArrayClearBit((BitArray) tblock, offset); + BitArraySetBit((BitArray) tblock, offset + 1); + break; + case XID_INPROGRESS: /* set 00 */ + BitArrayClearBit((BitArray) tblock, offset); + BitArrayClearBit((BitArray) tblock, offset + 1); + break; + default: + elog(NOTICE, + "TransBlockSetXidStatus: invalid status: %d (ignored)", + xstatus); + break; + } +} + +/* -------------------------------- + * TransBlockGetCommitTime + * + * This returns the transaction commit time for the + * specified transaction id in the trans block. + * -------------------------------- + */ +AbsoluteTime +TransBlockGetCommitTime(Block tblock, + TransactionId transactionId) +{ + Index index; + AbsoluteTime *timeArray; + + /* ---------------- + * sanity check + * ---------------- + */ + if (tblock == NULL) + return INVALID_ABSTIME; + + /* ---------------- + * calculate the index into the transaction data where + * our transaction commit time is located + * + * XXX this will be replaced soon when we move to the + * new transaction id scheme -cim 3/23/90 + * + * The new scheme is here. -mer 5/24/92 + * ---------------- + */ + index = transactionId % TP_NumTimePerBlock; + + /* ---------------- + * return the commit time to the caller + * ---------------- + */ + timeArray = (AbsoluteTime *) tblock; + return (AbsoluteTime) + timeArray[ index ]; +} + +/* -------------------------------- + * TransBlockSetCommitTime + * + * This sets the commit time of the specified transaction + * -------------------------------- + */ +void +TransBlockSetCommitTime(Block tblock, + TransactionId transactionId, + AbsoluteTime commitTime) +{ + Index index; + AbsoluteTime *timeArray; + + /* ---------------- + * sanity check + * ---------------- + */ + if (tblock == NULL) + return; + + + /* ---------------- + * calculate the index into the transaction data where + * we sould store our transaction status. + * + * XXX this will be replaced soon when we move to the + * new transaction id scheme -cim 3/23/90 + * + * The new scheme is here. -mer 5/24/92 + * ---------------- + */ + index = transactionId % TP_NumTimePerBlock; + + /* ---------------- + * store the transaction commit time at the specified index + * ---------------- + */ + timeArray = (AbsoluteTime *) tblock; + timeArray[ index ] = commitTime; +} + +/* ---------------------------------------------------------------- + * transam i/o support routines + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * TransBlockNumberGetXidStatus + * -------------------------------- + */ +XidStatus +TransBlockNumberGetXidStatus(Relation relation, + BlockNumber blockNumber, + TransactionId xid, + bool *failP) +{ + Buffer buffer; /* buffer associated with block */ + Block block; /* block containing xstatus */ + XidStatus xstatus; /* recorded status of xid */ + bool localfail; /* bool used if failP = NULL */ + + /* ---------------- + * SOMEDAY place a read lock on the log relation + * That someday is today 5 Aug 1991 -mer + * ---------------- + */ + RelationSetLockForRead(relation); + + /* ---------------- + * get the page containing the transaction information + * ---------------- + */ + buffer = ReadBuffer(relation, blockNumber); + block = BufferGetBlock(buffer); + + /* ---------------- + * get the status from the block. note, for now we always + * return false in failP. + * ---------------- + */ + if (failP == NULL) + failP = &localfail; + (*failP) = false; + + xstatus = TransBlockGetXidStatus(block, xid); + + /* ---------------- + * release the buffer and return the status + * ---------------- + */ + ReleaseBuffer(buffer); + + /* ---------------- + * SOMEDAY release our lock on the log relation + * ---------------- + */ + RelationUnsetLockForRead(relation); + + return + xstatus; +} + +/* -------------------------------- + * TransBlockNumberSetXidStatus + * -------------------------------- + */ +void +TransBlockNumberSetXidStatus(Relation relation, + BlockNumber blockNumber, + TransactionId xid, + XidStatus xstatus, + bool *failP) +{ + Buffer buffer; /* buffer associated with block */ + Block block; /* block containing xstatus */ + bool localfail; /* bool used if failP = NULL */ + + /* ---------------- + * SOMEDAY gain exclusive access to the log relation + * + * That someday is today 5 Aug 1991 -mer + * ---------------- + */ + RelationSetLockForWrite(relation); + + /* ---------------- + * get the block containing the transaction status + * ---------------- + */ + buffer = ReadBuffer(relation, blockNumber); + block = BufferGetBlock(buffer); + + /* ---------------- + * attempt to update the status of the transaction on the block. + * if we are successful, write the block. otherwise release the buffer. + * note, for now we always return false in failP. + * ---------------- + */ + if (failP == NULL) + failP = &localfail; + (*failP) = false; + + TransBlockSetXidStatus(block, xid, xstatus); + + if ((*failP) == false) + WriteBuffer(buffer); + else + ReleaseBuffer(buffer); + + /* ---------------- + * SOMEDAY release our lock on the log relation + * ---------------- + */ + RelationUnsetLockForWrite(relation); +} + +/* -------------------------------- + * TransBlockNumberGetCommitTime + * -------------------------------- + */ +AbsoluteTime +TransBlockNumberGetCommitTime(Relation relation, + BlockNumber blockNumber, + TransactionId xid, + bool *failP) +{ + Buffer buffer; /* buffer associated with block */ + Block block; /* block containing commit time */ + bool localfail; /* bool used if failP = NULL */ + AbsoluteTime xtime; /* commit time */ + + /* ---------------- + * SOMEDAY place a read lock on the time relation + * + * That someday is today 5 Aug. 1991 -mer + * ---------------- + */ + RelationSetLockForRead(relation); + + /* ---------------- + * get the block containing the transaction information + * ---------------- + */ + buffer = ReadBuffer(relation, blockNumber); + block = BufferGetBlock(buffer); + + /* ---------------- + * get the commit time from the block + * note, for now we always return false in failP. + * ---------------- + */ + if (failP == NULL) + failP = &localfail; + (*failP) = false; + + xtime = TransBlockGetCommitTime(block, xid); + + /* ---------------- + * release the buffer and return the commit time + * ---------------- + */ + ReleaseBuffer(buffer); + + /* ---------------- + * SOMEDAY release our lock on the time relation + * ---------------- + */ + RelationUnsetLockForRead(relation); + + if ((*failP) == false) + return xtime; + else + return INVALID_ABSTIME; + +} + +/* -------------------------------- + * TransBlockNumberSetCommitTime + * -------------------------------- + */ +void +TransBlockNumberSetCommitTime(Relation relation, + BlockNumber blockNumber, + TransactionId xid, + AbsoluteTime xtime, + bool *failP) +{ + Buffer buffer; /* buffer associated with block */ + Block block; /* block containing commit time */ + bool localfail; /* bool used if failP = NULL */ + + /* ---------------- + * SOMEDAY gain exclusive access to the time relation + * + * That someday is today 5 Aug. 1991 -mer + * ---------------- + */ + RelationSetLockForWrite(relation); + + /* ---------------- + * get the block containing our commit time + * ---------------- + */ + buffer = ReadBuffer(relation, blockNumber); + block = BufferGetBlock(buffer); + + /* ---------------- + * attempt to update the commit time of the transaction on the block. + * if we are successful, write the block. otherwise release the buffer. + * note, for now we always return false in failP. + * ---------------- + */ + if (failP == NULL) + failP = &localfail; + (*failP) = false; + + TransBlockSetCommitTime(block, xid, xtime); + + if ((*failP) == false) + WriteBuffer(buffer); + else + ReleaseBuffer(buffer); + + /* ---------------- + * SOMEDAY release our lock on the time relation + * ---------------- + */ + RelationUnsetLockForWrite(relation); + +} + +/* -------------------------------- + * TransGetLastRecordedTransaction + * -------------------------------- + */ +void +TransGetLastRecordedTransaction(Relation relation, + TransactionId xid, /* return: transaction id */ + bool *failP) +{ + BlockNumber blockNumber; /* block number */ + Buffer buffer; /* buffer associated with block */ + Block block; /* block containing xid status */ + BlockNumber n; /* number of blocks in the relation */ + TransactionId baseXid; + + (*failP) = false; + + /* ---------------- + * SOMEDAY gain exclusive access to the log relation + * + * That someday is today 5 Aug. 1991 -mer + * It looks to me like we only need to set a read lock here, despite + * the above comment about exclusive access. The block is never + * actually written into, we only check status bits. + * ---------------- + */ + RelationSetLockForRead(relation); + + /* ---------------- + * we assume the last block of the log contains the last + * recorded transaction. If the relation is empty we return + * failure to the user. + * ---------------- + */ + n = RelationGetNumberOfBlocks(relation); + if (n == 0) { + (*failP) = true; + return; + } + + /* ---------------- + * get the block containing the transaction information + * ---------------- + */ + blockNumber = n-1; + buffer = ReadBuffer(relation, blockNumber); + block = BufferGetBlock(buffer); + + /* ---------------- + * get the last xid on the block + * ---------------- + */ + baseXid = blockNumber * TP_NumXidStatusPerBlock; + +/* XXX ???? xid won't get returned! - AY '94 */ + (void) TransBlockGetLastTransactionIdStatus(block, baseXid, &xid); + + ReleaseBuffer(buffer); + + /* ---------------- + * SOMEDAY release our lock on the log relation + * ---------------- + */ + RelationUnsetLockForRead(relation); +} diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c new file mode 100644 index 00000000000..a53cc7d35b1 --- /dev/null +++ b/src/backend/access/transam/varsup.c @@ -0,0 +1,606 @@ +/*------------------------------------------------------------------------- + * + * varsup.c-- + * postgres variable relation support routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/transam/varsup.c,v 1.1.1.1 1996/07/09 06:21:13 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <math.h> +#include "postgres.h" + +#include "machine.h" /* in port/ directory (needed for BLCKSZ) */ +#include "storage/buf.h" +#include "storage/bufmgr.h" +#include "storage/ipc.h" /* for OIDGENLOCKID */ + +#include "utils/rel.h" +#include "utils/elog.h" + +#include "access/heapam.h" +#include "access/transam.h" /* where the declarations go */ +#include "access/xact.h" /* where the declarations go */ + +#include "catalog/catname.h" + +/* ---------- + * note: we reserve the first 16384 object ids for internal use. + * oid's less than this appear in the .bki files. the choice of + * 16384 is completely arbitrary. + * ---------- + */ +#define BootstrapObjectIdData 16384 + +/* --------------------- + * spin lock for oid generation + * --------------------- + */ +int OidGenLockId; + +/* ---------------------------------------------------------------- + * variable relation query/update routines + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * VariableRelationGetNextXid + * -------------------------------- + */ +void +VariableRelationGetNextXid(TransactionId *xidP) +{ + Buffer buf; + VariableRelationContents var; + + /* ---------------- + * We assume that a spinlock has been acquire to guarantee + * exclusive access to the variable relation. + * ---------------- + */ + + /* ---------------- + * do nothing before things are initialized + * ---------------- + */ + if (! RelationIsValid(VariableRelation)) + return; + + /* ---------------- + * read the variable page, get the the nextXid field and + * release the buffer + * ---------------- + */ + buf = ReadBuffer(VariableRelation, 0); + + if (! BufferIsValid(buf)) + { + SpinRelease(OidGenLockId); + elog(WARN, "VariableRelationGetNextXid: ReadBuffer failed"); + } + + var = (VariableRelationContents) BufferGetBlock(buf); + + TransactionIdStore(var->nextXidData, xidP); + ReleaseBuffer(buf); +} + +/* -------------------------------- + * VariableRelationGetLastXid + * -------------------------------- + */ +void +VariableRelationGetLastXid(TransactionId *xidP) +{ + Buffer buf; + VariableRelationContents var; + + /* ---------------- + * We assume that a spinlock has been acquire to guarantee + * exclusive access to the variable relation. + * ---------------- + */ + + /* ---------------- + * do nothing before things are initialized + * ---------------- + */ + if (! RelationIsValid(VariableRelation)) + return; + + /* ---------------- + * read the variable page, get the the lastXid field and + * release the buffer + * ---------------- + */ + buf = ReadBuffer(VariableRelation, 0); + + if (! BufferIsValid(buf)) + { + SpinRelease(OidGenLockId); + elog(WARN, "VariableRelationGetNextXid: ReadBuffer failed"); + } + + var = (VariableRelationContents) BufferGetBlock(buf); + + TransactionIdStore(var->lastXidData, xidP); + + ReleaseBuffer(buf); +} + +/* -------------------------------- + * VariableRelationPutNextXid + * -------------------------------- + */ +void +VariableRelationPutNextXid(TransactionId xid) +{ + Buffer buf; + VariableRelationContents var; + + /* ---------------- + * We assume that a spinlock has been acquire to guarantee + * exclusive access to the variable relation. + * ---------------- + */ + + /* ---------------- + * do nothing before things are initialized + * ---------------- + */ + if (! RelationIsValid(VariableRelation)) + return; + + /* ---------------- + * read the variable page, update the nextXid field and + * write the page back out to disk. + * ---------------- + */ + buf = ReadBuffer(VariableRelation, 0); + + if (! BufferIsValid(buf)) + { + SpinRelease(OidGenLockId); + elog(WARN, "VariableRelationPutNextXid: ReadBuffer failed"); + } + + var = (VariableRelationContents) BufferGetBlock(buf); + + TransactionIdStore(xid, &(var->nextXidData)); + + WriteBuffer(buf); +} + +/* -------------------------------- + * VariableRelationPutLastXid + * -------------------------------- + */ +void +VariableRelationPutLastXid(TransactionId xid) +{ + Buffer buf; + VariableRelationContents var; + + /* ---------------- + * We assume that a spinlock has been acquire to guarantee + * exclusive access to the variable relation. + * ---------------- + */ + + /* ---------------- + * do nothing before things are initialized + * ---------------- + */ + if (! RelationIsValid(VariableRelation)) + return; + + /* ---------------- + * read the variable page, update the lastXid field and + * force the page back out to disk. + * ---------------- + */ + buf = ReadBuffer(VariableRelation, 0); + + if (! BufferIsValid(buf)) + { + SpinRelease(OidGenLockId); + elog(WARN, "VariableRelationPutLastXid: ReadBuffer failed"); + } + + var = (VariableRelationContents) BufferGetBlock(buf); + + TransactionIdStore(xid, &(var->lastXidData)); + + WriteBuffer(buf); +} + +/* -------------------------------- + * VariableRelationGetNextOid + * -------------------------------- + */ +void +VariableRelationGetNextOid(Oid *oid_return) +{ + Buffer buf; + VariableRelationContents var; + + /* ---------------- + * We assume that a spinlock has been acquire to guarantee + * exclusive access to the variable relation. + * ---------------- + */ + + /* ---------------- + * if the variable relation is not initialized, then we + * assume we are running at bootstrap time and so we return + * an invalid object id -- during this time GetNextBootstrapObjectId + * should be called instead.. + * ---------------- + */ + if (! RelationIsValid(VariableRelation)) { + if (PointerIsValid(oid_return)) + (*oid_return) = InvalidOid; + return; + } + + /* ---------------- + * read the variable page, get the the nextOid field and + * release the buffer + * ---------------- + */ + buf = ReadBuffer(VariableRelation, 0); + + if (! BufferIsValid(buf)) + { + SpinRelease(OidGenLockId); + elog(WARN, "VariableRelationGetNextXid: ReadBuffer failed"); + } + + var = (VariableRelationContents) BufferGetBlock(buf); + + if (PointerIsValid(oid_return)) { + + /* ---------------- + * nothing up my sleeve... what's going on here is that this code + * is guaranteed never to be called until all files in data/base/ + * are created, and the template database exists. at that point, + * we want to append a pg_database tuple. the first time we do + * this, the oid stored in pg_variable will be bogus, so we use + * a bootstrap value defined at the top of this file. + * + * this comment no longer holds true. This code is called before + * all of the files in data/base are created and you can't rely + * on system oid's to be less than BootstrapObjectIdData. mer 9/18/91 + * ---------------- + */ + if (OidIsValid(var->nextOid)) + (*oid_return) = var->nextOid; + else + (*oid_return) = BootstrapObjectIdData; + } + + ReleaseBuffer(buf); +} + +/* -------------------------------- + * VariableRelationPutNextOid + * -------------------------------- + */ +void +VariableRelationPutNextOid(Oid *oidP) +{ + Buffer buf; + VariableRelationContents var; + + /* ---------------- + * We assume that a spinlock has been acquire to guarantee + * exclusive access to the variable relation. + * ---------------- + */ + + /* ---------------- + * do nothing before things are initialized + * ---------------- + */ + if (! RelationIsValid(VariableRelation)) + return; + + /* ---------------- + * sanity check + * ---------------- + */ + if (! PointerIsValid(oidP)) + { + SpinRelease(OidGenLockId); + elog(WARN, "VariableRelationPutNextOid: invalid oid pointer"); + } + + /* ---------------- + * read the variable page, update the nextXid field and + * write the page back out to disk. + * ---------------- + */ + buf = ReadBuffer(VariableRelation, 0); + + if (! BufferIsValid(buf)) + { + SpinRelease(OidGenLockId); + elog(WARN, "VariableRelationPutNextXid: ReadBuffer failed"); + } + + var = (VariableRelationContents) BufferGetBlock(buf); + + var->nextOid = (*oidP); + + WriteBuffer(buf); +} + +/* ---------------------------------------------------------------- + * transaction id generation support + * ---------------------------------------------------------------- + */ + +/* ---------------- + * GetNewTransactionId + * + * In the version 2 transaction system, transaction id's are + * restricted in several ways. + * + * First, all transaction id's are even numbers (4, 88, 121342, etc). + * This means the binary representation of the number will never + * have the least significent bit set. This bit is reserved to + * indicate that the transaction id does not in fact hold an XID, + * but rather a commit time. This makes it possible for the + * vaccuum daemon to disgard information from the log and time + * relations for committed tuples. This is important when archiving + * tuples to an optical disk because tuples with commit times + * stored in their xid fields will not need to consult the log + * and time relations. + * + * Second, since we may someday preform compression of the data + * in the log and time relations, we cause the numbering of the + * transaction ids to begin at 512. This means that some space + * on the page of the log and time relations corresponding to + * transaction id's 0 - 510 will never be used. This space is + * in fact used to store the version number of the postgres + * transaction log and will someday store compression information + * about the log. + * + * Lastly, rather then access the variable relation each time + * a backend requests a new transction id, we "prefetch" 32 + * transaction id's by incrementing the nextXid stored in the + * var relation by 64 (remember only even xid's are legal) and then + * returning these id's one at a time until they are exhausted. + * This means we reduce the number of accesses to the variable + * relation by 32 for each backend. + * + * Note: 32 has no special significance. We don't want the + * number to be too large because if when the backend + * terminates, we lose the xid's we cached. + * + * ---------------- + */ + +#define VAR_XID_PREFETCH 32 + +static int prefetched_xid_count = 0; +static TransactionId next_prefetched_xid; + +void +GetNewTransactionId(TransactionId *xid) +{ + TransactionId nextid; + + /* ---------------- + * during bootstrap initialization, we return the special + * bootstrap transaction id. + * ---------------- + */ + if (AMI_OVERRIDE) { + TransactionIdStore(AmiTransactionId, xid); + return; + } + + /* ---------------- + * if we run out of prefetched xids, then we get some + * more before handing them out to the caller. + * ---------------- + */ + + if (prefetched_xid_count == 0) { + /* ---------------- + * obtain exclusive access to the variable relation page + * + * get the "next" xid from the variable relation + * and save it in the prefetched id. + * ---------------- + */ + SpinAcquire(OidGenLockId); + VariableRelationGetNextXid(&nextid); + TransactionIdStore(nextid, &next_prefetched_xid); + + /* ---------------- + * now increment the variable relation's next xid + * and reset the prefetched_xid_count. We multiply + * the id by two because our xid's are always even. + * ---------------- + */ + prefetched_xid_count = VAR_XID_PREFETCH; + TransactionIdAdd(&nextid, prefetched_xid_count); + VariableRelationPutNextXid(nextid); + SpinRelease(OidGenLockId); + } + + /* ---------------- + * return the next prefetched xid in the pointer passed by + * the user and decrement the prefetch count. We add two + * to id we return the next time this is called because our + * transaction ids are always even. + * + * XXX Transaction Ids used to be even as the low order bit was + * used to determine commit status. This is no long true so + * we now use even and odd transaction ids. -mer 5/26/92 + * ---------------- + */ + TransactionIdStore(next_prefetched_xid, xid); + TransactionIdAdd(&next_prefetched_xid, 1); + prefetched_xid_count--; +} + +/* ---------------- + * UpdateLastCommittedXid + * ---------------- + */ + +void +UpdateLastCommittedXid(TransactionId xid) +{ + TransactionId lastid; + + + /* we assume that spinlock OidGenLockId has been acquired + * prior to entering this function + */ + + /* ---------------- + * get the "last committed" transaction id from + * the variable relation page. + * ---------------- + */ + VariableRelationGetLastXid(&lastid); + + /* ---------------- + * if the transaction id is greater than the last committed + * transaction then we update the last committed transaction + * in the variable relation. + * ---------------- + */ + if (TransactionIdIsLessThan(lastid, xid)) + VariableRelationPutLastXid(xid); + +} + +/* ---------------------------------------------------------------- + * object id generation support + * ---------------------------------------------------------------- + */ + +/* ---------------- + * GetNewObjectIdBlock + * + * This support function is used to allocate a block of object ids + * of the given size. applications wishing to do their own object + * id assignments should use this + * ---------------- + */ +void +GetNewObjectIdBlock(Oid *oid_return, /* place to return the new object id */ + int oid_block_size) /* number of oids desired */ +{ + Oid nextoid; + + /* ---------------- + * SOMEDAY obtain exclusive access to the variable relation page + * That someday is today -mer 6 Aug 1992 + * ---------------- + */ + SpinAcquire(OidGenLockId); + + /* ---------------- + * get the "next" oid from the variable relation + * and give it to the caller. + * ---------------- + */ + VariableRelationGetNextOid(&nextoid); + if (PointerIsValid(oid_return)) + (*oid_return) = nextoid; + + /* ---------------- + * now increment the variable relation's next oid + * field by the size of the oid block requested. + * ---------------- + */ + nextoid += oid_block_size; + VariableRelationPutNextOid(&nextoid); + + /* ---------------- + * SOMEDAY relinquish our lock on the variable relation page + * That someday is today -mer 6 Apr 1992 + * ---------------- + */ + SpinRelease(OidGenLockId); +} + +/* ---------------- + * GetNewObjectId + * + * This function allocates and parses out object ids. Like + * GetNewTransactionId(), it "prefetches" 32 object ids by + * incrementing the nextOid stored in the var relation by 32 and then + * returning these id's one at a time until they are exhausted. + * This means we reduce the number of accesses to the variable + * relation by 32 for each backend. + * + * Note: 32 has no special significance. We don't want the + * number to be too large because if when the backend + * terminates, we lose the oids we cached. + * + * ---------------- + */ + +#define VAR_OID_PREFETCH 32 + +static int prefetched_oid_count = 0; +static Oid next_prefetched_oid; + +void +GetNewObjectId(Oid *oid_return) /* place to return the new object id */ +{ + /* ---------------- + * if we run out of prefetched oids, then we get some + * more before handing them out to the caller. + * ---------------- + */ + + if (prefetched_oid_count == 0) { + int oid_block_size = VAR_OID_PREFETCH; + + /* ---------------- + * during bootstrap time, we want to allocate oids + * one at a time. Otherwise there might be some + * bootstrap oid's left in the block we prefetch which + * would be passed out after the variable relation was + * initialized. This would be bad. + * ---------------- + */ + if (! RelationIsValid(VariableRelation)) + VariableRelation = heap_openr(VariableRelationName); + + /* ---------------- + * get a new block of prefetched object ids. + * ---------------- + */ + GetNewObjectIdBlock(&next_prefetched_oid, oid_block_size); + + /* ---------------- + * now reset the prefetched_oid_count. + * ---------------- + */ + prefetched_oid_count = oid_block_size; + } + + /* ---------------- + * return the next prefetched oid in the pointer passed by + * the user and decrement the prefetch count. + * ---------------- + */ + if (PointerIsValid(oid_return)) + (*oid_return) = next_prefetched_oid; + + next_prefetched_oid++; + prefetched_oid_count--; +} diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c new file mode 100644 index 00000000000..1798d09d054 --- /dev/null +++ b/src/backend/access/transam/xact.c @@ -0,0 +1,1314 @@ +/*------------------------------------------------------------------------- + * + * xact.c-- + * top level transaction system support routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.1.1.1 1996/07/09 06:21:13 scrappy Exp $ + * + * NOTES + * Transaction aborts can now occur two ways: + * + * 1) system dies from some internal cause (Assert, etc..) + * 2) user types abort + * + * These two cases used to be treated identically, but now + * we need to distinguish them. Why? consider the following + * two situatuons: + * + * case 1 case 2 + * ------ ------ + * 1) user types BEGIN 1) user types BEGIN + * 2) user does something 2) user does something + * 3) user does not like what 3) system aborts for some reason + * she shes and types ABORT + * + * In case 1, we want to abort the transaction and return to the + * default state. In case 2, there may be more commands coming + * our way which are part of the same transaction block and we have + * to ignore these commands until we see an END transaction. + * + * Internal aborts are now handled by AbortTransactionBlock(), just as + * they always have been, and user aborts are now handled by + * UserAbortTransactionBlock(). Both of them rely on AbortTransaction() + * to do all the real work. The only difference is what state we + * enter after AbortTransaction() does it's work: + * + * * AbortTransactionBlock() leaves us in TBLOCK_ABORT and + * * UserAbortTransactionBlock() leaves us in TBLOCK_ENDABORT + * + * NOTES + * This file is an attempt at a redesign of the upper layer + * of the V1 transaction system which was too poorly thought + * out to describe. This new system hopes to be both simpler + * in design, simpler to extend and needs to contain added + * functionality to solve problems beyond the scope of the V1 + * system. (In particuler, communication of transaction + * information between parallel backends has to be supported) + * + * The essential aspects of the transaction system are: + * + * o transaction id generation + * o transaction log updating + * o memory cleanup + * o cache invalidation + * o lock cleanup + * + * Hence, the functional division of the transaction code is + * based on what of the above things need to be done during + * a start/commit/abort transaction. For instance, the + * routine AtCommit_Memory() takes care of all the memory + * cleanup stuff done at commit time. + * + * The code is layered as follows: + * + * StartTransaction + * CommitTransaction + * AbortTransaction + * UserAbortTransaction + * + * are provided to do the lower level work like recording + * the transaction status in the log and doing memory cleanup. + * above these routines are another set of functions: + * + * StartTransactionCommand + * CommitTransactionCommand + * AbortCurrentTransaction + * + * These are the routines used in the postgres main processing + * loop. They are sensitive to the current transaction block state + * and make calls to the lower level routines appropriately. + * + * Support for transaction blocks is provided via the functions: + * + * StartTransactionBlock + * CommitTransactionBlock + * AbortTransactionBlock + * + * These are invoked only in responce to a user "BEGIN", "END", + * or "ABORT" command. The tricky part about these functions + * is that they are called within the postgres main loop, in between + * the StartTransactionCommand() and CommitTransactionCommand(). + * + * For example, consider the following sequence of user commands: + * + * 1) begin + * 2) retrieve (foo.all) + * 3) append foo (bar = baz) + * 4) end + * + * in the main processing loop, this results in the following + * transaction sequence: + * + * / StartTransactionCommand(); + * 1) / ProcessUtility(); << begin + * \ StartTransactionBlock(); + * \ CommitTransactionCommand(); + * + * / StartTransactionCommand(); + * 2) < ProcessQuery(); << retrieve (foo.all) + * \ CommitTransactionCommand(); + * + * / StartTransactionCommand(); + * 3) < ProcessQuery(); << append foo (bar = baz) + * \ CommitTransactionCommand(); + * + * / StartTransactionCommand(); + * 4) / ProcessUtility(); << end + * \ CommitTransactionBlock(); + * \ CommitTransactionCommand(); + * + * The point of this example is to demonstrate the need for + * StartTransactionCommand() and CommitTransactionCommand() to + * be state smart -- they should do nothing in between the calls + * to StartTransactionBlock() and EndTransactionBlock() and + * outside these calls they need to do normal start/commit + * processing. + * + * Furthermore, suppose the "retrieve (foo.all)" caused an abort + * condition. We would then want to abort the transaction and + * ignore all subsequent commands up to the "end". + * -cim 3/23/90 + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" +#include "access/xact.h" +#include "commands/async.h" +#include "storage/bufmgr.h" +#include "storage/block.h" +#include "storage/proc.h" +#include "utils/inval.h" +#include "utils/relcache.h" +#include "access/transam.h" +#include "catalog/heap.h" + +/* ---------------- + * global variables holding the current transaction state. + * + * Note: when we are running several slave processes, the + * current transaction state data is copied into shared memory + * and the CurrentTransactionState pointer changed to + * point to the shared copy. All this occurrs in slaves.c + * ---------------- + */ +TransactionStateData CurrentTransactionStateData = { + 0, /* transaction id */ + FirstCommandId, /* command id */ + 0x0, /* start time */ + TRANS_DEFAULT, /* transaction state */ + TBLOCK_DEFAULT /* transaction block state */ + }; + +TransactionState CurrentTransactionState = + &CurrentTransactionStateData; + +/* ---------------- + * info returned when the system is desabled + * + * Note: I have no idea what the significance of the + * 1073741823 in DisabledStartTime.. I just carried + * this over when converting things from the old + * V1 transaction system. -cim 3/18/90 + * ---------------- + */ +TransactionId DisabledTransactionId = (TransactionId)-1; + +CommandId DisabledCommandId = (CommandId) -1; + +AbsoluteTime DisabledStartTime = (AbsoluteTime) 1073741823; + +/* ---------------- + * overflow flag + * ---------------- + */ +bool CommandIdCounterOverflowFlag; + +/* ---------------- + * catalog creation transaction bootstrapping flag. + * This should be eliminated and added to the transaction + * state stuff. -cim 3/19/90 + * ---------------- + */ +bool AMI_OVERRIDE = false; + +/* ---------------------------------------------------------------- + * transaction state accessors + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * TranactionFlushEnabled() + * SetTranactionFlushEnabled() + * + * These are used to test and set the "TransactionFlushState" + * varable. If this variable is true (the default), then + * the system will flush all dirty buffers to disk at the end + * of each transaction. If false then we are assuming the + * buffer pool resides in stable main memory, in which case we + * only do writes as necessary. + * -------------------------------- + */ +static int TransactionFlushState = 1; + +int +TransactionFlushEnabled() +{ + return TransactionFlushState; +} + +void +SetTransactionFlushEnabled(bool state) +{ + TransactionFlushState = (state == true); +} + +/* -------------------------------- + * IsTransactionState + * + * This returns true if we are currently running a query + * within an executing transaction. + * -------------------------------- + */ +bool +IsTransactionState() +{ + TransactionState s = CurrentTransactionState; + + switch (s->state) { + case TRANS_DEFAULT: return false; + case TRANS_START: return true; + case TRANS_INPROGRESS: return true; + case TRANS_COMMIT: return true; + case TRANS_ABORT: return true; + case TRANS_DISABLED: return false; + } + /* + * Shouldn't get here, but lint is not happy with this... + */ + return(false); +} + +/* -------------------------------- + * IsAbortedTransactionBlockState + * + * This returns true if we are currently running a query + * within an aborted transaction block. + * -------------------------------- + */ +bool +IsAbortedTransactionBlockState() +{ + TransactionState s = CurrentTransactionState; + + if (s->blockState == TBLOCK_ABORT) + return true; + + return false; +} + +/* -------------------------------- + * OverrideTransactionSystem + * + * This is used to temporarily disable the transaction + * processing system in order to do initialization of + * the transaction system data structures and relations + * themselves. + * -------------------------------- + */ +int SavedTransactionState; + +void +OverrideTransactionSystem(bool flag) +{ + TransactionState s = CurrentTransactionState; + + if (flag == true) { + if (s->state == TRANS_DISABLED) + return; + + SavedTransactionState = s->state; + s->state = TRANS_DISABLED; + } else { + if (s->state != TRANS_DISABLED) + return; + + s->state = SavedTransactionState; + } +} + +/* -------------------------------- + * GetCurrentTransactionId + * + * This returns the id of the current transaction, or + * the id of the "disabled" transaction. + * -------------------------------- + */ +TransactionId +GetCurrentTransactionId() +{ + TransactionState s = CurrentTransactionState; + + /* ---------------- + * if the transaction system is disabled, we return + * the special "disabled" transaction id. + * ---------------- + */ + if (s->state == TRANS_DISABLED) + return (TransactionId) DisabledTransactionId; + + /* ---------------- + * otherwise return the current transaction id. + * ---------------- + */ + return (TransactionId) s->transactionIdData; +} + + +/* -------------------------------- + * GetCurrentCommandId + * -------------------------------- + */ +CommandId +GetCurrentCommandId() +{ + TransactionState s = CurrentTransactionState; + + /* ---------------- + * if the transaction system is disabled, we return + * the special "disabled" command id. + * ---------------- + */ + if (s->state == TRANS_DISABLED) + return (CommandId) DisabledCommandId; + + return s->commandId; +} + + +/* -------------------------------- + * GetCurrentTransactionStartTime + * -------------------------------- + */ +AbsoluteTime +GetCurrentTransactionStartTime() +{ + TransactionState s = CurrentTransactionState; + + /* ---------------- + * if the transaction system is disabled, we return + * the special "disabled" starting time. + * ---------------- + */ + if (s->state == TRANS_DISABLED) + return (AbsoluteTime) DisabledStartTime; + + return s->startTime; +} + + +/* -------------------------------- + * TransactionIdIsCurrentTransactionId + * -------------------------------- + */ +bool +TransactionIdIsCurrentTransactionId(TransactionId xid) +{ + TransactionState s = CurrentTransactionState; + + if (AMI_OVERRIDE) + return false; + + return (bool) + TransactionIdEquals(xid, s->transactionIdData); +} + + +/* -------------------------------- + * CommandIdIsCurrentCommandId + * -------------------------------- + */ +bool +CommandIdIsCurrentCommandId(CommandId cid) +{ + TransactionState s = CurrentTransactionState; + + if (AMI_OVERRIDE) + return false; + + return + (cid == s->commandId) ? true : false; +} + + +/* -------------------------------- + * ClearCommandIdCounterOverflowFlag + * -------------------------------- + */ +void +ClearCommandIdCounterOverflowFlag() +{ + CommandIdCounterOverflowFlag = false; +} + + +/* -------------------------------- + * CommandCounterIncrement + * -------------------------------- + */ +void +CommandCounterIncrement() +{ + CurrentTransactionStateData.commandId += 1; + if (CurrentTransactionStateData.commandId == FirstCommandId) { + CommandIdCounterOverflowFlag = true; + elog(WARN, "You may only have 65535 commands per transaction"); + } + + /* make cache changes visible to me */ + AtCommit_Cache(); + AtStart_Cache(); +} + +/* ---------------------------------------------------------------- + * initialization stuff + * ---------------------------------------------------------------- + */ +void +InitializeTransactionSystem() +{ + InitializeTransactionLog(); +} + +/* ---------------------------------------------------------------- + * StartTransaction stuff + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * AtStart_Cache + * -------------------------------- + */ +void +AtStart_Cache() +{ + DiscardInvalid(); +} + +/* -------------------------------- + * AtStart_Locks + * -------------------------------- + */ +void +AtStart_Locks() +{ + /* + * at present, it is unknown to me what belongs here -cim 3/18/90 + * + * There isn't anything to do at the start of a xact for locks. + * -mer 5/24/92 + */ +} + +/* -------------------------------- + * AtStart_Memory + * -------------------------------- + */ +void +AtStart_Memory() +{ + Portal portal; + MemoryContext portalContext; + + /* ---------------- + * get the blank portal and its memory context + * ---------------- + */ + portal = GetPortalByName(NULL); + portalContext = (MemoryContext) PortalGetHeapMemory(portal); + + /* ---------------- + * tell system to allocate in the blank portal context + * ---------------- + */ + (void) MemoryContextSwitchTo(portalContext); + StartPortalAllocMode(DefaultAllocMode, 0); +} + + +/* ---------------------------------------------------------------- + * CommitTransaction stuff + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * RecordTransactionCommit + * + * Note: the two calls to BufferManagerFlush() exist to ensure + * that data pages are written before log pages. These + * explicit calls should be replaced by a more efficient + * ordered page write scheme in the buffer manager + * -cim 3/18/90 + * -------------------------------- + */ +void +RecordTransactionCommit() +{ + TransactionId xid; + int leak; + + /* ---------------- + * get the current transaction id + * ---------------- + */ + xid = GetCurrentTransactionId(); + + /* ---------------- + * flush the buffer manager pages. Note: if we have stable + * main memory, dirty shared buffers are not flushed + * plai 8/7/90 + * ---------------- + */ + leak = BufferPoolCheckLeak(); + FlushBufferPool(!TransactionFlushEnabled()); + if (leak) ResetBufferPool(); + + /* ---------------- + * have the transaction access methods record the status + * of this transaction id in the pg_log / pg_time relations. + * ---------------- + */ + TransactionIdCommit(xid); + + /* ---------------- + * Now write the log/time info to the disk too. + * ---------------- + */ + leak = BufferPoolCheckLeak(); + FlushBufferPool(!TransactionFlushEnabled()); + if (leak) ResetBufferPool(); +} + + +/* -------------------------------- + * AtCommit_Cache + * -------------------------------- + */ +void +AtCommit_Cache() +{ + /* ---------------- + * Make catalog changes visible to me for the next command. + * Other backends will not process my invalidation messages until + * after I commit and free my locks--though they will do + * unnecessary work if I abort. + * ---------------- + */ + RegisterInvalid(true); +} + +/* -------------------------------- + * AtCommit_Locks + * -------------------------------- + */ +void +AtCommit_Locks() +{ + /* ---------------- + * XXX What if ProcReleaseLocks fails? (race condition?) + * + * Then you're up a creek! -mer 5/24/92 + * ---------------- + */ + ProcReleaseLocks(); +} + +/* -------------------------------- + * AtCommit_Memory + * -------------------------------- + */ +void +AtCommit_Memory() +{ + /* ---------------- + * now that we're "out" of a transaction, have the + * system allocate things in the top memory context instead + * of the blank portal memory context. + * ---------------- + */ + EndPortalAllocMode(); + (void) MemoryContextSwitchTo(TopMemoryContext); +} + +/* ---------------------------------------------------------------- + * AbortTransaction stuff + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * RecordTransactionAbort + * -------------------------------- + */ +void +RecordTransactionAbort() +{ + TransactionId xid; + + /* ---------------- + * get the current transaction id + * ---------------- + */ + xid = GetCurrentTransactionId(); + + /* ---------------- + * have the transaction access methods record the status + * of this transaction id in the pg_log / pg_time relations. + * ---------------- + */ + TransactionIdAbort(xid); + + /* ---------------- + * flush the buffer manager pages. Note: if we have stable + * main memory, dirty shared buffers are not flushed + * plai 8/7/90 + * ---------------- + */ + ResetBufferPool(); +} + +/* -------------------------------- + * AtAbort_Cache + * -------------------------------- + */ +void +AtAbort_Cache() +{ + RegisterInvalid(false); +} + +/* -------------------------------- + * AtAbort_Locks + * -------------------------------- + */ +void +AtAbort_Locks() +{ + /* ---------------- + * XXX What if ProcReleaseLocks() fails? (race condition?) + * + * Then you're up a creek without a paddle! -mer + * ---------------- + */ + ProcReleaseLocks(); +} + + +/* -------------------------------- + * AtAbort_Memory + * -------------------------------- + */ +void +AtAbort_Memory() +{ + /* ---------------- + * after doing an abort transaction, make certain the + * system uses the top memory context rather then the + * portal memory context (until the next transaction). + * ---------------- + */ + (void) MemoryContextSwitchTo(TopMemoryContext); +} + +/* ---------------------------------------------------------------- + * interface routines + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * StartTransaction + * + * -------------------------------- + */ +void +StartTransaction() +{ + TransactionState s = CurrentTransactionState; + + /* ---------------- + * Check the current transaction state. If the transaction system + * is switched off, or if we're already in a transaction, do nothing. + * We're already in a transaction when the monitor sends a null + * command to the backend to flush the comm channel. This is a + * hacky fix to a communications problem, and we keep having to + * deal with it here. We should fix the comm channel code. mao 080891 + * ---------------- + */ + if (s->state == TRANS_DISABLED || s->state == TRANS_INPROGRESS) + return; + + /* ---------------- + * set the current transaction state information + * appropriately during start processing + * ---------------- + */ + s->state = TRANS_START; + + /* ---------------- + * generate a new transaction id + * ---------------- + */ + GetNewTransactionId(&(s->transactionIdData)); + + /* ---------------- + * initialize current transaction state fields + * ---------------- + */ + s->commandId = FirstCommandId; + s->startTime = GetCurrentAbsoluteTime(); + + /* ---------------- + * initialize the various transaction subsystems + * ---------------- + */ + AtStart_Cache(); + AtStart_Locks(); + AtStart_Memory(); + + /* -------------- + initialize temporary relations list + the tempRelList is a list of temporary relations that + are created in the course of the transactions + they need to be destroyed properly at the end of the transactions + */ + InitTempRelList(); + + /* ---------------- + * done with start processing, set current transaction + * state to "in progress" + * ---------------- + */ + s->state = TRANS_INPROGRESS; +} + +/* --------------- + * Tell me if we are currently in progress + * --------------- + */ +bool +CurrentXactInProgress() +{ + return (CurrentTransactionState->state == TRANS_INPROGRESS); +} + +/* -------------------------------- + * CommitTransaction + * + * -------------------------------- + */ +void +CommitTransaction() +{ + TransactionState s = CurrentTransactionState; + + /* ---------------- + * check the current transaction state + * ---------------- + */ + if (s->state == TRANS_DISABLED) + return; + + if (s->state != TRANS_INPROGRESS) + elog(NOTICE, "CommitTransaction and not in in-progress state "); + + /* ---------------- + * set the current transaction state information + * appropriately during the abort processing + * ---------------- + */ + s->state = TRANS_COMMIT; + + /* ---------------- + * do commit processing + * ---------------- + */ + DestroyTempRels(); + AtEOXact_portals(); + RecordTransactionCommit(); + RelationPurgeLocalRelation(true); + AtCommit_Cache(); + AtCommit_Locks(); + AtCommit_Memory(); + + /* ---------------- + * done with commit processing, set current transaction + * state back to default + * ---------------- + */ + s->state = TRANS_DEFAULT; + { /* want this after commit */ + if (IsNormalProcessingMode()) + Async_NotifyAtCommit(); + } +} + +/* -------------------------------- + * AbortTransaction + * + * -------------------------------- + */ +void +AbortTransaction() +{ + TransactionState s = CurrentTransactionState; + + /* ---------------- + * check the current transaction state + * ---------------- + */ + if (s->state == TRANS_DISABLED) + return; + + if (s->state != TRANS_INPROGRESS) + elog(NOTICE, "AbortTransaction and not in in-progress state "); + + /* ---------------- + * set the current transaction state information + * appropriately during the abort processing + * ---------------- + */ + s->state = TRANS_ABORT; + + /* ---------------- + * do abort processing + * ---------------- + */ + AtEOXact_portals(); + RecordTransactionAbort(); + RelationPurgeLocalRelation(false); + DestroyTempRels(); + AtAbort_Cache(); + AtAbort_Locks(); + AtAbort_Memory(); + + /* ---------------- + * done with abort processing, set current transaction + * state back to default + * ---------------- + */ + s->state = TRANS_DEFAULT; + { + /* We need to do this in case another process notified us while + we are in the middle of an aborted transaction. We need to + notify our frontend after we finish the current transaction. + -- jw, 1/3/94 + */ + if (IsNormalProcessingMode()) + Async_NotifyAtAbort(); + } +} + +/* -------------------------------- + * StartTransactionCommand + * -------------------------------- + */ +void +StartTransactionCommand() +{ + TransactionState s = CurrentTransactionState; + + switch(s->blockState) { + /* ---------------- + * if we aren't in a transaction block, we + * just do our usual start transaction. + * ---------------- + */ + case TBLOCK_DEFAULT: + StartTransaction(); + break; + + /* ---------------- + * We should never experience this -- if we do it + * means the BEGIN state was not changed in the previous + * CommitTransactionCommand(). If we get it, we print + * a warning and change to the in-progress state. + * ---------------- + */ + case TBLOCK_BEGIN: + elog(NOTICE, "StartTransactionCommand: unexpected TBLOCK_BEGIN"); + s->blockState = TBLOCK_INPROGRESS; + break; + + /* ---------------- + * This is the case when are somewhere in a transaction + * block and about to start a new command. For now we + * do nothing but someday we may do command-local resource + * initialization. + * ---------------- + */ + case TBLOCK_INPROGRESS: + break; + + /* ---------------- + * As with BEGIN, we should never experience this -- + * if we do it means the END state was not changed in the + * previous CommitTransactionCommand(). If we get it, we + * print a warning, commit the transaction, start a new + * transaction and change to the default state. + * ---------------- + */ + case TBLOCK_END: + elog(NOTICE, "StartTransactionCommand: unexpected TBLOCK_END"); + s->blockState = TBLOCK_DEFAULT; + CommitTransaction(); + StartTransaction(); + break; + + /* ---------------- + * Here we are in the middle of a transaction block but + * one of the commands caused an abort so we do nothing + * but remain in the abort state. Eventually we will get + * to the "END TRANSACTION" which will set things straight. + * ---------------- + */ + case TBLOCK_ABORT: + break; + + /* ---------------- + * This means we somehow aborted and the last call to + * CommitTransactionCommand() didn't clear the state so + * we remain in the ENDABORT state and mabey next time + * we get to CommitTransactionCommand() the state will + * get reset to default. + * ---------------- + */ + case TBLOCK_ENDABORT: + elog(NOTICE, "StartTransactionCommand: unexpected TBLOCK_ENDABORT"); + break; + } +} +/* -------------------------------- + * CommitTransactionCommand + * -------------------------------- + */ +void +CommitTransactionCommand() +{ + TransactionState s = CurrentTransactionState; + + switch(s->blockState) { + /* ---------------- + * if we aren't in a transaction block, we + * just do our usual transaction commit + * ---------------- + */ + case TBLOCK_DEFAULT: + CommitTransaction(); + break; + + /* ---------------- + * This is the case right after we get a "BEGIN TRANSACTION" + * command, but the user hasn't done anything else yet, so + * we change to the "transaction block in progress" state + * and return. + * ---------------- + */ + case TBLOCK_BEGIN: + s->blockState = TBLOCK_INPROGRESS; + break; + + /* ---------------- + * This is the case when we have finished executing a command + * someplace within a transaction block. We increment the + * command counter and return. Someday we may free resources + * local to the command. + * ---------------- + */ + case TBLOCK_INPROGRESS: + CommandCounterIncrement(); + break; + + /* ---------------- + * This is the case when we just got the "END TRANSACTION" + * statement, so we go back to the default state and + * commit the transaction. + * ---------------- + */ + case TBLOCK_END: + s->blockState = TBLOCK_DEFAULT; + CommitTransaction(); + break; + + /* ---------------- + * Here we are in the middle of a transaction block but + * one of the commands caused an abort so we do nothing + * but remain in the abort state. Eventually we will get + * to the "END TRANSACTION" which will set things straight. + * ---------------- + */ + case TBLOCK_ABORT: + break; + + /* ---------------- + * Here we were in an aborted transaction block which + * just processed the "END TRANSACTION" command from the + * user, so now we return the to default state. + * ---------------- + */ + case TBLOCK_ENDABORT: + s->blockState = TBLOCK_DEFAULT; + break; + } +} + +/* -------------------------------- + * AbortCurrentTransaction + * -------------------------------- + */ +void +AbortCurrentTransaction() +{ + TransactionState s = CurrentTransactionState; + + switch(s->blockState) { + /* ---------------- + * if we aren't in a transaction block, we + * just do our usual abort transaction. + * ---------------- + */ + case TBLOCK_DEFAULT: + AbortTransaction(); + break; + + /* ---------------- + * If we are in the TBLOCK_BEGIN it means something + * screwed up right after reading "BEGIN TRANSACTION" + * so we enter the abort state. Eventually an "END + * TRANSACTION" will fix things. + * ---------------- + */ + case TBLOCK_BEGIN: + s->blockState = TBLOCK_ABORT; + AbortTransaction(); + break; + + /* ---------------- + * This is the case when are somewhere in a transaction + * block which aborted so we abort the transaction and + * set the ABORT state. Eventually an "END TRANSACTION" + * will fix things and restore us to a normal state. + * ---------------- + */ + case TBLOCK_INPROGRESS: + s->blockState = TBLOCK_ABORT; + AbortTransaction(); + break; + + /* ---------------- + * Here, the system was fouled up just after the + * user wanted to end the transaction block so we + * abort the transaction and put us back into the + * default state. + * ---------------- + */ + case TBLOCK_END: + s->blockState = TBLOCK_DEFAULT; + AbortTransaction(); + break; + + /* ---------------- + * Here, we are already in an aborted transaction + * state and are waiting for an "END TRANSACTION" to + * come along and lo and behold, we abort again! + * So we just remain in the abort state. + * ---------------- + */ + case TBLOCK_ABORT: + break; + + /* ---------------- + * Here we were in an aborted transaction block which + * just processed the "END TRANSACTION" command but somehow + * aborted again.. since we must have done the abort + * processing, we return to the default state. + * ---------------- + */ + case TBLOCK_ENDABORT: + s->blockState = TBLOCK_DEFAULT; + break; + } +} + +/* ---------------------------------------------------------------- + * transaction block support + * ---------------------------------------------------------------- + */ +/* -------------------------------- + * BeginTransactionBlock + * -------------------------------- + */ +void +BeginTransactionBlock() +{ + TransactionState s = CurrentTransactionState; + + /* ---------------- + * check the current transaction state + * ---------------- + */ + if (s->state == TRANS_DISABLED) + return; + + if (s->blockState != TBLOCK_DEFAULT) + elog(NOTICE, "BeginTransactionBlock and not in default state "); + + /* ---------------- + * set the current transaction block state information + * appropriately during begin processing + * ---------------- + */ + s->blockState = TBLOCK_BEGIN; + + /* ---------------- + * do begin processing + * ---------------- + */ + + /* ---------------- + * done with begin processing, set block state to inprogress + * ---------------- + */ + s->blockState = TBLOCK_INPROGRESS; +} + +/* -------------------------------- + * EndTransactionBlock + * -------------------------------- + */ +void +EndTransactionBlock() +{ + TransactionState s = CurrentTransactionState; + + /* ---------------- + * check the current transaction state + * ---------------- + */ + if (s->state == TRANS_DISABLED) + return; + + if (s->blockState == TBLOCK_INPROGRESS) { + /* ---------------- + * here we are in a transaction block which should commit + * when we get to the upcoming CommitTransactionCommand() + * so we set the state to "END". CommitTransactionCommand() + * will recognize this and commit the transaction and return + * us to the default state + * ---------------- + */ + s->blockState = TBLOCK_END; + return; + } + + if (s->blockState == TBLOCK_ABORT) { + /* ---------------- + * here, we are in a transaction block which aborted + * and since the AbortTransaction() was already done, + * we do whatever is needed and change to the special + * "END ABORT" state. The upcoming CommitTransactionCommand() + * will recognise this and then put us back in the default + * state. + * ---------------- + */ + s->blockState = TBLOCK_ENDABORT; + return; + } + + /* ---------------- + * We should not get here, but if we do, we go to the ENDABORT + * state after printing a warning. The upcoming call to + * CommitTransactionCommand() will then put us back into the + * default state. + * ---------------- + */ + elog(NOTICE, "EndTransactionBlock and not inprogress/abort state "); + s->blockState = TBLOCK_ENDABORT; +} + +/* -------------------------------- + * AbortTransactionBlock + * -------------------------------- + */ +void +AbortTransactionBlock() +{ + TransactionState s = CurrentTransactionState; + + /* ---------------- + * check the current transaction state + * ---------------- + */ + if (s->state == TRANS_DISABLED) + return; + + if (s->blockState == TBLOCK_INPROGRESS) { + /* ---------------- + * here we were inside a transaction block something + * screwed up inside the system so we enter the abort state, + * do the abort processing and then return. + * We remain in the abort state until we see the upcoming + * END TRANSACTION command. + * ---------------- + */ + s->blockState = TBLOCK_ABORT; + + /* ---------------- + * do abort processing and return + * ---------------- + */ + AbortTransaction(); + return; + } + + /* ---------------- + * this case should not be possible, because it would mean + * the user entered an "abort" from outside a transaction block. + * So we print an error message, abort the transaction and + * enter the "ENDABORT" state so we will end up in the default + * state after the upcoming CommitTransactionCommand(). + * ---------------- + */ + elog(NOTICE, "AbortTransactionBlock and not inprogress state"); + AbortTransaction(); + s->blockState = TBLOCK_ENDABORT; +} + +/* -------------------------------- + * UserAbortTransactionBlock + * -------------------------------- + */ +void +UserAbortTransactionBlock() +{ + TransactionState s = CurrentTransactionState; + + /* ---------------- + * check the current transaction state + * ---------------- + */ + if (s->state == TRANS_DISABLED) + return; + + if (s->blockState == TBLOCK_INPROGRESS) { + /* ---------------- + * here we were inside a transaction block and we + * got an abort command from the user, so we move to + * the abort state, do the abort processing and + * then change to the ENDABORT state so we will end up + * in the default state after the upcoming + * CommitTransactionCommand(). + * ---------------- + */ + s->blockState = TBLOCK_ABORT; + + /* ---------------- + * do abort processing + * ---------------- + */ + AbortTransaction(); + + /* ---------------- + * change to the end abort state and return + * ---------------- + */ + s->blockState = TBLOCK_ENDABORT; + return; + } + + /* ---------------- + * this case should not be possible, because it would mean + * the user entered an "abort" from outside a transaction block. + * So we print an error message, abort the transaction and + * enter the "ENDABORT" state so we will end up in the default + * state after the upcoming CommitTransactionCommand(). + * ---------------- + */ + elog(NOTICE, "UserAbortTransactionBlock and not inprogress state"); + AbortTransaction(); + s->blockState = TBLOCK_ENDABORT; +} + +bool +IsTransactionBlock() +{ + TransactionState s = CurrentTransactionState; + + if (s->blockState == TBLOCK_INPROGRESS + || s->blockState == TBLOCK_ENDABORT) { + return (true); + } + + return (false); +} diff --git a/src/backend/access/transam/xid.c b/src/backend/access/transam/xid.c new file mode 100644 index 00000000000..faeeb623d58 --- /dev/null +++ b/src/backend/access/transam/xid.c @@ -0,0 +1,156 @@ +/*------------------------------------------------------------------------- + * + * xid.c-- + * POSTGRES transaction identifier code. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/access/transam/Attic/xid.c,v 1.1.1.1 1996/07/09 06:21:14 scrappy Exp $ + * + * OLD COMMENTS + * XXX WARNING + * Much of this file will change when we change our representation + * of transaction ids -cim 3/23/90 + * + * It is time to make the switch from 5 byte to 4 byte transaction ids + * This file was totally reworked. -mer 5/22/92 + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> +#include "postgres.h" +#include "utils/palloc.h" +#include "utils/elog.h" +#include "utils/memutils.h" +#include "utils/nabstime.h" + +extern TransactionId NullTransactionId; +extern TransactionId DisabledTransactionId; +extern TransactionId AmiTransactionId; +extern TransactionId FirstTransactionId; + +/* ---------------------------------------------------------------- + * TransactionIdIsValid + * + * Macro-ize me. + * ---------------------------------------------------------------- + */ +bool +TransactionIdIsValid(TransactionId transactionId) +{ + return ((bool) (transactionId != NullTransactionId) ); +} + +/* XXX char16 name for catalogs */ +TransactionId +xidin(char *representation) +{ + return (atol(representation)); +} + +/* XXX char16 name for catalogs */ +char* +xidout(TransactionId transactionId) +{ +/* return(TransactionIdFormString(transactionId)); */ + char *representation; + + /* maximum 32 bit unsigned integer representation takes 10 chars */ + representation = palloc(11); + + (void)sprintf(representation, "%u", transactionId); + + return (representation); + +} + +/* ---------------------------------------------------------------- + * StoreInvalidTransactionId + * + * Maybe do away with Pointer types in these routines. + * Macro-ize this one. + * ---------------------------------------------------------------- + */ +void +StoreInvalidTransactionId(TransactionId *destination) +{ + *destination = NullTransactionId; +} + +/* ---------------------------------------------------------------- + * TransactionIdStore + * + * Macro-ize this one. + * ---------------------------------------------------------------- + */ +void +TransactionIdStore(TransactionId transactionId, + TransactionId *destination) +{ + *destination = transactionId; +} + +/* ---------------------------------------------------------------- + * TransactionIdEquals + * ---------------------------------------------------------------- + */ +bool +TransactionIdEquals(TransactionId id1, TransactionId id2) +{ + return ((bool) (id1 == id2)); +} + +/* ---------------------------------------------------------------- + * TransactionIdIsLessThan + * ---------------------------------------------------------------- + */ +bool +TransactionIdIsLessThan(TransactionId id1, TransactionId id2) +{ + return ((bool)(id1 < id2)); +} + +/* ---------------------------------------------------------------- + * xideq + * ---------------------------------------------------------------- + */ + +/* + * xideq - returns 1, iff xid1 == xid2 + * 0 else; + */ +bool +xideq(TransactionId xid1, TransactionId xid2) +{ + return( (bool) (xid1 == xid2) ); +} + + + +/* ---------------------------------------------------------------- + * TransactionIdIncrement + * ---------------------------------------------------------------- + */ +void +TransactionIdIncrement(TransactionId *transactionId) +{ + + (*transactionId)++; + if (*transactionId == DisabledTransactionId) + elog(FATAL, "TransactionIdIncrement: exhausted XID's"); + return; +} + +/* ---------------------------------------------------------------- + * TransactionIdAdd + * ---------------------------------------------------------------- + */ +void +TransactionIdAdd(TransactionId *xid, int value) +{ + *xid += value; + return; +} + diff --git a/src/backend/access/tupdesc.h b/src/backend/access/tupdesc.h new file mode 100644 index 00000000000..a26bbc704da --- /dev/null +++ b/src/backend/access/tupdesc.h @@ -0,0 +1,53 @@ +/*------------------------------------------------------------------------- + * + * tupdesc.h-- + * POSTGRES tuple descriptor definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: tupdesc.h,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef TUPDESC_H +#define TUPDESC_H + +#include "postgres.h" +#include "access/attnum.h" +#include "nodes/pg_list.h" /* for List */ +#include "catalog/pg_attribute.h" + +/* + * a TupleDesc is an array of AttributeTupleForms, each of which is a + * pointer to a AttributeTupleForm + */ +/* typedef AttributeTupleForm *TupleDesc; */ + +/* a TupleDesc is a pointer to a structure which includes an array of */ +/* AttributeTupleForms, i.e. pg_attribute information, and the size of */ +/* the array, i.e. the number of attributes */ +/* in short, a TupleDesc completely captures the attribute information */ +/* for a tuple */ + +typedef struct tupleDesc { + int natts; + AttributeTupleForm *attrs; +} *TupleDesc; + +extern TupleDesc CreateTemplateTupleDesc(int natts); + +extern TupleDesc CreateTupleDesc(int natts, AttributeTupleForm *attrs); + +extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc); + +extern bool TupleDescInitEntry(TupleDesc desc, + AttrNumber attributeNumber, + char *attributeName, + char *typeName, + int attdim, + bool attisset); + +extern TupleDesc BuildDescForRelation(List *schema, char *relname); + +#endif /* TUPDESC_H */ diff --git a/src/backend/access/tupmacs.h b/src/backend/access/tupmacs.h new file mode 100644 index 00000000000..9a9bcce3b41 --- /dev/null +++ b/src/backend/access/tupmacs.h @@ -0,0 +1,43 @@ +/*------------------------------------------------------------------------- + * + * tupmacs.h-- + * Tuple macros used by both index tuples and heap tuples. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: tupmacs.h,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef TUPMACS_H +#define TUPMACS_H + +/* + * check to see if the ATT'th bit of an array of 8-bit bytes is set. + */ +#define att_isnull(ATT, BITS) (!((BITS)[(ATT) >> 3] & (1 << ((ATT) & 0x07)))) + +/* + * given a AttributeTupleForm and a pointer into a tuple's data + * area, return the correct value or pointer. + * + * note that T must already be properly LONGALIGN/SHORTALIGN'd for + * this to work correctly. + * + * the double-cast is to stop gcc from (correctly) complaining about + * casting integer types with size < sizeof(char *) to (char *). + * sign-extension may get weird if you use an integer type that + * isn't the same size as (char *) for the first cast. (on the other + * hand, it's safe to use another type for the (foo *)(T).) + */ +#define fetchatt(A, T) \ + ((*(A))->attbyval \ + ? ((*(A))->attlen > sizeof(int16) \ + ? (char *) (long) *((int32 *)(T)) \ + : ((*(A))->attlen < sizeof(int16) \ + ? (char *) (long) *((char *)(T)) \ + : (char *) (long) *((int16 *)(T)))) \ + : (char *) (T)) + +#endif diff --git a/src/backend/access/valid.h b/src/backend/access/valid.h new file mode 100644 index 00000000000..1c5cf8cdeb3 --- /dev/null +++ b/src/backend/access/valid.h @@ -0,0 +1,37 @@ +/*------------------------------------------------------------------------- + * + * valid.h-- + * POSTGRES tuple qualification validity definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: valid.h,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef VALID_H +#define VALID_H + +#include "c.h" +#include "access/skey.h" +#include "storage/buf.h" +#include "utils/tqual.h" +#include "access/tupdesc.h" +#include "utils/rel.h" +#include "storage/bufpage.h" + +/* ---------------- + * extern decl's + * ---------------- + */ + +extern bool heap_keytest(HeapTuple t, TupleDesc tupdesc, + int nkeys, ScanKey keys); + +extern HeapTuple heap_tuple_satisfies(ItemId itemId, Relation relation, + PageHeader disk_page, TimeQual qual, int nKeys, ScanKey key); + +extern bool TupleUpdatedByCurXactAndCmd(HeapTuple t); + +#endif /* VALID_H */ diff --git a/src/backend/access/xact.h b/src/backend/access/xact.h new file mode 100644 index 00000000000..15f376ec5ed --- /dev/null +++ b/src/backend/access/xact.h @@ -0,0 +1,115 @@ +/*------------------------------------------------------------------------- + * + * xact.h-- + * postgres transaction system header + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: xact.h,v 1.1.1.1 1996/07/09 06:21:09 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef XACT_H +#define XACT_H + +#include <signal.h> + +#include "storage/ipc.h" +#include "miscadmin.h" +#include "utils/portal.h" +#include "utils/elog.h" +#include "utils/mcxt.h" +#include "utils/nabstime.h" + +/* ---------------- + * transaction state structure + * ---------------- + */ +typedef struct TransactionStateData { + TransactionId transactionIdData; + CommandId commandId; + AbsoluteTime startTime; + int state; + int blockState; +} TransactionStateData; + +/* ---------------- + * transaction states + * ---------------- + */ +#define TRANS_DEFAULT 0 +#define TRANS_START 1 +#define TRANS_INPROGRESS 2 +#define TRANS_COMMIT 3 +#define TRANS_ABORT 4 +#define TRANS_DISABLED 5 + +/* ---------------- + * transaction block states + * ---------------- + */ +#define TBLOCK_DEFAULT 0 +#define TBLOCK_BEGIN 1 +#define TBLOCK_INPROGRESS 2 +#define TBLOCK_END 3 +#define TBLOCK_ABORT 4 +#define TBLOCK_ENDABORT 5 + +typedef TransactionStateData *TransactionState; + +/* ---------------- + * extern definitions + * ---------------- + */ +extern int TransactionFlushEnabled(); +extern void SetTransactionFlushEnabled(bool state); + +extern bool IsTransactionState(void); +extern bool IsAbortedTransactionBlockState(void); +extern void OverrideTransactionSystem(bool flag); +extern TransactionId GetCurrentTransactionId(void); +extern CommandId GetCurrentCommandId(void); +extern AbsoluteTime GetCurrentTransactionStartTime(void); +extern bool TransactionIdIsCurrentTransactionId(TransactionId xid); +extern bool CommandIdIsCurrentCommandId(CommandId cid); +extern void ClearCommandIdCounterOverflowFlag(void); +extern void CommandCounterIncrement(void); +extern void InitializeTransactionSystem(void); +extern void AtStart_Cache(void); +extern void AtStart_Locks(void); +extern void AtStart_Memory(void); +extern void RecordTransactionCommit(void); +extern void AtCommit_Cache(void); +extern void AtCommit_Locks(void); +extern void AtCommit_Memory(void); +extern void RecordTransactionAbort(void); +extern void AtAbort_Cache(void); +extern void AtAbort_Locks(void); +extern void AtAbort_Memory(void); +extern void StartTransaction(void); +extern bool CurrentXactInProgress(void); +extern void CommitTransaction(void); +extern void AbortTransaction(void); +extern void StartTransactionCommand(void); +extern void CommitTransactionCommand(void); +extern void AbortCurrentTransaction(void); +extern void BeginTransactionBlock(void); +extern void EndTransactionBlock(void); +extern void AbortTransactionBlock(void); +extern bool IsTransactionBlock(); +extern void UserAbortTransactionBlock(); + +extern TransactionId DisabledTransactionId; + +/* defined in xid.c */ +extern bool TransactionIdIsValid(TransactionId transactionId); +extern void StoreInvalidTransactionId(TransactionId *destination); +extern void TransactionIdStore(TransactionId transactionId, + TransactionId *destination); +extern bool TransactionIdEquals(TransactionId id1, TransactionId id2); +extern bool TransactionIdIsLessThan(TransactionId id1, TransactionId id2); +extern void TransactionIdIncrement(TransactionId *transactionId); +extern void TransactionIdAdd(TransactionId *xid, int value); + +#endif /* XACT_H */ diff --git a/src/backend/bootstrap/Makefile.inc b/src/backend/bootstrap/Makefile.inc new file mode 100644 index 00000000000..72871343e83 --- /dev/null +++ b/src/backend/bootstrap/Makefile.inc @@ -0,0 +1,63 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for the bootstrap module +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/bootstrap/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:14 scrappy Exp $ +# +# +# Another kinda weird Makefile.inc cause we need two +# scanner/parsers in the backend and most yaccs and lexs +# don't have the prefix option. +# +# sed files are HACK CITY! - redo... +# +#------------------------------------------------------------------------- + +bootdir= $(CURDIR)/bootstrap +VPATH:= $(VPATH):$(bootdir) + +#BOOTYACCS= bootstrap_tokens.h bootparse.c +BOOTYACCS= bootparse.c + +SRCS_BOOTSTRAP= bootparse.c bootscanner.c bootstrap.c + +$(BOOTYACCS): bootparse.y + cd $(objdir); \ + $(YACC) $(YFLAGS) $<; \ + sed -f $(bootdir)/boot.sed < y.tab.c > bootparse.c; \ + mv y.tab.h bootstrap_tokens.h; \ + rm -f y.tab.c + +$(objdir)/bootparse.o: bootparse.c + $(cc_inobjdir) + + +bootscanner.c: bootscanner.l + cd $(objdir); \ + $(LEX) $<; \ + sed -f $(bootdir)/boot.sed < lex.yy.c > bootscanner.c; \ + rm -f lex.yy.c + +$(objdir)/bootscanner.o: bootscanner.c + $(cc_inobjdir) + + + +# +# The following insures that y.tab.h gets made as bootstrap.c +# includes it +# +bootstrap.o: $(BOOTYACCS) + +POSTGRES_DEPEND+= $(BOOTYACCS) bootscanner.c + + +CLEANFILES+= bootscanner.c $(BOOTYACCS) y.tab.h y.output + +HEADERS+= bootstrap.h + diff --git a/src/backend/bootstrap/boot.sed b/src/backend/bootstrap/boot.sed new file mode 100644 index 00000000000..8ec71025cea --- /dev/null +++ b/src/backend/bootstrap/boot.sed @@ -0,0 +1,9 @@ +# +# lex.sed - sed rules to remove conflicts between the +# bootstrap backend interface LEX scanner and the +# normal backend SQL LEX scanner +# +# $Header: /cvsroot/pgsql/src/backend/bootstrap/Attic/boot.sed,v 1.1.1.1 1996/07/09 06:21:14 scrappy Exp $ +# +s/^yy/Int_yy/g +s/\([^a-zA-Z0-9_]\)yy/\1Int_yy/g diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y new file mode 100644 index 00000000000..0362b302b16 --- /dev/null +++ b/src/backend/bootstrap/bootparse.y @@ -0,0 +1,293 @@ +%{ +/*------------------------------------------------------------------------- + * + * backendparse.y-- + * yacc parser grammer for the "backend" initialization program. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/bootstrap/bootparse.y,v 1.1.1.1 1996/07/09 06:21:14 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "access/heapam.h" +#include "access/tupdesc.h" +#include "bootstrap/bootstrap.h" +#include "utils/portal.h" +#include "storage/smgr.h" +#include "nodes/pg_list.h" +#include "catalog/catalog.h" +#include "catalog/catname.h" +#include "catalog/heap.h" +#include "catalog/index.h" +#include "commands/rename.h" +#include "commands/defrem.h" +#include "access/transam.h" +#include "access/xact.h" + +#define DO_START { StartTransactionCommand();\ + } + +#define DO_END { CommitTransactionCommand();\ + if (!Quiet) { EMITPROMPT; }\ + fflush(stdout); \ + } + +int num_tuples_read = 0; +static Oid objectid; + +%} + +%union { + List *list; + IndexElem *ielem; + char *str; + int ival; +} + +%type <list> arg_list +%type <ielem> index_params index_on +%type <ival> const ident +%type <ival> optbootstrap optoideq tuple tuplelist + +%token <ival> CONST ID +%token OPEN XCLOSE XCREATE INSERT_TUPLE +%token STRING XDEFINE +%token XDECLARE INDEX ON USING XBUILD INDICES +%token COMMA EQUALS LPAREN RPAREN +%token OBJ_ID XBOOTSTRAP NULLVAL +%start TopLevel + +%nonassoc low +%nonassoc high + +%% + +TopLevel: + Queries + | + ; + +Queries: + Query + | Queries Query + ; + +Query : + OpenStmt + | CloseStmt + | CreateStmt + | InsertStmt + | DeclareIndexStmt + | BuildIndsStmt + ; + +OpenStmt: + OPEN ident + { + DO_START; + boot_openrel(LexIDStr($2)); + DO_END; + } + ; + +CloseStmt: + XCLOSE ident %prec low + { + DO_START; + closerel(LexIDStr($2)); + DO_END; + } + | XCLOSE %prec high + { + DO_START; + closerel(NULL); + DO_END; + } + ; + +CreateStmt: + XCREATE optbootstrap ident LPAREN + { + DO_START; + numattr=(int)0; + } + typelist + { + if (!Quiet) putchar('\n'); + DO_END; + } + RPAREN + { + DO_START; + + if ($2) { + extern Relation reldesc; + TupleDesc tupdesc; + + if (reldesc) { + puts("create bootstrap: Warning, open relation"); + puts("exists, closing first"); + closerel(NULL); + } + if (DebugMode) + puts("creating bootstrap relation"); + tupdesc = CreateTupleDesc(numattr,attrtypes); + reldesc = heap_creatr(LexIDStr($3), + DEFAULT_SMGR, + tupdesc); + if (DebugMode) + puts("bootstrap relation created ok"); + } else { + Oid id; + TupleDesc tupdesc; + /* extern Oid heap_create();*/ + + tupdesc = CreateTupleDesc(numattr,attrtypes); + id = heap_create(LexIDStr($3), + NULL, + 'n', + DEFAULT_SMGR, + tupdesc); + if (!Quiet) + printf("CREATED relation %s with OID %d\n", + LexIDStr($3), id); + } + DO_END; + if (DebugMode) + puts("Commit End"); + } + ; + +InsertStmt: + INSERT_TUPLE optoideq + { + DO_START; + if (DebugMode) + printf("tuple %d<", $2); + num_tuples_read = 0; + } + LPAREN tuplelist RPAREN + { + if (num_tuples_read != numattr) + elog(WARN,"incorrect number of values for tuple"); + if (reldesc == (Relation)NULL) { + elog(WARN,"must OPEN RELATION before INSERT\n"); + err(); + } + if (DebugMode) + puts("Insert Begin"); + objectid = $2; + InsertOneTuple(objectid); + if (DebugMode) + puts("Insert End"); + if (!Quiet) { putchar('\n'); } + DO_END; + if (DebugMode) + puts("Transaction End"); + } + ; + +DeclareIndexStmt: + XDECLARE INDEX ident ON ident USING ident LPAREN index_params RPAREN + { + List *params; + + DO_START; + + params = lappend(NIL, (List*)$9); + DefineIndex(LexIDStr($5), + LexIDStr($3), + LexIDStr($7), + params, NIL, 0, NIL); + DO_END; + } + ; + +BuildIndsStmt: + XBUILD INDICES { build_indices(); } + +index_params: + index_on ident + { + IndexElem *n = (IndexElem*)$1; + n->class = LexIDStr($2); + $$ = n; + } + +index_on: + ident + { + IndexElem *n = makeNode(IndexElem); + n->name = LexIDStr($1); + $$ = n; + } + | ident LPAREN arg_list RPAREN + { + IndexElem *n = makeNode(IndexElem); + n->name = LexIDStr($1); + n->args = (List*)$3; + $$ = n; + } + +arg_list: + ident + { + $$ = lappend(NIL, makeString(LexIDStr($1))); + } + | arg_list COMMA ident + { + $$ = lappend((List*)$1, makeString(LexIDStr($3))); + } + +optbootstrap: + XBOOTSTRAP { $$ = 1; } + | { $$ = 0; } + ; + +typelist: + typething + | typelist COMMA typething + ; + +typething: + ident EQUALS ident + { + if(++numattr > MAXATTR) + elog(FATAL,"Too many attributes\n"); + DefineAttr(LexIDStr($1),LexIDStr($3),numattr-1); + if (DebugMode) + printf("\n"); + } + ; + +optoideq: + OBJ_ID EQUALS ident { $$ = atol(LexIDStr($3)); } + | { extern Oid newoid(); $$ = newoid(); } + ; + +tuplelist: + tuple + | tuplelist tuple + | tuplelist COMMA tuple + ; + +tuple: + ident {InsertOneValue(objectid, LexIDStr($1), num_tuples_read++); } + | const {InsertOneValue(objectid, LexIDStr($1), num_tuples_read++); } + | NULLVAL + { InsertOneNull(num_tuples_read++); } + ; + +const : + CONST { $$=yylval.ival; } + ; + +ident : + ID { $$=yylval.ival; } + ; +%% + + diff --git a/src/backend/bootstrap/bootscanner.l b/src/backend/bootstrap/bootscanner.l new file mode 100644 index 00000000000..9dbd92cb93a --- /dev/null +++ b/src/backend/bootstrap/bootscanner.l @@ -0,0 +1,108 @@ +%{ +/*------------------------------------------------------------------------- + * + * bootscanner.lex-- + * a lexical scanner for the bootstrap parser + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/bootstrap/bootscanner.l,v 1.1.1.1 1996/07/09 06:21:14 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "bootstrap/bootstrap.h" +#include "utils/portal.h" +#include "access/xact.h" +#include "parser/scansup.h" + +#include "bootstrap_tokens.h" + +/* some versions of lex define this as a macro */ +#if defined(yywrap) +#undef yywrap +#endif /* yywrap */ + +YYSTYPE yylval; +int yyline; /* keep track of the line number for error reporting */ + +%} + +D [0-9] +oct \\{D}{D}{D} +Exp [Ee][-+]?{D}+ +id ([A-Za-z0-9_]|{oct}|\-)+ +sid \"([^\"])*\" +arrayid [A-Za-z0-9_]+\[{D}*\] + +%% + +open { return(OPEN); } + +close { return(XCLOSE); } + +create { return(XCREATE); } + +OID { return(OBJ_ID); } +bootstrap { return(XBOOTSTRAP); } +_null_ { return(NULLVAL); } + +insert { return(INSERT_TUPLE); } + +"," { return(COMMA); } +"=" { return(EQUALS); } +"(" { return(LPAREN); } +")" { return(RPAREN); } + +[\n] { yyline++; } +[\t] ; +" " ; + +^\#[^\n]* ; /* drop everything after "#" for comments */ + + +"declare" { return(XDECLARE); } +"build" { return(XBUILD); } +"indices" { return(INDICES); } +"index" { return(INDEX); } +"on" { return(ON); } +"using" { return(USING); } +{arrayid} { + yylval.ival = EnterString(MapArrayTypeName((char*)yytext)); + return(ID); + } +{id} { + yylval.ival = EnterString(scanstr((char*)yytext)); + return(ID); + } +{sid} { + yylval.ival = EnterString(scanstr((char*)yytext)); + return(ID); + } + +(-)?{D}+"."{D}*({Exp})? | +(-)?{D}*"."{D}+({Exp})? | +(-)?{D}+{Exp} { + yylval.ival = EnterString((char*)yytext); + return(CONST); + } + +. { + printf("syntax error %d : -> %s\n", yyline, yytext); + } + + + +%% + +yywrap() +{ + return 1; +} + +yyerror(str) + char *str; +{ + fprintf(stderr,"\tsyntax error %d : %s",yyline, str); +} diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c new file mode 100644 index 00000000000..e2df755109c --- /dev/null +++ b/src/backend/bootstrap/bootstrap.c @@ -0,0 +1,1049 @@ +/*------------------------------------------------------------------------- + * + * bootstrap.c-- + * routines to support running postgres in 'bootstrap' mode + * bootstrap mode is used to create the initial template database + * + * Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/bootstrap/bootstrap.c,v 1.1.1.1 1996/07/09 06:21:14 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <string.h> +#include <unistd.h> +#include "libpq/pqsignal.h" /* substitute for <signal.h> */ +#if defined(PORTNAME_linux) +#ifndef __USE_POSIX +#define __USE_POSIX +#endif +#endif /* defined(PORTNAME_linux) */ +#include <setjmp.h> + +#define BOOTSTRAP_INCLUDE /* mask out stuff in tcop/tcopprot.h */ + +#include "bootstrap/bootstrap.h" +#include "postgres.h" +#include "miscadmin.h" +#include "tcop/tcopprot.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/tupdesc.h" +#include "utils/builtins.h" +#include "utils/rel.h" +#include "utils/tqual.h" +#include "utils/lsyscache.h" +#include "access/xact.h" +#include "utils/exc.h" /* for ExcAbort and <setjmp.h> */ +#include "fmgr.h" +#include "utils/palloc.h" +#include "utils/mcxt.h" +#include "storage/smgr.h" +#include "commands/defrem.h" + +#include "catalog/pg_type.h" +#include "catalog/catname.h" +#include "catalog/indexing.h" +#include "catalog/index.h" + +#define ALLOC(t, c) (t *)calloc((unsigned)(c), sizeof(t)) +#define FIRST_TYPE_OID 16 /* OID of the first type */ + +/* ---------------- + * global variables + * ---------------- + */ +/* + * In the lexical analyzer, we need to get the reference number quickly from + * the string, and the string from the reference number. Thus we have + * as our data structure a hash table, where the hashing key taken from + * the particular string. The hash table is chained. One of the fields + * of the hash table node is an index into the array of character pointers. + * The unique index number that every string is assigned is simply the + * position of its string pointer in the array of string pointers. + */ + +#define STRTABLESIZE 10000 +#define HASHTABLESIZE 503 + +/* Hash function numbers */ +#define NUM 23 +#define NUMSQR 529 +#define NUMCUBE 12167 + +char *strtable [STRTABLESIZE]; +hashnode *hashtable [HASHTABLESIZE]; + +static int strtable_end = -1; /* Tells us last occupied string space */ + +/*- + * Basic information associated with each type. This is used before + * pg_type is created. + * + * XXX several of these input/output functions do catalog scans + * (e.g., F_REGPROCIN scans pg_proc). this obviously creates some + * order dependencies in the catalog creation process. + */ +struct typinfo { + char name[NAMEDATALEN]; + Oid oid; + Oid elem; + int16 len; + Oid inproc; + Oid outproc; +}; + +static struct typinfo Procid[] = { + { "bool", 16, 0, 1, F_BOOLIN, F_BOOLOUT }, + { "bytea", 17, 0, -1, F_BYTEAIN, F_BYTEAOUT }, + { "char", 18, 0, 1, F_CHARIN, F_CHAROUT }, + { "name", 19, 0, NAMEDATALEN, F_NAMEIN, F_NAMEOUT }, + { "char16", 20, 0, 16, F_CHAR16IN, F_CHAR16OUT}, +/* { "dt", 20, 0, 4, F_DTIN, F_DTOUT}, */ + { "int2", 21, 0, 2, F_INT2IN, F_INT2OUT }, + { "int28", 22, 0, 16, F_INT28IN, F_INT28OUT }, + { "int4", 23, 0, 4, F_INT4IN, F_INT4OUT }, + { "regproc", 24, 0, 4, F_REGPROCIN, F_REGPROCOUT }, + { "text", 25, 0, -1, F_TEXTIN, F_TEXTOUT }, + { "oid", 26, 0, 4, F_INT4IN, F_INT4OUT }, + { "tid", 27, 0, 6, F_TIDIN, F_TIDOUT }, + { "xid", 28, 0, 5, F_XIDIN, F_XIDOUT }, + { "iid", 29, 0, 1, F_CIDIN, F_CIDOUT }, + { "oid8", 30, 0, 32, F_OID8IN, F_OID8OUT }, + { "smgr", 210, 0, 2, F_SMGRIN, F_SMGROUT }, + { "_int4", 1007, 23, -1, F_ARRAY_IN, F_ARRAY_OUT }, + { "_aclitem", 1034, 1033, -1, F_ARRAY_IN, F_ARRAY_OUT } +}; + +static int n_types = sizeof(Procid) / sizeof(struct typinfo); + +struct typmap { /* a hack */ + Oid am_oid; + TypeTupleFormData am_typ; +}; + +static struct typmap **Typ = (struct typmap **)NULL; +static struct typmap *Ap = (struct typmap *)NULL; + +static int Warnings = 0; +static char Blanks[MAXATTR]; + +Relation reldesc; /* current relation descriptor */ +static char *relname; /* current relation name */ + +AttributeTupleForm attrtypes[MAXATTR]; /* points to attribute info */ +static char *values[MAXATTR]; /* cooresponding attribute values */ +int numattr; /* number of attributes for cur. rel */ + +#if defined(WIN32) || defined(PORTNAME_next) +static jmp_buf Warn_restart; +#define sigsetjmp(x,y) setjmp(x) +#define siglongjmp longjmp +#else +static sigjmp_buf Warn_restart; +#endif + +int DebugMode; +static GlobalMemory nogc = (GlobalMemory) NULL; /* special no-gc mem context */ + +extern int optind; +extern char *optarg; + +/* + * At bootstrap time, we first declare all the indices to be built, and + * then build them. The IndexList structure stores enough information + * to allow us to build the indices after they've been declared. + */ + +typedef struct _IndexList { + char* il_heap; + char* il_ind; + int il_natts; + AttrNumber *il_attnos; + uint16 il_nparams; + Datum * il_params; + FuncIndexInfo *il_finfo; + PredInfo *il_predInfo; + struct _IndexList *il_next; +} IndexList; + +static IndexList *ILHead = (IndexList *) NULL; + +typedef void (*sig_func)(); + + + +/* ---------------------------------------------------------------- + * misc functions + * ---------------------------------------------------------------- + */ + +/* ---------------- + * error handling / abort routines + * ---------------- + */ +#if !defined(PORTNAME_bsdi) +void err() +{ + Warnings++; + cleanup(); +} +#endif + +/* usage: + usage help for the bootstrap backen +*/ +static void +usage() +{ + fprintf(stderr,"Usage: postgres -boot [-d] [-C] [-O] [-Q] [-P portno] [dbName]\n"); + fprintf(stderr," d: debug mode\n"); + fprintf(stderr," C: disable version checking\n"); + fprintf(stderr," O: set BootstrapProcessing mode\n"); + fprintf(stderr," P portno: specify port number\n"); + + exitpg(1); +} + +/* ---------------------------------------------------------------- + * BootstrapMain + * the main loop for handling the backend in bootstrap mode + * the bootstrap mode is used to initialize the template database + * the bootstrap backend doesn't speak SQL, but instead expects + * commands in a special bootstrap language. + * they are a special bootstrap language. + * + * the arguments passed in to BootstrapMain are the run-time arguments + * without the argument '-boot', the caller is required to have + * removed -boot from the run-time args + * ---------------------------------------------------------------- + */ +int +BootstrapMain(int argc, char *argv[]) +{ + int i; + int portFd = -1; + char *dbName; + int flag; + int override = 1; /* use BootstrapProcessing or InitProcessing mode */ + + extern int optind; + extern char *optarg; + + /* ---------------- + * initialize signal handlers + * ---------------- + */ + signal(SIGINT, (sig_func) die); +#ifndef WIN32 + signal(SIGHUP, (sig_func) die); + signal(SIGTERM, (sig_func) die); +#endif /* WIN32 */ + + /* -------------------- + * initialize globals + * ------------------- + */ + + InitGlobals(); + + /* ---------------- + * process command arguments + * ---------------- + */ + Quiet = 0; + Noversion = 0; + dbName = NULL; + + while ((flag = getopt(argc, argv, "dCOQP")) != EOF) { + switch (flag) { + case 'd': + DebugMode = 1; /* print out debuggin info while parsing */ + break; + case 'C': + Noversion = 1; + break; + case 'O': + override = true; + break; + case 'Q': + Quiet = 1; + break; + case 'P':/* specify port */ + portFd = atoi(optarg); + break; + default: + usage(); + break; + } + } /* while */ + + if (argc - optind > 1) { + usage(); + } else + if (argc - optind == 1) { + dbName = argv[optind]; + } + + if (dbName == NULL) { + dbName = getenv("USER"); + if (dbName == NULL) { + fputs("bootstrap backend: failed, no db name specified\n", stderr); + fputs(" and no USER enviroment variable\n", stderr); + exitpg(1); + } + } + + /* ---------------- + * initialize input fd + * ---------------- + */ + if (IsUnderPostmaster == true && portFd < 0) { + fputs("backend: failed, no -P option with -postmaster opt.\n", stderr); + exitpg(1); + } + +#ifdef WIN32 + _nt_init(); + _nt_attach(); +#endif /* WIN32 */ + + + /* ---------------- + * backend initialization + * ---------------- + */ + SetProcessingMode((override) ? BootstrapProcessing : InitProcessing); + InitPostgres(dbName); + LockDisable(true); + + for (i = 0 ; i < MAXATTR; i++) { + attrtypes[i]=(AttributeTupleForm )NULL; + Blanks[i] = ' '; + } + for(i = 0; i < STRTABLESIZE; ++i) + strtable[i] = NULL; + for(i = 0; i < HASHTABLESIZE; ++i) + hashtable[i] = NULL; + + /* ---------------- + * abort processing resumes here - What to do in WIN32? + * ---------------- + */ +#ifndef WIN32 + signal(SIGHUP, handle_warn); + + if (sigsetjmp(Warn_restart, 1) != 0) { +#else + if (setjmp(Warn_restart) != 0) { +#endif /* WIN32 */ + Warnings++; + AbortCurrentTransaction(); + } + + /* ---------------- + * process input. + * ---------------- + */ + + /* the sed script boot.sed renamed yyparse to Int_yyparse + for the bootstrap parser to avoid conflicts with the normal SQL + parser */ + Int_yyparse(); + + /* clean up processing */ + StartTransactionCommand(); + cleanup(); + + /* not reached, here to make compiler happy */ + return 0; + +} + +/* ---------------------------------------------------------------- + * MANUAL BACKEND INTERACTIVE INTERFACE COMMANDS + * ---------------------------------------------------------------- + */ + +/* ---------------- + * boot_openrel + * ---------------- + */ +void +boot_openrel(char *relname) +{ + int i; + struct typmap **app; + Relation rdesc; + HeapScanDesc sdesc; + HeapTuple tup; + + if (strlen(relname) > 15) + relname[15] ='\000'; + + if (Typ == (struct typmap **)NULL) { + StartPortalAllocMode(DefaultAllocMode, 0); + rdesc = heap_openr(TypeRelationName); + sdesc = heap_beginscan(rdesc, 0, NowTimeQual, 0, (ScanKey)NULL); + for (i=0; PointerIsValid(tup=heap_getnext(sdesc,0,(Buffer *)NULL)); ++i); + heap_endscan(sdesc); + app = Typ = ALLOC(struct typmap *, i + 1); + while (i-- > 0) + *app++ = ALLOC(struct typmap, 1); + *app = (struct typmap *)NULL; + sdesc = heap_beginscan(rdesc, 0, NowTimeQual, 0, (ScanKey)NULL); + app = Typ; + while (PointerIsValid(tup = heap_getnext(sdesc, 0, (Buffer *)NULL))) { + (*app)->am_oid = tup->t_oid; + memmove((char *)&(*app++)->am_typ, + (char *)GETSTRUCT(tup), + sizeof ((*app)->am_typ)); + } + heap_endscan(sdesc); + heap_close(rdesc); + EndPortalAllocMode(); + } + + if (reldesc != NULL) { + closerel(NULL); + } + + if (!Quiet) + printf("Amopen: relation %s. attrsize %d\n", relname, + ATTRIBUTE_TUPLE_SIZE); + + reldesc = heap_openr(relname); + Assert(reldesc); + numattr = reldesc->rd_rel->relnatts; + for (i = 0; i < numattr; i++) { + if (attrtypes[i] == NULL) { + attrtypes[i] = AllocateAttribute(); + } + memmove((char *)attrtypes[i], + (char *)reldesc->rd_att->attrs[i], + ATTRIBUTE_TUPLE_SIZE); + + /* Some old pg_attribute tuples might not have attisset. */ + /* If the attname is attisset, don't look for it - it may + not be defined yet. + */ + if (namestrcmp(&attrtypes[i]->attname, "attisset") == 0) + attrtypes[i]->attisset = get_attisset(reldesc->rd_id, + attrtypes[i]->attname.data); + else + attrtypes[i]->attisset = false; + + if (DebugMode) { + AttributeTupleForm at = attrtypes[i]; + printf("create attribute %d name %.*s len %d num %d type %d\n", + i, NAMEDATALEN, at->attname.data, at->attlen, at->attnum, + at->atttypid + ); + fflush(stdout); + } + } +} + +/* ---------------- + * closerel + * ---------------- + */ +void +closerel(char *name) +{ + if (name) { + if (reldesc) { + if (namestrcmp(RelationGetRelationName(reldesc), name) != 0) + elog(WARN,"closerel: close of '%s' when '%s' was expected", + name, relname); + } else + elog(WARN,"closerel: close of '%s' before any relation was opened", + name); + + } + + if (reldesc == NULL) { + elog(WARN,"Warning: no opened relation to close.\n"); + } else { + if (!Quiet) printf("Amclose: relation %s.\n", relname); + heap_close(reldesc); + reldesc = (Relation)NULL; + } +} + + +/* ---------------- + * DEFINEATTR() + * + * define a <field,type> pair + * if there are n fields in a relation to be created, this routine + * will be called n times + * ---------------- + */ +void +DefineAttr(char *name, char *type, int attnum) +{ + int attlen; + int t; + + if (reldesc != NULL) { + fputs("Warning: no open relations allowed with 't' command.\n",stderr); + closerel(relname); + } + + t = gettype(type); + if (attrtypes[attnum] == (AttributeTupleForm )NULL) + attrtypes[attnum] = AllocateAttribute(); + if (Typ != (struct typmap **)NULL) { + attrtypes[attnum]->atttypid = Ap->am_oid; + namestrcpy(&attrtypes[attnum]->attname, name); + if (!Quiet) printf("<%.*s %s> ", NAMEDATALEN, + attrtypes[attnum]->attname.data, type); + attrtypes[attnum]->attnum = 1 + attnum; /* fillatt */ + attlen = attrtypes[attnum]->attlen = Ap->am_typ.typlen; + attrtypes[attnum]->attbyval = Ap->am_typ.typbyval; + } else { + attrtypes[attnum]->atttypid = Procid[t].oid; + namestrcpy(&attrtypes[attnum]->attname,name); + if (!Quiet) printf("<%.*s %s> ", NAMEDATALEN, + attrtypes[attnum]->attname.data, type); + attrtypes[attnum]->attnum = 1 + attnum; /* fillatt */ + attlen = attrtypes[attnum]->attlen = Procid[t].len; + attrtypes[attnum]->attbyval = (attlen==1) || (attlen==2)||(attlen==4); + } +} + + +/* ---------------- + * InsertOneTuple + * assumes that 'oid' will not be zero. + * ---------------- + */ +void +InsertOneTuple(Oid objectid) +{ + HeapTuple tuple; + TupleDesc tupDesc; + + int i; + + if (DebugMode) { + printf("InsertOneTuple oid %d, %d attrs\n", objectid, numattr); + fflush(stdout); + } + + tupDesc = CreateTupleDesc(numattr,attrtypes); + tuple = heap_formtuple(tupDesc,(Datum*)values,Blanks); + pfree(tupDesc); /* just free's tupDesc, not the attrtypes */ + + if(objectid !=(Oid)0) { + tuple->t_oid=objectid; + } + heap_insert(reldesc, tuple); + pfree(tuple); + if (DebugMode) { + printf("End InsertOneTuple, objectid=%d\n", objectid); + fflush(stdout); + } + /* + * Reset blanks for next tuple + */ + for (i = 0; i<numattr; i++) + Blanks[i] = ' '; +} + +/* ---------------- + * InsertOneValue + * ---------------- + */ +void +InsertOneValue(Oid objectid, char *value, int i) +{ + int typeindex; + char *prt; + struct typmap **app; + + if (DebugMode) + printf("Inserting value: '%s'\n", value); + if (i < 0 || i >= MAXATTR) { + printf("i out of range: %d\n", i); + Assert(0); + } + + if (Typ != (struct typmap **)NULL) { + struct typmap *ap; + if (DebugMode) + puts("Typ != NULL"); + app = Typ; + while (*app && (*app)->am_oid != reldesc->rd_att->attrs[i]->atttypid) + ++app; + ap = *app; + if (ap == NULL) { + printf("Unable to find atttypid in Typ list! %d\n", + reldesc->rd_att->attrs[i]->atttypid + ); + Assert(0); + } + values[i] = fmgr(ap->am_typ.typinput, + value, + ap->am_typ.typelem, + -1); /* shouldn't have char() or varchar() types + during boostrapping but just to be safe */ + prt = fmgr(ap->am_typ.typoutput, values[i], + ap->am_typ.typelem); + if (!Quiet) printf("%s ", prt); + pfree(prt); + } else { + typeindex = attrtypes[i]->atttypid - FIRST_TYPE_OID; + if (DebugMode) + printf("Typ == NULL, typeindex = %d idx = %d\n", typeindex, i); + values[i] = fmgr(Procid[typeindex].inproc, value, + Procid[typeindex].elem, -1); + prt = fmgr(Procid[typeindex].outproc, values[i], + Procid[typeindex].elem); + if (!Quiet) printf("%s ", prt); + pfree(prt); + } + if (DebugMode) { + puts("End InsertValue"); + fflush(stdout); + } +} + +/* ---------------- + * InsertOneNull + * ---------------- + */ +void +InsertOneNull(int i) +{ + if (DebugMode) + printf("Inserting null\n"); + if (i < 0 || i >= MAXATTR) { + elog(FATAL, "i out of range (too many attrs): %d\n", i); + } + values[i] = (char *)NULL; + Blanks[i] = 'n'; +} + +#define MORE_THAN_THE_NUMBER_OF_CATALOGS 256 + +bool +BootstrapAlreadySeen(Oid id) +{ + static Oid seenArray[MORE_THAN_THE_NUMBER_OF_CATALOGS]; + static int nseen = 0; + bool seenthis; + int i; + + seenthis = false; + + for (i=0; i < nseen; i++) { + if (seenArray[i] == id) { + seenthis = true; + break; + } + } + if (!seenthis) { + seenArray[nseen] = id; + nseen++; + } + return (seenthis); +} + +/* ---------------- + * cleanup + * ---------------- + */ +void +cleanup() +{ + static int beenhere = 0; + + if (!beenhere) + beenhere = 1; + else { + elog(FATAL,"Memory manager fault: cleanup called twice.\n", stderr); + exitpg(1); + } + if (reldesc != (Relation)NULL) { + heap_close(reldesc); + } + CommitTransactionCommand(); + exitpg(Warnings); +} + +/* ---------------- + * gettype + * ---------------- + */ +int +gettype(char *type) +{ + int i; + Relation rdesc; + HeapScanDesc sdesc; + HeapTuple tup; + struct typmap **app; + + if (Typ != (struct typmap **)NULL) { + for (app = Typ; *app != (struct typmap *)NULL; app++) { + if (strncmp((*app)->am_typ.typname.data, type, NAMEDATALEN) == 0) { + Ap = *app; + return((*app)->am_oid); + } + } + } else { + for (i = 0; i <= n_types; i++) { + if (strncmp(type, Procid[i].name, NAMEDATALEN) == 0) { + return(i); + } + } + if (DebugMode) + printf("bootstrap.c: External Type: %.*s\n", NAMEDATALEN, type); + rdesc = heap_openr(TypeRelationName); + sdesc = heap_beginscan(rdesc, 0, NowTimeQual, 0, (ScanKey)NULL); + i = 0; + while (PointerIsValid(tup = heap_getnext(sdesc, 0, (Buffer *)NULL))) + ++i; + heap_endscan(sdesc); + app = Typ = ALLOC(struct typmap *, i + 1); + while (i-- > 0) + *app++ = ALLOC(struct typmap, 1); + *app = (struct typmap *)NULL; + sdesc = heap_beginscan(rdesc, 0, NowTimeQual, 0, (ScanKey)NULL); + app = Typ; + while (PointerIsValid(tup = heap_getnext(sdesc, 0, (Buffer *)NULL))) { + (*app)->am_oid = tup->t_oid; + memmove((char *)&(*app++)->am_typ, + (char *)GETSTRUCT(tup), + sizeof ((*app)->am_typ)); + } + heap_endscan(sdesc); + heap_close(rdesc); + return(gettype(type)); + } + elog(WARN, "Error: unknown type '%s'.\n", type); + err(); + /* not reached, here to make compiler happy */ + return 0; +} + +/* ---------------- + * AllocateAttribute + * ---------------- + */ +AttributeTupleForm /* XXX */ +AllocateAttribute() +{ + AttributeTupleForm attribute = + (AttributeTupleForm)malloc(ATTRIBUTE_TUPLE_SIZE); + + if (!PointerIsValid(attribute)) { + elog(FATAL, "AllocateAttribute: malloc failed"); + } + memset(attribute, 0, ATTRIBUTE_TUPLE_SIZE); + + return (attribute); +} + +/* ---------------- + * MapArrayTypeName + * XXX arrays of "basetype" are always "_basetype". + * this is an evil hack inherited from rel. 3.1. + * XXX array dimension is thrown away because we + * don't support fixed-dimension arrays. again, + * sickness from 3.1. + * + * the string passed in must have a '[' character in it + * + * the string returned is a pointer to static storage and should NOT + * be freed by the CALLER. + * ---------------- + */ +char* +MapArrayTypeName(char *s) +{ + int i, j; + static char newStr[NAMEDATALEN]; /* array type names < NAMEDATALEN long */ + + if (s == NULL || s[0] == '\0') + return s; + + j = 1; + newStr[0] = '_'; + for (i=0; i<NAMEDATALEN-1 && s[i] != '['; i++, j++) + newStr[j] = s[i]; + + newStr[j] = '\0'; + + return newStr; +} + +/* ---------------- + * EnterString + * returns the string table position of the identifier + * passed to it. We add it to the table if we can't find it. + * ---------------- + */ +int +EnterString (char *str) +{ + hashnode *node; + int len; + + len= strlen(str); + + node = FindStr(str, len, 0); + if (node) { + return (node->strnum); + } else { + node = AddStr(str, len, 0); + return (node->strnum); + } +} + +/* ---------------- + * LexIDStr + * when given an idnum into the 'string-table' return the string + * associated with the idnum + * ---------------- + */ +char * +LexIDStr(int ident_num) +{ + return(strtable[ident_num]); +} + + +/* ---------------- + * CompHash + * + * Compute a hash function for a given string. We look at the first, + * the last, and the middle character of a string to try to get spread + * the strings out. The function is rather arbitrary, except that we + * are mod'ing by a prime number. + * ---------------- + */ +int +CompHash(char *str, int len) +{ + register int result; + + result =(NUM * str[0] + NUMSQR * str[len-1] + NUMCUBE * str[(len-1)/2]); + + return (result % HASHTABLESIZE); + +} + +/* ---------------- + * FindStr + * + * This routine looks for the specified string in the hash + * table. It returns a pointer to the hash node found, + * or NULL if the string is not in the table. + * ---------------- + */ +hashnode * +FindStr(char *str, int length, hashnode *mderef) +{ + hashnode *node; + node = hashtable [CompHash (str, length)]; + while (node != NULL) { + /* + * We must differentiate between string constants that + * might have the same value as a identifier + * and the identifier itself. + */ + if (!strcmp(str, strtable[node->strnum])) { + return(node); /* no need to check */ + } else { + node = node->next; + } + } + /* Couldn't find it in the list */ + return (NULL); +} + +/* ---------------- + * AddStr + * + * This function adds the specified string, along with its associated + * data, to the hash table and the string table. We return the node + * so that the calling routine can find out the unique id that AddStr + * has assigned to this string. + * ---------------- + */ +hashnode * +AddStr(char *str, int strlength, int mderef) +{ + hashnode *temp, *trail, *newnode; + int hashresult; + int len; + + if (++strtable_end == STRTABLESIZE) { + /* Error, string table overflow, so we Punt */ + elog(FATAL, + "There are too many string constants and identifiers for the compiler to handle."); + + + } + + /* + * Some of the utilites (eg, define type, create relation) assume + * that the string they're passed is a NAMEDATALEN. We get array bound + * read violations from purify if we don't allocate at least NAMEDATALEN + * bytes for strings of this sort. Because we're lazy, we allocate + * at least NAMEDATALEN bytes all the time. + */ + + if ((len = strlength + 1) < NAMEDATALEN) + len = NAMEDATALEN; + + strtable [strtable_end] = malloc((unsigned) len); + strcpy (strtable[strtable_end], str); + + /* Now put a node in the hash table */ + + newnode = (hashnode*)malloc(sizeof(hashnode)*1); + newnode->strnum = strtable_end; + newnode->next = NULL; + + /* Find out where it goes */ + + hashresult = CompHash (str, strlength); + if (hashtable [hashresult] == NULL) { + hashtable [hashresult] = newnode; + } else { /* There is something in the list */ + trail = hashtable [hashresult]; + temp = trail->next; + while (temp != NULL) { + trail = temp; + temp = temp->next; + } + trail->next = newnode; + } + return (newnode); +} + + + +/* + * index_register() -- record an index that has been set up for building + * later. + * + * At bootstrap time, we define a bunch of indices on system catalogs. + * We postpone actually building the indices until just before we're + * finished with initialization, however. This is because more classes + * and indices may be defined, and we want to be sure that all of them + * are present in the index. + */ +void +index_register(char *heap, + char *ind, + int natts, + AttrNumber *attnos, + uint16 nparams, + Datum *params, + FuncIndexInfo *finfo, + PredInfo *predInfo) +{ + Datum *v; + IndexList *newind; + int len; + MemoryContext oldcxt; + + /* + * XXX mao 10/31/92 -- don't gc index reldescs, associated info + * at bootstrap time. we'll declare the indices now, but want to + * create them later. + */ + + if (nogc == (GlobalMemory) NULL) + nogc = CreateGlobalMemory("BootstrapNoGC"); + + oldcxt = MemoryContextSwitchTo((MemoryContext) nogc); + + newind = (IndexList *) palloc(sizeof(IndexList)); + newind->il_heap = pstrdup(heap); + newind->il_ind = pstrdup(ind); + newind->il_natts = natts; + + if (PointerIsValid(finfo)) + len = FIgetnArgs(finfo) * sizeof(AttrNumber); + else + len = natts * sizeof(AttrNumber); + + newind->il_attnos = (AttrNumber *) palloc(len); + memmove(newind->il_attnos, attnos, len); + + if ((newind->il_nparams = nparams) > 0) { + v = newind->il_params = (Datum *) palloc(2 * nparams * sizeof(Datum)); + nparams *= 2; + while (nparams-- > 0) { + *v = (Datum) palloc(strlen((char *)(*params)) + 1); + strcpy((char *) *v++, (char *) *params++); + } + } else { + newind->il_params = (Datum *) NULL; + } + + if (finfo != (FuncIndexInfo *) NULL) { + newind->il_finfo = (FuncIndexInfo *) palloc(sizeof(FuncIndexInfo)); + memmove(newind->il_finfo, finfo, sizeof(FuncIndexInfo)); + } else { + newind->il_finfo = (FuncIndexInfo *) NULL; + } + + if (predInfo != NULL) { + newind->il_predInfo = (PredInfo*)palloc(sizeof(PredInfo)); + newind->il_predInfo->pred = predInfo->pred; + newind->il_predInfo->oldPred = predInfo->oldPred; + } else { + newind->il_predInfo = NULL; + } + + newind->il_next = ILHead; + + ILHead = newind; + + (void) MemoryContextSwitchTo(oldcxt); +} + +void +build_indices() +{ + Relation heap; + Relation ind; + + for ( ; ILHead != (IndexList *) NULL; ILHead = ILHead->il_next) { + heap = heap_openr(ILHead->il_heap); + ind = index_openr(ILHead->il_ind); + index_build(heap, ind, ILHead->il_natts, ILHead->il_attnos, + ILHead->il_nparams, ILHead->il_params, ILHead->il_finfo, + ILHead->il_predInfo); + + /* + * All of the rest of this routine is needed only because in bootstrap + * processing we don't increment xact id's. The normal DefineIndex + * code replaces a pg_class tuple with updated info including the + * relhasindex flag (which we need to have updated). Unfortunately, + * there are always two indices defined on each catalog causing us to + * update the same pg_class tuple twice for each catalog getting an + * index during bootstrap resulting in the ghost tuple problem (see + * heap_replace). To get around this we change the relhasindex + * field ourselves in this routine keeping track of what catalogs we + * already changed so that we don't modify those tuples twice. The + * normal mechanism for updating pg_class is disabled during bootstrap. + * + * -mer + */ + heap = heap_openr(ILHead->il_heap); + + if (!BootstrapAlreadySeen(heap->rd_id)) + UpdateStats(heap->rd_id, 0, true); + } +} + diff --git a/src/backend/bootstrap/bootstrap.h b/src/backend/bootstrap/bootstrap.h new file mode 100644 index 00000000000..8ade7664f1f --- /dev/null +++ b/src/backend/bootstrap/bootstrap.h @@ -0,0 +1,78 @@ +/*------------------------------------------------------------------------- + * + * bootstrap.h-- + * include file for the bootstrapping code + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: bootstrap.h,v 1.1.1.1 1996/07/09 06:21:14 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef BOOTSTRAP_H +#define BOOTSTRAP_H + +#include <sys/file.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <ctype.h> + +#include "access/htup.h" +#include "access/itup.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "utils/tqual.h" +#include "storage/buf.h" +#include "storage/bufmgr.h" /* for BufferManagerFlush */ +#include "utils/portal.h" +#include "utils/elog.h" +#include "utils/rel.h" + +#define MAXATTR 40 /* max. number of attributes in a relation */ + +typedef struct hashnode { + int strnum; /* Index into string table */ + struct hashnode *next; +} hashnode; + +#define EMITPROMPT printf("> ") + +extern Relation reldesc; +extern AttributeTupleForm attrtypes[MAXATTR]; +extern int numattr; +extern int DebugMode; + +extern int BootstrapMain(int ac, char *av[]); +extern void index_register(char *heap, + char *ind, + int natts, + AttrNumber *attnos, + uint16 nparams, + Datum *params, + FuncIndexInfo *finfo, + PredInfo *predInfo); + +extern void err(void); +extern void InsertOneTuple(Oid objectid); +extern void closerel(char *name); +extern void boot_openrel(char *name); +extern char *LexIDStr(int ident_num); + +extern void DefineAttr(char *name, char *type, int attnum); +extern void InsertOneValue(Oid objectid, char *value, int i); +extern void InsertOneNull(int i); +extern bool BootstrapAlreadySeen(Oid id); +extern void cleanup(void); +extern int gettype(char *type); +extern AttributeTupleForm AllocateAttribute(void); +extern char* MapArrayTypeName(char *s); +extern char* CleanUpStr(char *s); +extern int EnterString (char *str); +extern int CompHash (char *str, int len); +extern hashnode *FindStr (char *str, int length, hashnode *mderef); +extern hashnode *AddStr(char *str, int strlength, int mderef); +extern void build_indices(void); + +#endif /* BOOTSTRAP_H */ diff --git a/src/backend/catalog/Makefile.inc b/src/backend/catalog/Makefile.inc new file mode 100644 index 00000000000..b29a0bfad25 --- /dev/null +++ b/src/backend/catalog/Makefile.inc @@ -0,0 +1,69 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for the system catalogs module +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/catalog/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:14 scrappy Exp $ +# +#------------------------------------------------------------------------- + +catdir=$(CURDIR)/catalog +VPATH:=$(VPATH):$(catdir) + + +SRCS_CATALOG= catalog.c heap.c index.c indexing.c \ + pg_aggregate.c pg_operator.c pg_proc.c pg_type.c + +HEADERS+= catalog.h catname.h heap.h index.h indexing.h pg_aggregate.h \ + pg_am.h pg_amop.h pg_amproc.h pg_attribute.h pg_database.h \ + pg_defaults.h pg_demon.h pg_group.h pg_index.h pg_inheritproc.h \ + pg_inherits.h pg_ipl.h pg_language.h pg_listener.h \ + pg_log.h pg_magic.h pg_opclass.h pg_operator.h pg_parg.h \ + pg_proc.h pg_class.h \ + pg_rewrite.h pg_server.h pg_statistic.h pg_time.h pg_type.h \ + pg_user.h pg_variable.h pg_version.h + +# +# The following is to create the .bki files. +# TODO: sort headers, (figure some automatic way of of determining +# the bki sources?) +# +# XXX - more grot. includes names and uid's in the header file. FIX THIS +# (not sure if i got this right - which do i need - or should i +# burn the whole damned thing) +# +ifdef ALLOW_PG_GROUP +BKIOPTS= -DALLOW_PG_GROUP +endif + +GENBKI= $(catdir)/genbki.sh +BKIFILES= global1.bki local1_template1.bki + +GLOBALBKI_SRCS= pg_database.h pg_demon.h pg_magic.h pg_defaults.h \ + pg_variable.h pg_server.h pg_user.h pg_hosts.h \ + pg_group.h pg_log.h pg_time.h + +LOCALBKI_SRCS= pg_proc.h pg_type.h pg_attribute.h pg_class.h \ + pg_inherits.h pg_index.h pg_version.h pg_statistic.h pg_operator.h \ + pg_opclass.h pg_am.h pg_amop.h pg_amproc.h pg_language.h pg_parg.h \ + pg_aggregate.h pg_ipl.h pg_inheritproc.h \ + pg_rewrite.h pg_listener.h indexing.h + +global1.bki: $(GENBKI) $(GLOBALBKI_SRCS) + sh $(SHOPTS) $(GENBKI) $(BKIOPTS) \ + $(patsubst $(GENBKI),,$^) > $(objdir)/$(@F) + + +local1_template1.bki: $(GENBKI) $(LOCALBKI_SRCS) + sh $(SHOPTS) $(GENBKI) $(BKIOPTS) \ + $(patsubst $(GENBKI),,$^) > $(objdir)/$(@F) + + +#${PROG}: ${BKIFILES} +# + +CLEANFILES+= ${BKIFILES} diff --git a/src/backend/catalog/README b/src/backend/catalog/README new file mode 100644 index 00000000000..5bfc359e382 --- /dev/null +++ b/src/backend/catalog/README @@ -0,0 +1,66 @@ +$Header: /cvsroot/pgsql/src/backend/catalog/README,v 1.1.1.1 1996/07/09 06:21:15 scrappy Exp $ + +This directory contains .c files that manipulate the system catalogs +as well as .h files that define the structure of the system catalogs. + +When the compile-time scripts (such as Gen_fmgrtab.sh and genbki.sh) +execute, they grep the DATA statements out of the .h files and munge +these in order to generate the .bki files. The .bki files are then +used as input to initdb (which is just a wrapper around postgres +running single-user in bootstrapping mode) in order to generate the +initial (template) system catalog relation files. + +----------------------------------------------------------------- + +People who are going to hose around with the .h files should be aware +of the following facts: + +- It is very important that the DATA statements be properly formatted +(e.g., no broken lines, proper use of white-space and _null_). The +scripts are line-oriented and break easily. In addition, the only +documentation on the proper format for them is the code in the +bootstrap/ directory. Just be careful when adding new DATA +statements. + +- Some catalogs require that OIDs be preallocated to tuples because +certain catalogs contain circular references. For example, pg_type +contains pointers into pg_proc (pg_type.typinput), and pg_proc +contains back-pointers into pg_type (pg_proc.proargtypes). In these +cases, the references may be explicitly set by use of the "OID =" +clause of the .bki insert statement. If no such pointers are required +to a given tuple, then the OID may be set to the wildcard value 0 +(i.e., the system generates a random OID in the usual way). + +If you need to find a valid OID for a set of tuples that refer to each +other, use the unused_oids script. It generates inclusive ranges of +*unused* OIDs (i.e., the line "45-900" means OIDs 45 through 900 have +not been allocated yet). However, you should not rely 100% on this +script, since it only looks at the .h files in the catalog/ directory. +Do a pg_grepsrc (recursive grep) of the source tree to insure that +there aren't any hidden crocks (i.e., explicit use of a numeric OID) +anywhere in the code. + +----------------------------------------------------------------- + +When munging the .c files, you should be aware of certain conventions: + +- The system catalog cache code (and most catalog-munging code in +general) assumes that the fixed-length portion of all system catalog +tuples are in fact present. That is, only the variable-length +portions of a catalog tuple are assumed to be permitted to be +non-NULL. For example, if you set pg_type.typdelim to be NULL, a +piece of code will likely perform "typetup->typdelim" (or, worse, +"typetyp->typelem", which follows typdelim). This will result in +random errors or even segmentation violations. Hence, do NOT insert +catalog tuples that contain NULL attributes except in their +variable-length portions! + +- Modification of the catalogs must be performed with the proper +updating of catalog indexes! That is, several catalogs have indexes +on them; when you munge them using the executor, the executor will +take care of doing the index updates, but if you make direct access +method calls to insert new or modified tuples into a heap, you must +also make the calls to insert the tuple into ALL of its indexes! If +not, the new tuple will generally be "invisible" to the system because +most of the accesses to the catalogs in question will be through the +associated indexes. diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c new file mode 100644 index 00000000000..25588c0f88b --- /dev/null +++ b/src/backend/catalog/catalog.c @@ -0,0 +1,205 @@ +/*------------------------------------------------------------------------- + * + * catalog.c-- + * + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/catalog/catalog.c,v 1.1.1.1 1996/07/09 06:21:15 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <string.h> /* XXX */ +#include "postgres.h" +#include "miscadmin.h" /* for DataDir */ +#include "access/htup.h" +#include "storage/buf.h" +#include "utils/elog.h" +#include "utils/palloc.h" + +#include "utils/syscache.h" +#include "catalog/catname.h" /* NameIs{,Shared}SystemRelationName */ +#include "catalog/pg_attribute.h" +#include "catalog/pg_type.h" +#include "catalog/catalog.h" +#include "storage/bufmgr.h" +#include "access/transam.h" + + +#ifndef MAXPATHLEN +#define MAXPATHLEN 80 +#endif + +/* + * relpath - path to the relation + * Perhaps this should be in-line code in relopen(). + */ +char * +relpath(char relname[]) +{ + char *path; + + if (IsSharedSystemRelationName(relname)) { + path = (char *) palloc(strlen(DataDir) + sizeof(NameData) + 2); + sprintf(path, "%s/%.*s", DataDir, NAMEDATALEN, relname); + return (path); + } + return(relname); +} + +/* + * issystem - returns non-zero iff relname is a system catalog + * + * We now make a new requirement where system catalog relns must begin + * with pg_ while user relns are forbidden to do so. Make the test + * trivial and instantaneous. + * + * XXX this is way bogus. -- pma + */ +bool +issystem(char relname[]) +{ + if (relname[0] && relname[1] && relname[2]) + return (relname[0] == 'p' && + relname[1] == 'g' && + relname[2] == '_'); + else + return FALSE; +} + +/* + * IsSystemRelationName -- + * True iff name is the name of a system catalog relation. + * + * We now make a new requirement where system catalog relns must begin + * with pg_ while user relns are forbidden to do so. Make the test + * trivial and instantaneous. + * + * XXX this is way bogus. -- pma + */ +bool +IsSystemRelationName(char *relname) +{ + if (relname[0] && relname[1] && relname[2]) + return (relname[0] == 'p' && + relname[1] == 'g' && + relname[2] == '_'); + else + return FALSE; +} + +/* + * IsSharedSystemRelationName -- + * True iff name is the name of a shared system catalog relation. + */ +bool +IsSharedSystemRelationName(char *relname) +{ + int i; + + /* + * Quick out: if it's not a system relation, it can't be a shared + * system relation. + */ + if (!IsSystemRelationName(relname)) + return FALSE; + + i = 0; + while ( SharedSystemRelationNames[i] != NULL) { + if (strcmp(SharedSystemRelationNames[i],relname) == 0) + return TRUE; + i++; + } + return FALSE; +} + +/* + * newoid - returns a unique identifier across all catalogs. + * + * Object Id allocation is now done by GetNewObjectID in + * access/transam/varsup.c. oids are now allocated correctly. + * + * old comments: + * This needs to change soon, it fails if there are too many more + * than one call per second when postgres restarts after it dies. + * + * The distribution of OID's should be done by the POSTMASTER. + * Also there needs to be a facility to preallocate OID's. Ie., + * for a block of OID's to be declared as invalid ones to allow + * user programs to use them for temporary object identifiers. + */ +Oid newoid() +{ + Oid lastoid; + + GetNewObjectId(&lastoid); + if (! OidIsValid(lastoid)) + elog(WARN, "newoid: GetNewObjectId returns invalid oid"); + return lastoid; +} + +/* + * fillatt - fills the ATTRIBUTE relation fields from the TYP + * + * Expects that the atttypid domain is set for each att[]. + * Returns with the attnum, and attlen domains set. + * attnum, attproc, atttyparg, ... should be set by the user. + * + * In the future, attnum may not be set?!? or may be passed as an arg?!? + * + * Current implementation is very inefficient--should cashe the + * information if this is at all possible. + * + * Check to see if this is really needed, and especially in the case + * of index tuples. + */ +void +fillatt(TupleDesc tupleDesc) +{ + AttributeTupleForm *attributeP; + register TypeTupleForm typp; + HeapTuple tuple; + int i; + int natts = tupleDesc->natts; + AttributeTupleForm *att = tupleDesc->attrs; + + if (natts < 0 || natts > MaxHeapAttributeNumber) + elog(WARN, "fillatt: %d attributes is too large", natts); + if (natts == 0) { + elog(DEBUG, "fillatt: called with natts == 0"); + return; + } + + attributeP = &att[0]; + + for (i = 0; i < natts;) { + tuple = SearchSysCacheTuple(TYPOID, + Int32GetDatum((*attributeP)->atttypid), + 0,0,0); + if (!HeapTupleIsValid(tuple)) { + elog(WARN, "fillatt: unknown atttypid %ld", + (*attributeP)->atttypid); + } else { + (*attributeP)->attnum = (int16) ++i; + /* Check if the attr is a set before messing with the length + and byval, since those were already set in + TupleDescInitEntry. In fact, this seems redundant + here, but who knows what I'll break if I take it out... + + same for char() and varchar() stuff. I share the same + sentiments. This function is poorly written anyway. -ay 6/95 + */ + if (!(*attributeP)->attisset && + (*attributeP)->atttypid!=BPCHAROID && + (*attributeP)->atttypid!=VARCHAROID) { + + typp = (TypeTupleForm) GETSTRUCT(tuple); /* XXX */ + (*attributeP)->attlen = typp->typlen; + (*attributeP)->attbyval = typp->typbyval; + } + } + attributeP += 1; + } +} diff --git a/src/backend/catalog/catalog.h b/src/backend/catalog/catalog.h new file mode 100644 index 00000000000..9a54e833b11 --- /dev/null +++ b/src/backend/catalog/catalog.h @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * + * catalog.h-- + * prototypes for functions in lib/catalog/catalog.c + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: catalog.h,v 1.1.1.1 1996/07/09 06:21:15 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef CATALOG_H +#define CATALOG_H + +#include "access/tupdesc.h" + +extern char *relpath(char relname[]); +extern bool IsSystemRelationName(char *relname); +extern bool IsSharedSystemRelationName(char *relname); +extern Oid newoid(void); +extern void fillatt(TupleDesc att); + +#endif /* CATALOG_H */ diff --git a/src/backend/catalog/catname.h b/src/backend/catalog/catname.h new file mode 100644 index 00000000000..8d965419493 --- /dev/null +++ b/src/backend/catalog/catname.h @@ -0,0 +1,52 @@ +/*------------------------------------------------------------------------- + * + * catname.h-- + * POSTGRES system catalog relation name definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: catname.h,v 1.1.1.1 1996/07/09 06:21:15 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef CATNAME_H +#define CATNAME_H + +#include "postgres.h" + + +#define AggregateRelationName "pg_aggregate" +#define AccessMethodRelationName "pg_am" +#define AccessMethodOperatorRelationName "pg_amop" +#define AccessMethodProcedureRelationName "pg_amproc" +#define AttributeRelationName "pg_attribute" +#define DatabaseRelationName "pg_database" +#define DefaultsRelationName "pg_defaults" +#define DemonRelationName "pg_demon" +#define GroupRelationName "pg_group" +#define HostsRelationName "pg_hosts" +#define IndexRelationName "pg_index" +#define InheritProcedureRelationName "pg_inheritproc" +#define InheritsRelationName "pg_inherits" +#define InheritancePrecidenceListRelationName "pg_ipl" +#define LanguageRelationName "pg_language" +#define ListenerRelationName "pg_listener" +#define LogRelationName "pg_log" +#define MagicRelationName "pg_magic" +#define OperatorClassRelationName "pg_opclass" +#define OperatorRelationName "pg_operator" +#define ProcedureRelationName "pg_proc" +#define RelationRelationName "pg_class" +#define RewriteRelationName "pg_rewrite" +#define ServerRelationName "pg_server" +#define StatisticRelationName "pg_statistic" +#define TimeRelationName "pg_time" +#define TypeRelationName "pg_type" +#define UserRelationName "pg_user" +#define VariableRelationName "pg_variable" +#define VersionRelationName "pg_version" + +extern char *SharedSystemRelationNames[]; + +#endif /* CATNAME_H */ diff --git a/src/backend/catalog/genbki.sh b/src/backend/catalog/genbki.sh new file mode 100644 index 00000000000..2f7e4025b83 --- /dev/null +++ b/src/backend/catalog/genbki.sh @@ -0,0 +1,218 @@ +#!/bin/sh +#------------------------------------------------------------------------- +# +# genbki.sh-- +# shell script which generates .bki files from specially formatted .h +# files. These .bki files are used to initialize the postgres template +# database. +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/catalog/Attic/genbki.sh,v 1.1.1.1 1996/07/09 06:21:15 scrappy Exp $ +# +# NOTES +# non-essential whitespace is removed from the generated file. +# if this is ever a problem, then the sed script at the very +# end can be changed into another awk script or something smarter.. +# +#------------------------------------------------------------------------- + +PATH=$PATH:/lib:/usr/ccs/lib # to find cpp +BKIOPTS='' +if [ $? != 0 ] +then + echo `basename $0`: Bad option + exit 1 +fi + +for opt in $* +do + case $opt in + -D) BKIOPTS="$BKIOPTS -D$2"; shift; shift;; + -D*) BKIOPTS="$BKIOPTS $1";shift;; + --) shift; break;; + esac +done + +# ---------------- +# collect nodefiles +# ---------------- +SYSFILES='' +x=1 +numargs=$# +while test $x -le $numargs ; do + SYSFILES="$SYSFILES $1" + x=`expr $x + 1` + shift +done + +# ---------------- +# strip comments and trash from .h before we generate +# the .bki file... +# ---------------- +# also, change Oid to oid. -- AY 8/94. +# also, change NameData to name. -- jolly 8/21/95. +# +cat $SYSFILES | \ +sed -e 's/\/\*.*\*\///g' \ + -e 's/;[ ]*$//g' \ + -e 's/\ Oid/\ oid/g' \ + -e 's/\ NameData/\ name/g' \ + -e 's/(NameData/(name/g' \ + -e 's/(Oid/(oid/g' | \ +awk ' +# ---------------- +# now use awk to process remaining .h file.. +# +# nc is the number of catalogs +# inside is a variable set to 1 when we are scanning the +# contents of a catalog definition. +# inserting_data is a flag indicating when we are processing DATA lines. +# (i.e. have a relation open and need to close it) +# ---------------- +BEGIN { + inside = 0; + raw = 0; + bootstrap = 0; + nc = 0; + reln_open = 0; +} + +# ---------------- +# anything in a BKI_BEGIN .. BKI_END block should be passed +# along without interpretation. +# ---------------- +/^BKI_BEGIN/ { raw = 1; next; } +/^BKI_END/ { raw = 0; next; } +raw == 1 { print; next; } + +# ---------------- +# DATA() statements should get passed right through after +# stripping off the DATA( and the ) on the end. +# ---------------- +/^DATA\(/ { + data = substr($0, 6, length($0) - 6); + print data; + next; +} + +/^DECLARE_INDEX\(/ { +# ---- +# end any prior catalog data insertions before starting a define index +# ---- + if (reln_open == 1) { +# print "show"; + print "close " catalog; + reln_open = 0; + } + + data = substr($0, 15, length($0) - 15); + print "declare index " data +} + +/^BUILD_INDICES/ { print "build indices"; } + +# ---------------- +# CATALOG() definitions take some more work. +# ---------------- +/^CATALOG\(/ { +# ---- +# end any prior catalog data insertions before starting a new one.. +# ---- + if (reln_open == 1) { +# print "show"; + print "close " catalog; + reln_open = 0; + } + +# ---- +# get the name of the new catalog +# ---- + pos = index($1,")"); + catalog = substr($1,9,pos-9); + + if ($0 ~ /BOOTSTRAP/) { + bootstrap = 1; + } + + i = 1; + inside = 1; + nc++; + next; +} + +# ---------------- +# process the contents of the catalog definition +# +# attname[ x ] contains the attribute name for attribute x +# atttype[ x ] contains the attribute type fot attribute x +# ---------------- +inside == 1 { +# ---- +# ignore a leading brace line.. +# ---- + if ($1 ~ /\{/) + next; + +# ---- +# if this is the last line, then output the bki catalog stuff. +# ---- + if ($1 ~ /}/) { + if (bootstrap) { + print "create bootstrap " catalog; + } else { + print "create " catalog; + } + print "\t("; + + for (j=1; j<i-1; j++) { + print "\t " attname[ j ] " = " atttype[ j ] " ,"; + } + print "\t " attname[ j ] " = " atttype[ j ] ; + print "\t)"; + + if (! bootstrap) { + print "open " catalog; + } + + i = 1; + reln_open = 1; + inside = 0; + bootstrap = 0; + next; + } + +# ---- +# if we are inside the catalog definition, then keep sucking up +# attibute names and types +# ---- + if ($2 ~ /\[.*\]/) { # array attribute + idlen = index($2,"[") - 1; + atttype[ i ] = $1 "[]"; # variable-length only.. + attname[ i ] = substr($2,1,idlen); + } else { + atttype[ i ] = $1; + attname[ i ] = $2; + } + i++; + next; +} + +END { + if (reln_open == 1) { +# print "show"; + print "close " catalog; + reln_open = 0; + } +} +' | \ +cpp $BKIOPTS | \ +sed -e '/^[ ]*$/d' \ + -e 's/[ ][ ]*/ /g' + +# ---------------- +# all done +# ---------------- +exit 0 diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c new file mode 100644 index 00000000000..6e9634aa95a --- /dev/null +++ b/src/backend/catalog/heap.c @@ -0,0 +1,1428 @@ +/*------------------------------------------------------------------------- + * + * heap.c-- + * code to create and destroy POSTGRES heap relations + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.1.1.1 1996/07/09 06:21:15 scrappy Exp $ + * + * INTERFACE ROUTINES + * heap_creatr() - Create an uncataloged heap relation + * heap_create() - Create a cataloged relation + * heap_destroy() - Removes named relation from catalogs + * + * NOTES + * this code taken from access/heap/create.c, which contains + * the old heap_creater, amcreate, and amdestroy. those routines + * will soon call these routines using the function manager, + * just like the poorly named "NewXXX" routines do. The + * "New" routines are all going to die soon, once and for all! + * -cim 1/13/91 + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> /* for sprintf() */ +#include <sys/file.h> +#include <string.h> + +#include "postgres.h" + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/htup.h" +#include "access/istrat.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "utils/tqual.h" /* for NowTimeQual */ +#include "storage/buf.h" +#include "storage/bufmgr.h" +#include "storage/itemptr.h" +#include "lib/hasht.h" +#include "miscadmin.h" +#include "fmgr.h" +#include "utils/builtins.h" +#include "utils/elog.h" /* XXX */ +#include "utils/mcxt.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/relcache.h" + +#include "catalog/catname.h" +#include "catalog/pg_class.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_index.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_ipl.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "catalog/index.h" +#include "catalog/indexing.h" + +#include "catalog/catalog.h" +#include "parser/catalog_utils.h" + +#include "storage/lmgr.h" + +#include "rewrite/rewriteRemove.h" + +static void AddNewAttributeTuples(Oid new_rel_oid, TupleDesc tupdesc); +static void CheckAttributeNames(TupleDesc tupdesc); + +/* ---------------------------------------------------------------- + * XXX UGLY HARD CODED BADNESS FOLLOWS XXX + * + * these should all be moved to someplace in the lib/catalog + * module, if not obliterated first. + * ---------------------------------------------------------------- + */ + + +/* + * Note: + * Should the executor special case these attributes in the future? + * Advantage: consume 1/2 the space in the ATTRIBUTE relation. + * Disadvantage: having rules to compute values in these tuples may + * be more difficult if not impossible. + */ + +static FormData_pg_attribute a1 = { + 0xffffffff, {"ctid"}, 27l, 0l, 0l, 0l, sizeof (ItemPointerData), + SelfItemPointerAttributeNumber, 0, '\0', '\001', 0l, 'i' +}; + +static FormData_pg_attribute a2 = { + 0xffffffff, {"oid"}, 26l, 0l, 0l, 0l, sizeof(Oid), + ObjectIdAttributeNumber, 0, '\001', '\001', 0l, 'i' +}; + +static FormData_pg_attribute a3 = { + 0xffffffff, {"xmin"}, 28l, 0l, 0l, 0l, sizeof (TransactionId), + MinTransactionIdAttributeNumber, 0, '\0', '\001', 0l, 'i', +}; + +static FormData_pg_attribute a4 = { + 0xffffffff, {"cmin"}, 29l, 0l, 0l, 0l, sizeof (CommandId), + MinCommandIdAttributeNumber, 0, '\001', '\001', 0l, 's' +}; + +static FormData_pg_attribute a5 = { + 0xffffffff, {"xmax"}, 28l, 0l, 0l, 0l, sizeof (TransactionId), + MaxTransactionIdAttributeNumber, 0, '\0', '\001', 0l, 'i' +}; + +static FormData_pg_attribute a6 = { + 0xffffffff, {"cmax"}, 29l, 0l, 0l, 0l, sizeof (CommandId), + MaxCommandIdAttributeNumber, 0, '\001', '\001', 0l, 's' +}; + +static FormData_pg_attribute a7 = { + 0xffffffff, {"chain"}, 27l, 0l, 0l, 0l, sizeof (ItemPointerData), + ChainItemPointerAttributeNumber, 0, '\0', '\001', 0l, 'i', +}; + +static FormData_pg_attribute a8 = { + 0xffffffff, {"anchor"}, 27l, 0l, 0l, 0l, sizeof (ItemPointerData), + AnchorItemPointerAttributeNumber, 0, '\0', '\001', 0l, 'i' +}; + +static FormData_pg_attribute a9 = { + 0xffffffff, {"tmin"}, 20l, 0l, 0l, 0l, sizeof (AbsoluteTime), + MinAbsoluteTimeAttributeNumber, 0, '\001', '\001', 0l, 'i' +}; + +static FormData_pg_attribute a10 = { + 0xffffffff, {"tmax"}, 20l, 0l, 0l, 0l, sizeof (AbsoluteTime), + MaxAbsoluteTimeAttributeNumber, 0, '\001', '\001', 0l, 'i' +}; + +static FormData_pg_attribute a11 = { + 0xffffffff, {"vtype"}, 18l, 0l, 0l, 0l, sizeof (char), + VersionTypeAttributeNumber, 0, '\001', '\001', 0l, 'c' +}; + +static AttributeTupleForm HeapAtt[] = +{ &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10, &a11 }; + +/* ---------------------------------------------------------------- + * XXX END OF UGLY HARD CODED BADNESS XXX + * ---------------------------------------------------------------- + */ + +/* the tempRelList holds + the list of temporary uncatalogued relations that are created. + these relations should be destroyed at the end of transactions +*/ +typedef struct tempRelList { + Relation *rels; /* array of relation descriptors */ + int num; /* number of temporary relations */ + int size; /* size of space allocated for the rels array */ +} TempRelList; + +#define TEMP_REL_LIST_SIZE 32 + +static TempRelList *tempRels = NULL; + + +/* ---------------------------------------------------------------- + * heap_creatr - Create an uncataloged heap relation + * + * Fields relpages, reltuples, reltuples, relkeys, relhistory, + * relisindexed, and relkind of rdesc->rd_rel are initialized + * to all zeros, as are rd_last and rd_hook. Rd_refcnt is set to 1. + * + * Remove the system relation specific code to elsewhere eventually. + * + * Eventually, must place information about this temporary relation + * into the transaction context block. + * + * + * if heap_creatr is called with "" as the name, then heap_creatr will create a + * temporary name "temp_$RELOID" for the relation + * ---------------------------------------------------------------- + */ +Relation +heap_creatr(char *name, + unsigned smgr, + TupleDesc tupDesc) +{ + register unsigned i; + Oid relid; + Relation rdesc; + int len; + bool nailme = false; + char* relname = name; + char tempname[40]; + int isTemp = 0; + int natts = tupDesc->natts; +/* AttributeTupleForm *att = tupDesc->attrs; */ + + extern GlobalMemory CacheCxt; + MemoryContext oldcxt; + + /* ---------------- + * sanity checks + * ---------------- + */ + AssertArg(natts > 0); + + if (IsSystemRelationName(relname) && IsNormalProcessingMode()) + { + elog(WARN, + "Illegal class name: %s -- pg_ is reserved for system catalogs", + relname); + } + + /* ---------------- + * switch to the cache context so that we don't lose + * allocations at the end of this transaction, I guess. + * -cim 6/14/90 + * ---------------- + */ + if (!CacheCxt) + CacheCxt = CreateGlobalMemory("Cache"); + + oldcxt = MemoryContextSwitchTo((MemoryContext)CacheCxt); + + /* ---------------- + * real ugly stuff to assign the proper relid in the relation + * descriptor follows. + * ---------------- + */ + if (! strcmp(RelationRelationName,relname)) + { + relid = RelOid_pg_class; + nailme = true; + } + else if (! strcmp(AttributeRelationName,relname)) + { + relid = RelOid_pg_attribute; + nailme = true; + } + else if (! strcmp(ProcedureRelationName, relname)) + { + relid = RelOid_pg_proc; + nailme = true; + } + else if (! strcmp(TypeRelationName,relname)) + { + relid = RelOid_pg_type; + nailme = true; + } + else + { + relid = newoid(); + + if (name[0] == '\0') + { + sprintf(tempname, "temp_%d", relid); + relname = tempname; + isTemp = 1; + }; + } + + /* ---------------- + * allocate a new relation descriptor. + * + * XXX the length computation may be incorrect, handle elsewhere + * ---------------- + */ + len = sizeof(RelationData); + + rdesc = (Relation) palloc(len); + memset((char *)rdesc, 0,len); + + /* ---------- + create a new tuple descriptor from the one passed in + */ + rdesc->rd_att = CreateTupleDescCopy(tupDesc); + + /* ---------------- + * initialize the fields of our new relation descriptor + * ---------------- + */ + + /* ---------------- + * nail the reldesc if this is a bootstrap create reln and + * we may need it in the cache later on in the bootstrap + * process so we don't ever want it kicked out. e.g. pg_attribute!!! + * ---------------- + */ + if (nailme) + rdesc->rd_isnailed = true; + + RelationSetReferenceCount(rdesc, 1); + + rdesc->rd_rel = (Form_pg_class)palloc(sizeof *rdesc->rd_rel); + + memset((char *)rdesc->rd_rel, 0, + sizeof *rdesc->rd_rel); + namestrcpy(&(rdesc->rd_rel->relname), relname); + rdesc->rd_rel->relkind = RELKIND_UNCATALOGED; + rdesc->rd_rel->relnatts = natts; + rdesc->rd_rel->relsmgr = smgr; + + for (i = 0; i < natts; i++) { + rdesc->rd_att->attrs[i]->attrelid = relid; + } + + rdesc->rd_id = relid; + + if (nailme) { + /* for system relations, set the reltype field here */ + rdesc->rd_rel->reltype = relid; + } + + /* ---------------- + * have the storage manager create the relation. + * ---------------- + */ + + rdesc->rd_fd = (File)smgrcreate(smgr, rdesc); + + RelationRegisterRelation(rdesc); + + MemoryContextSwitchTo(oldcxt); + + /* add all temporary relations to the tempRels list + so they can be properly disposed of at the end of transaction + */ + if (isTemp) + AddToTempRelList(rdesc); + + return (rdesc); +} + + +/* ---------------------------------------------------------------- + * heap_create - Create a cataloged relation + * + * this is done in 6 steps: + * + * 1) CheckAttributeNames() is used to make certain the tuple + * descriptor contains a valid set of attribute names + * + * 2) pg_class is opened and RelationAlreadyExists() + * preforms a scan to ensure that no relation with the + * same name already exists. + * + * 3) heap_creater() is called to create the new relation on + * disk. + * + * 4) TypeDefine() is called to define a new type corresponding + * to the new relation. + * + * 5) AddNewAttributeTuples() is called to register the + * new relation's schema in pg_attribute. + * + * 6) AddPgRelationTuple() is called to register the + * relation itself in the catalogs. + * + * 7) the relations are closed and the new relation's oid + * is returned. + * + * old comments: + * A new relation is inserted into the RELATION relation + * with the specified attribute(s) (newly inserted into + * the ATTRIBUTE relation). How does concurrency control + * work? Is it automatic now? Expects the caller to have + * attname, atttypid, atttyparg, attproc, and attlen domains filled. + * Create fills the attnum domains sequentually from zero, + * fills the attnvals domains with zeros, and fills the + * attrelid fields with the relid. + * + * scan relation catalog for name conflict + * scan type catalog for typids (if not arg) + * create and insert attribute(s) into attribute catalog + * create new relation + * insert new relation into attribute catalog + * + * Should coordinate with heap_creater(). Either it should + * not be called or there should be a way to prevent + * the relation from being removed at the end of the + * transaction if it is successful ('u'/'r' may be enough). + * Also, if the transaction does not commit, then the + * relation should be removed. + * + * XXX amcreate ignores "off" when inserting (for now). + * XXX amcreate (like the other utilities) needs to understand indexes. + * + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * CheckAttributeNames + * + * this is used to make certain the tuple descriptor contains a + * valid set of attribute names. a problem simply generates + * elog(WARN) which aborts the current transaction. + * -------------------------------- + */ +static void +CheckAttributeNames(TupleDesc tupdesc) +{ + unsigned i; + unsigned j; + int natts = tupdesc->natts; + + /* ---------------- + * first check for collision with system attribute names + * ---------------- + * + * also, warn user if attribute to be created has + * an unknown typid (usually as a result of a 'retrieve into' + * - jolly + */ + for (i = 0; i < natts; i += 1) { + for (j = 0; j < sizeof HeapAtt / sizeof HeapAtt[0]; j += 1) { + if (nameeq(&(HeapAtt[j]->attname), + &(tupdesc->attrs[i]->attname))) { + elog(WARN, + "create: system attribute named \"%s\"", + HeapAtt[j]->attname.data); + } + } + if (tupdesc->attrs[i]->atttypid == UNKNOWNOID) + { + elog(NOTICE, + "create: attribute named \"%s\" has an unknown type", + tupdesc->attrs[i]->attname.data); + } + } + + /* ---------------- + * next check for repeated attribute names + * ---------------- + */ + for (i = 1; i < natts; i += 1) { + for (j = 0; j < i; j += 1) { + if (nameeq(&(tupdesc->attrs[j]->attname), + &(tupdesc->attrs[i]->attname))) { + elog(WARN, + "create: repeated attribute \"%s\"", + tupdesc->attrs[j]->attname.data); + } + } + } +} + +/* -------------------------------- + * RelationAlreadyExists + * + * this preforms a scan of pg_class to ensure that + * no relation with the same name already exists. The caller + * has to open pg_class and pass an open descriptor. + * -------------------------------- + */ +int +RelationAlreadyExists(Relation pg_class_desc, char relname[]) +{ + ScanKeyData key; + HeapScanDesc pg_class_scan; + HeapTuple tup; + + /* + * If this is not bootstrap (initdb) time, use the catalog index + * on pg_class. + */ + + if (!IsBootstrapProcessingMode()) { + tup = ClassNameIndexScan(pg_class_desc, relname); + if (HeapTupleIsValid(tup)) { + pfree(tup); + return ((int) true); + } else + return ((int) false); + } + + /* ---------------- + * At bootstrap time, we have to do this the hard way. Form the + * scan key. + * ---------------- + */ + ScanKeyEntryInitialize(&key, + 0, + (AttrNumber)Anum_pg_class_relname, + (RegProcedure)NameEqualRegProcedure, + (Datum) relname); + + /* ---------------- + * begin the scan + * ---------------- + */ + pg_class_scan = heap_beginscan(pg_class_desc, + 0, + NowTimeQual, + 1, + &key); + + /* ---------------- + * get a tuple. if the tuple is NULL then it means we + * didn't find an existing relation. + * ---------------- + */ + tup = heap_getnext(pg_class_scan, 0, (Buffer *)NULL); + + /* ---------------- + * end the scan and return existance of relation. + * ---------------- + */ + heap_endscan(pg_class_scan); + + return + (PointerIsValid(tup) == true); +} + +/* -------------------------------- + * AddNewAttributeTuples + * + * this registers the new relation's schema by adding + * tuples to pg_attribute. + * -------------------------------- + */ +static void +AddNewAttributeTuples(Oid new_rel_oid, + TupleDesc tupdesc) +{ + AttributeTupleForm *dpp; + unsigned i; + HeapTuple tup; + Relation rdesc; + bool hasindex; + Relation idescs[Num_pg_attr_indices]; + int natts = tupdesc->natts; + + /* ---------------- + * open pg_attribute + * ---------------- + */ + rdesc = heap_openr(AttributeRelationName); + + /* ----------------- + * Check if we have any indices defined on pg_attribute. + * ----------------- + */ + Assert(rdesc); + Assert(rdesc->rd_rel); + hasindex = RelationGetRelationTupleForm(rdesc)->relhasindex; + if (hasindex) + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); + + /* ---------------- + * initialize tuple descriptor. Note we use setheapoverride() + * so that we can see the effects of our TypeDefine() done + * previously. + * ---------------- + */ + setheapoverride(true); + fillatt(tupdesc); + setheapoverride(false); + + /* ---------------- + * first we add the user attributes.. + * ---------------- + */ + dpp = tupdesc->attrs; + for (i = 0; i < natts; i++) { + (*dpp)->attrelid = new_rel_oid; + (*dpp)->attnvals = 0l; + + tup = heap_addheader(Natts_pg_attribute, + ATTRIBUTE_TUPLE_SIZE, + (char *) *dpp); + + heap_insert(rdesc, tup); + + if (hasindex) + CatalogIndexInsert(idescs, Num_pg_attr_indices, rdesc, tup); + + pfree(tup); + dpp++; + } + + /* ---------------- + * next we add the system attributes.. + * ---------------- + */ + dpp = HeapAtt; + for (i = 0; i < -1 - FirstLowInvalidHeapAttributeNumber; i++) { + (*dpp)->attrelid = new_rel_oid; + /* (*dpp)->attnvals = 0l; unneeded */ + + tup = heap_addheader(Natts_pg_attribute, + ATTRIBUTE_TUPLE_SIZE, + (char *)*dpp); + + heap_insert(rdesc, tup); + + if (hasindex) + CatalogIndexInsert(idescs, Num_pg_attr_indices, rdesc, tup); + + pfree(tup); + dpp++; + } + + heap_close(rdesc); + + /* + * close pg_attribute indices + */ + if (hasindex) + CatalogCloseIndices(Num_pg_attr_indices, idescs); +} + +/* -------------------------------- + * AddPgRelationTuple + * + * this registers the new relation in the catalogs by + * adding a tuple to pg_class. + * -------------------------------- + */ +void +AddPgRelationTuple(Relation pg_class_desc, + Relation new_rel_desc, + Oid new_rel_oid, + int arch, + unsigned natts) +{ + Form_pg_class new_rel_reltup; + HeapTuple tup; + Relation idescs[Num_pg_class_indices]; + bool isBootstrap; + + /* ---------------- + * first we munge some of the information in our + * uncataloged relation's relation descriptor. + * ---------------- + */ + new_rel_reltup = new_rel_desc->rd_rel; + + /* CHECK should get new_rel_oid first via an insert then use XXX */ + /* new_rel_reltup->reltuples = 1; */ /* XXX */ + + new_rel_reltup->relowner = GetUserId(); + new_rel_reltup->relkind = RELKIND_RELATION; + new_rel_reltup->relarch = arch; + new_rel_reltup->relnatts = natts; + + /* ---------------- + * now form a tuple to add to pg_class + * XXX Natts_pg_class_fixed is a hack - see pg_class.h + * ---------------- + */ + tup = heap_addheader(Natts_pg_class_fixed, + CLASS_TUPLE_SIZE, + (char *) new_rel_reltup); + tup->t_oid = new_rel_oid; + + /* ---------------- + * finally insert the new tuple and free it. + * + * Note: I have no idea why we do a + * SetProcessingMode(BootstrapProcessing); + * here -cim 6/14/90 + * ---------------- + */ + isBootstrap = IsBootstrapProcessingMode() ? true : false; + + SetProcessingMode(BootstrapProcessing); + + heap_insert(pg_class_desc, tup); + + if (! isBootstrap) { + /* + * First, open the catalog indices and insert index tuples for + * the new relation. + */ + + CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_class_indices, pg_class_desc, tup); + CatalogCloseIndices(Num_pg_class_indices, idescs); + + /* now restore processing mode */ + SetProcessingMode(NormalProcessing); + } + + pfree(tup); +} + + +/* -------------------------------- + * addNewRelationType - + * + * define a complex type corresponding to the new relation + * -------------------------------- + */ +void +addNewRelationType(char *typeName, Oid new_rel_oid) +{ + Oid new_type_oid; + + /* The sizes are set to oid size because it makes implementing sets MUCH + * easier, and no one (we hope) uses these fields to figure out + * how much space to allocate for the type. + * An oid is the type used for a set definition. When a user + * requests a set, what they actually get is the oid of a tuple in + * the pg_proc catalog, so the size of the "set" is the size + * of an oid. + * Similarly, byval being true makes sets much easier, and + * it isn't used by anything else. + * Note the assumption that OIDs are the same size as int4s. + */ + new_type_oid = TypeCreate(typeName, /* type name */ + new_rel_oid, /* relation oid */ + tlen(type("oid")), /* internal size */ + tlen(type("oid")), /* external size */ + 'c', /* type-type (catalog) */ + ',', /* default array delimiter */ + "int4in", /* input procedure */ + "int4out", /* output procedure */ + "int4in", /* send procedure */ + "int4out", /* receive procedure */ + NULL, /* array element type - irrelevent */ + "-", /* default type value */ + (bool) 1, /* passed by value */ + 'i'); /* default alignment */ +} + +/* -------------------------------- + * heap_create + * + * creates a new cataloged relation. see comments above. + * -------------------------------- + */ +Oid +heap_create(char relname[], + char *typename, /* not used currently */ + int arch, + unsigned smgr, + TupleDesc tupdesc) +{ + Relation pg_class_desc; + Relation new_rel_desc; + Oid new_rel_oid; +/* NameData typeNameData; */ + int natts = tupdesc->natts; + + /* ---------------- + * sanity checks + * ---------------- + */ + AssertState(IsNormalProcessingMode() || IsBootstrapProcessingMode()); + if (natts == 0 || natts > MaxHeapAttributeNumber) + elog(WARN, "amcreate: from 1 to %d attributes must be specified", + MaxHeapAttributeNumber); + + CheckAttributeNames(tupdesc); + + /* ---------------- + * open pg_class and see that the relation doesn't + * already exist. + * ---------------- + */ + pg_class_desc = heap_openr(RelationRelationName); + + if (RelationAlreadyExists(pg_class_desc, relname)) { + heap_close(pg_class_desc); + elog(WARN, "amcreate: %s relation already exists", relname); + } + + /* ---------------- + * ok, relation does not already exist so now we + * create an uncataloged relation and pull its relation oid + * from the newly formed relation descriptor. + * + * Note: The call to heap_creatr() does all the "real" work + * of creating the disk file for the relation. + * ---------------- + */ + new_rel_desc = heap_creatr(relname, smgr, tupdesc); + new_rel_oid = new_rel_desc->rd_att->attrs[0]->attrelid; + + /* ---------------- + * since defining a relation also defines a complex type, + * we add a new system type corresponding to the new relation. + * ---------------- + */ +/* namestrcpy(&typeNameData, relname);*/ +/* addNewRelationType(&typeNameData, new_rel_oid);*/ + addNewRelationType(relname, new_rel_oid); + + /* ---------------- + * now add tuples to pg_attribute for the attributes in + * our new relation. + * ---------------- + */ + AddNewAttributeTuples(new_rel_oid, tupdesc); + + /* ---------------- + * now update the information in pg_class. + * ---------------- + */ + AddPgRelationTuple(pg_class_desc, + new_rel_desc, + new_rel_oid, + arch, + natts); + + /* ---------------- + * ok, the relation has been cataloged, so close our relations + * and return the oid of the newly created relation. + * + * SOMEDAY: fill the STATISTIC relation properly. + * ---------------- + */ + heap_close(new_rel_desc); + heap_close(pg_class_desc); + + return new_rel_oid; +} + + +/* ---------------------------------------------------------------- + * heap_destroy - removes all record of named relation from catalogs + * + * 1) open relation, check for existence, etc. + * 2) remove inheritance information + * 3) remove indexes + * 4) remove pg_class tuple + * 5) remove pg_attribute tuples + * 6) remove pg_type tuples + * 7) unlink relation + * + * old comments + * Except for vital relations, removes relation from + * relation catalog, and related attributes from + * attribute catalog (needed?). (Anything else???) + * + * get proper relation from relation catalog (if not arg) + * check if relation is vital (strcmp()/reltype???) + * scan attribute catalog deleting attributes of reldesc + * (necessary?) + * delete relation from relation catalog + * (How are the tuples of the relation discarded???) + * + * XXX Must fix to work with indexes. + * There may be a better order for doing things. + * Problems with destroying a deleted database--cannot create + * a struct reldesc without having an open file descriptor. + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * RelationRemoveInheritance + * + * Note: for now, we cause an exception if relation is a + * superclass. Someday, we may want to allow this and merge + * the type info into subclass procedures.... this seems like + * lots of work. + * -------------------------------- + */ +void +RelationRemoveInheritance(Relation relation) +{ + Relation catalogRelation; + HeapTuple tuple; + HeapScanDesc scan; + ScanKeyData entry; + + /* ---------------- + * open pg_inherits + * ---------------- + */ + catalogRelation = heap_openr(InheritsRelationName); + + /* ---------------- + * form a scan key for the subclasses of this class + * and begin scanning + * ---------------- + */ + ScanKeyEntryInitialize(&entry, 0x0, Anum_pg_inherits_inhparent, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(RelationGetRelationId(relation))); + + scan = heap_beginscan(catalogRelation, + false, + NowTimeQual, + 1, + &entry); + + /* ---------------- + * if any subclasses exist, then we disallow the deletion. + * ---------------- + */ + tuple = heap_getnext(scan, 0, (Buffer *)NULL); + if (HeapTupleIsValid(tuple)) { + heap_endscan(scan); + heap_close(catalogRelation); + + elog(WARN, "relation <%d> inherits \"%s\"", + ((InheritsTupleForm) GETSTRUCT(tuple))->inhrel, + RelationGetRelationName(relation)); + } + + /* ---------------- + * If we get here, it means the relation has no subclasses + * so we can trash it. First we remove dead INHERITS tuples. + * ---------------- + */ + entry.sk_attno = Anum_pg_inherits_inhrel; + + scan = heap_beginscan(catalogRelation, + false, + NowTimeQual, + 1, + &entry); + + for (;;) { + tuple = heap_getnext(scan, 0, (Buffer *)NULL); + if (!HeapTupleIsValid(tuple)) { + break; + } + heap_delete(catalogRelation, &tuple->t_ctid); + } + + heap_endscan(scan); + heap_close(catalogRelation); + + /* ---------------- + * now remove dead IPL tuples + * ---------------- + */ + catalogRelation = + heap_openr(InheritancePrecidenceListRelationName); + + entry.sk_attno = Anum_pg_ipl_iplrel; + + scan = heap_beginscan(catalogRelation, + false, + NowTimeQual, + 1, + &entry); + + for (;;) { + tuple = heap_getnext(scan, 0, (Buffer *)NULL); + if (!HeapTupleIsValid(tuple)) { + break; + } + heap_delete(catalogRelation, &tuple->t_ctid); + } + + heap_endscan(scan); + heap_close(catalogRelation); +} + +/* -------------------------------- + * RelationRemoveIndexes + * + * -------------------------------- + */ +void +RelationRemoveIndexes(Relation relation) +{ + Relation indexRelation; + HeapTuple tuple; + HeapScanDesc scan; + ScanKeyData entry; + + indexRelation = heap_openr(IndexRelationName); + + ScanKeyEntryInitialize(&entry, 0x0, Anum_pg_index_indrelid, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(RelationGetRelationId(relation))); + + scan = heap_beginscan(indexRelation, + false, + NowTimeQual, + 1, + &entry); + + for (;;) { + tuple = heap_getnext(scan, 0, (Buffer *)NULL); + if (!HeapTupleIsValid(tuple)) { + break; + } + + index_destroy(((IndexTupleForm)GETSTRUCT(tuple))->indexrelid); + } + + heap_endscan(scan); + heap_close(indexRelation); +} + +/* -------------------------------- + * DeletePgRelationTuple + * + * -------------------------------- + */ +void +DeletePgRelationTuple(Relation rdesc) +{ + Relation pg_class_desc; + HeapScanDesc pg_class_scan; + ScanKeyData key; + HeapTuple tup; + + /* ---------------- + * open pg_class + * ---------------- + */ + pg_class_desc = heap_openr(RelationRelationName); + + /* ---------------- + * create a scan key to locate the relation oid of the + * relation to delete + * ---------------- + */ + ScanKeyEntryInitialize(&key, 0, ObjectIdAttributeNumber, + F_INT4EQ, rdesc->rd_att->attrs[0]->attrelid); + + pg_class_scan = heap_beginscan(pg_class_desc, + 0, + NowTimeQual, + 1, + &key); + + /* ---------------- + * use heap_getnext() to fetch the pg_class tuple. If this + * tuple is not valid then something's wrong. + * ---------------- + */ + tup = heap_getnext(pg_class_scan, 0, (Buffer *) NULL); + + if (! PointerIsValid(tup)) { + heap_endscan(pg_class_scan); + heap_close(pg_class_desc); + elog(WARN, "DeletePgRelationTuple: %s relation nonexistent", + &rdesc->rd_rel->relname); + } + + /* ---------------- + * delete the relation tuple from pg_class, and finish up. + * ---------------- + */ + heap_endscan(pg_class_scan); + heap_delete(pg_class_desc, &tup->t_ctid); + + heap_close(pg_class_desc); +} + +/* -------------------------------- + * DeletePgAttributeTuples + * + * -------------------------------- + */ +void +DeletePgAttributeTuples(Relation rdesc) +{ + Relation pg_attribute_desc; + HeapScanDesc pg_attribute_scan; + ScanKeyData key; + HeapTuple tup; + + /* ---------------- + * open pg_attribute + * ---------------- + */ + pg_attribute_desc = heap_openr(AttributeRelationName); + + /* ---------------- + * create a scan key to locate the attribute tuples to delete + * and begin the scan. + * ---------------- + */ + ScanKeyEntryInitialize(&key, 0, Anum_pg_attribute_attrelid, + F_INT4EQ, rdesc->rd_att->attrs[0]->attrelid); + + /* ----------------- + * Get a write lock _before_ getting the read lock in the scan + * ---------------- + */ + RelationSetLockForWrite(pg_attribute_desc); + + pg_attribute_scan = heap_beginscan(pg_attribute_desc, + 0, + NowTimeQual, + 1, + &key); + + /* ---------------- + * use heap_getnext() / amdelete() until all attribute tuples + * have been deleted. + * ---------------- + */ + while (tup = heap_getnext(pg_attribute_scan, 0, (Buffer *)NULL), + PointerIsValid(tup)) { + + heap_delete(pg_attribute_desc, &tup->t_ctid); + } + + /* ---------------- + * finish up. + * ---------------- + */ + heap_endscan(pg_attribute_scan); + + /* ---------------- + * Release the write lock + * ---------------- + */ + RelationUnsetLockForWrite(pg_attribute_desc); + heap_close(pg_attribute_desc); +} + + +/* -------------------------------- + * DeletePgTypeTuple + * + * If the user attempts to destroy a relation and there + * exists attributes in other relations of type + * "relation we are deleting", then we have to do something + * special. presently we disallow the destroy. + * -------------------------------- + */ +void +DeletePgTypeTuple(Relation rdesc) +{ + Relation pg_type_desc; + HeapScanDesc pg_type_scan; + Relation pg_attribute_desc; + HeapScanDesc pg_attribute_scan; + ScanKeyData key; + ScanKeyData attkey; + HeapTuple tup; + HeapTuple atttup; + Oid typoid; + + /* ---------------- + * open pg_type + * ---------------- + */ + pg_type_desc = heap_openr(TypeRelationName); + + /* ---------------- + * create a scan key to locate the type tuple corresponding + * to this relation. + * ---------------- + */ + ScanKeyEntryInitialize(&key, 0, Anum_pg_type_typrelid, F_INT4EQ, + rdesc->rd_att->attrs[0]->attrelid); + + pg_type_scan = heap_beginscan(pg_type_desc, + 0, + NowTimeQual, + 1, + &key); + + /* ---------------- + * use heap_getnext() to fetch the pg_type tuple. If this + * tuple is not valid then something's wrong. + * ---------------- + */ + tup = heap_getnext(pg_type_scan, 0, (Buffer *)NULL); + + if (! PointerIsValid(tup)) { + heap_endscan(pg_type_scan); + heap_close(pg_type_desc); + elog(WARN, "DeletePgTypeTuple: %s type nonexistent", + &rdesc->rd_rel->relname); + } + + /* ---------------- + * now scan pg_attribute. if any other relations have + * attributes of the type of the relation we are deleteing + * then we have to disallow the deletion. should talk to + * stonebraker about this. -cim 6/19/90 + * ---------------- + */ + typoid = tup->t_oid; + + pg_attribute_desc = heap_openr(AttributeRelationName); + + ScanKeyEntryInitialize(&attkey, + 0, Anum_pg_attribute_atttypid, F_INT4EQ, + typoid); + + pg_attribute_scan = heap_beginscan(pg_attribute_desc, + 0, + NowTimeQual, + 1, + &attkey); + + /* ---------------- + * try and get a pg_attribute tuple. if we succeed it means + * we cant delete the relation because something depends on + * the schema. + * ---------------- + */ + atttup = heap_getnext(pg_attribute_scan, 0, (Buffer *)NULL); + + if (PointerIsValid(atttup)) { + Oid relid = ((AttributeTupleForm) GETSTRUCT(atttup))->attrelid; + + heap_endscan(pg_type_scan); + heap_close(pg_type_desc); + heap_endscan(pg_attribute_scan); + heap_close(pg_attribute_desc); + + elog(WARN, "DeletePgTypeTuple: att of type %s exists in relation %d", + &rdesc->rd_rel->relname, relid); + } + heap_endscan(pg_attribute_scan); + heap_close(pg_attribute_desc); + + /* ---------------- + * Ok, it's safe so we delete the relation tuple + * from pg_type and finish up. But first end the scan so that + * we release the read lock on pg_type. -mer 13 Aug 1991 + * ---------------- + */ + heap_endscan(pg_type_scan); + heap_delete(pg_type_desc, &tup->t_ctid); + + heap_close(pg_type_desc); +} + +/* -------------------------------- + * heap_destroy + * + * -------------------------------- + */ +void +heap_destroy(char *relname) +{ + Relation rdesc; + + /* ---------------- + * first open the relation. if the relation does exist, + * heap_openr() returns NULL. + * ---------------- + */ + rdesc = heap_openr(relname); + if (rdesc == NULL) + elog(WARN,"Relation %s Does Not Exist!", relname); + + /* ---------------- + * prevent deletion of system relations + * ---------------- + */ + if (IsSystemRelationName(RelationGetRelationName(rdesc)->data)) + elog(WARN, "amdestroy: cannot destroy %s relation", + &rdesc->rd_rel->relname); + + /* ---------------- + * remove inheritance information + * ---------------- + */ + RelationRemoveInheritance(rdesc); + + /* ---------------- + * remove indexes if necessary + * ---------------- + */ + if (rdesc->rd_rel->relhasindex) { + RelationRemoveIndexes(rdesc); + } + + /* ---------------- + * remove rules if necessary + * ---------------- + */ + if (rdesc->rd_rules != NULL) { + RelationRemoveRules(rdesc->rd_id); + } + + /* ---------------- + * delete attribute tuples + * ---------------- + */ + DeletePgAttributeTuples(rdesc); + + /* ---------------- + * delete type tuple. here we want to see the effects + * of the deletions we just did, so we use setheapoverride(). + * ---------------- + */ + setheapoverride(true); + DeletePgTypeTuple(rdesc); + setheapoverride(false); + + /* ---------------- + * delete relation tuple + * ---------------- + */ + DeletePgRelationTuple(rdesc); + + /* ---------------- + * flush the relation from the relcache + * ---------------- + */ + RelationIdInvalidateRelationCacheByRelationId(rdesc->rd_id); + + /* ---------------- + * unlink the relation and finish up. + * ---------------- + */ + (void) smgrunlink(rdesc->rd_rel->relsmgr, rdesc); + heap_close(rdesc); +} + +/* + * heap_destroyr + * destroy and close temporary relations + * + */ + +void +heap_destroyr(Relation rdesc) +{ + ReleaseTmpRelBuffers(rdesc); + (void) smgrunlink(rdesc->rd_rel->relsmgr, rdesc); + heap_close(rdesc); + RemoveFromTempRelList(rdesc); +} + + +/************************************************************** + functions to deal with the list of temporary relations +**************************************************************/ + +/* -------------- + InitTempRellist(): + + initialize temporary relations list + the tempRelList is a list of temporary relations that + are created in the course of the transactions + they need to be destroyed properly at the end of the transactions + + MODIFIES the global variable tempRels + + >> NOTE << + + malloc is used instead of palloc because we KNOW when we are + going to free these things. Keeps us away from the memory context + hairyness + +*/ +void +InitTempRelList() +{ + if (tempRels) { + free(tempRels->rels); + free(tempRels); + }; + + tempRels = (TempRelList*)malloc(sizeof(TempRelList)); + tempRels->size = TEMP_REL_LIST_SIZE; + tempRels->rels = (Relation*)malloc(sizeof(Relation) * tempRels->size); + memset(tempRels->rels, sizeof(Relation) * tempRels->size , 0); + tempRels->num = 0; +} + +/* + removes a relation from the TempRelList + + MODIFIES the global variable tempRels + we don't really remove it, just mark it as NULL + and DestroyTempRels will look for NULLs +*/ +void +RemoveFromTempRelList(Relation r) +{ + int i; + + if (!tempRels) + return; + + for (i=0; i<tempRels->num; i++) { + if (tempRels->rels[i] == r) { + tempRels->rels[i] = NULL; + break; + } + } +} + +/* + add a temporary relation to the TempRelList + + MODIFIES the global variable tempRels +*/ +void +AddToTempRelList(Relation r) +{ + if (!tempRels) + return; + + if (tempRels->num == tempRels->size) { + tempRels->size += TEMP_REL_LIST_SIZE; + tempRels->rels = realloc(tempRels->rels, tempRels->size); + } + tempRels->rels[tempRels->num] = r; + tempRels->num++; +} + +/* + go through the tempRels list and destroy each of the relations +*/ +void +DestroyTempRels() +{ + int i; + Relation rdesc; + + if (!tempRels) + return; + + for (i=0;i<tempRels->num;i++) { + rdesc = tempRels->rels[i]; + /* rdesc may be NULL if it has been removed from the list already */ + if (rdesc) + heap_destroyr(rdesc); + } + free(tempRels->rels); + free(tempRels); + tempRels = NULL; +} + diff --git a/src/backend/catalog/heap.h b/src/backend/catalog/heap.h new file mode 100644 index 00000000000..edcd5bf5ed8 --- /dev/null +++ b/src/backend/catalog/heap.h @@ -0,0 +1,42 @@ +/*------------------------------------------------------------------------- + * + * heap.h-- + * prototypes for functions in lib/catalog/heap.c + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: heap.h,v 1.1.1.1 1996/07/09 06:21:15 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef HEAP_H +#define HEAP_H + +extern Relation heap_creatr(char *relname, unsigned smgr, TupleDesc att); + +extern int RelationAlreadyExists(Relation pg_class_desc, char relname[]); +extern void addNewRelationType(char *typeName, Oid new_rel_oid); + +extern void AddPgRelationTuple(Relation pg_class_desc, + Relation new_rel_desc, Oid new_rel_oid, int arch, unsigned natts); + +extern Oid heap_create(char relname[], + char *typename, + int arch, + unsigned smgr, TupleDesc tupdesc); + +extern void RelationRemoveInheritance(Relation relation); +extern void RelationRemoveIndexes(Relation relation); +extern void DeletePgRelationTuple(Relation rdesc); +extern void DeletePgAttributeTuples(Relation rdesc); +extern void DeletePgTypeTuple(Relation rdesc); +extern void heap_destroy(char relname[]); +extern void heap_destroyr(Relation r); + +extern void InitTempRelList(); +extern void AddToTempRelList(Relation r); +extern void RemoveFromTempRelList(Relation r); +extern void DestroyTempRels(); + +#endif /* HEAP_H */ diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c new file mode 100644 index 00000000000..b04010bf95b --- /dev/null +++ b/src/backend/catalog/index.c @@ -0,0 +1,1655 @@ +/*------------------------------------------------------------------------- + * + * index.c-- + * code to create and destroy POSTGRES index relations + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.1.1.1 1996/07/09 06:21:15 scrappy Exp $ + * + * + * INTERFACE ROUTINES + * index_create() - Create a cataloged index relation + * index_destroy() - Removes index relation from catalogs + * + * NOTES + * Much of this code uses hardcoded sequential heap relation scans + * to fetch information from the catalogs. These should all be + * rewritten to use the system caches lookup routines like + * SearchSysCacheTuple, which can do efficient lookup and + * caching. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/attnum.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "access/itup.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "utils/builtins.h" +#include "utils/tqual.h" +#include "access/tupdesc.h" +#include "access/funcindex.h" +#include "access/xact.h" + +#include "storage/smgr.h" +#include "miscadmin.h" +#include "utils/mcxt.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "utils/relcache.h" +#include "utils/elog.h" + +#include "bootstrap/bootstrap.h" + +#include "catalog/catname.h" +#include "catalog/catalog.h" +#include "utils/syscache.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_index.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_class.h" +#include "catalog/pg_type.h" +#include "catalog/indexing.h" + +#include "catalog/heap.h" + +#include "nodes/execnodes.h" +#include "nodes/plannodes.h" + +#include "catalog/index.h" + +#include "executor/executor.h" +#include "executor/tuptable.h" + +#include "optimizer/clauses.h" +#include "optimizer/prep.h" + +#include "parser/catalog_utils.h" + +#include "machine.h" + +/* + * macros used in guessing how many tuples are on a page. + */ +#define AVG_TUPLE_SIZE 8 +#define NTUPLES_PER_PAGE(natts) (BLCKSZ/((natts)*AVG_TUPLE_SIZE)) + +/* non-export function prototypes */ +static Oid RelationNameGetObjectId(char *relationName, Relation pg_class, + bool setHasIndexAttribute); +static Oid GetHeapRelationOid(char *heapRelationName, char *indexRelationName); +static TupleDesc BuildFuncTupleDesc(FuncIndexInfo *funcInfo); +static TupleDesc ConstructTupleDescriptor(Oid heapoid, Relation heapRelation, + int numatts, AttrNumber attNums[]); + +static void ConstructIndexReldesc(Relation indexRelation, Oid amoid); +static Oid UpdateRelationRelation(Relation indexRelation); +static void InitializeAttributeOids(Relation indexRelation, + int numatts, + Oid indexoid); +static void +AppendAttributeTuples(Relation indexRelation, int numatts); +static void UpdateIndexRelation(Oid indexoid, Oid heapoid, + FuncIndexInfo *funcInfo, int natts, + AttrNumber attNums[], Oid classOids[], Node *predicate); +static void DefaultBuild(Relation heapRelation, Relation indexRelation, + int numberOfAttributes, AttrNumber attributeNumber[], + IndexStrategy indexStrategy, uint16 parameterCount, + Datum parameter[], FuncIndexInfoPtr funcInfo, PredInfo *predInfo); + +/* ---------------------------------------------------------------- + * sysatts is a structure containing attribute tuple forms + * for system attributes (numbered -1, -2, ...). This really + * should be generated or eliminated or moved elsewhere. -cim 1/19/91 + * + * typedef struct FormData_pg_attribute { + * Oid attrelid; + * NameData attname; + * Oid atttypid; + * Oid attdefrel; + * uint32 attnvals; + * Oid atttyparg; type arg for arrays/spquel/procs + * int16 attlen; + * AttrNumber attnum; + * uint16 attbound; + * bool attbyval; + * bool attcanindex; + * Oid attproc; spquel? + * } FormData_pg_attribute; + * + * The data in this table was taken from local1_template.ami + * but tmin and tmax were switched because local1 was incorrect. + * ---------------------------------------------------------------- + */ +static FormData_pg_attribute sysatts[] = { + { 0l, {"ctid"}, 27l, 0l, 0l, 0l, 6, -1, 0, '\0', '\001', 0l, 'i' }, + { 0l, {"oid"}, 26l, 0l, 0l, 0l, 4, -2, 0, '\001', '\001', 0l, 'i' }, + { 0l, {"xmin"}, 28l, 0l, 0l, 0l, 5, -3, 0, '\0', '\001', 0l, 'i' }, + { 0l, {"cmin"}, 29l, 0l, 0l, 0l, 1, -4, 0, '\001', '\001', 0l, 's' }, + { 0l, {"xmax"}, 28l, 0l, 0l, 0l, 5, -5, 0, '\0', '\001', 0l, 'i' }, + { 0l, {"cmax"}, 29l, 0l, 0l, 0l, 1, -6, 0, '\001', '\001', 0l, 's' }, + { 0l, {"chain"}, 27l, 0l, 0l, 0l, 6, -7, 0, '\0', '\001', 0l, 'i' }, + { 0l, {"anchor"}, 27l, 0l, 0l, 0l, 6, -8, 0, '\0', '\001', 0l, 'i' }, + { 0l, {"tmin"}, 20l, 0l, 0l, 0l, 4, -9, 0, '\001', '\001', 0l, 'i' }, + { 0l, {"tmax"}, 20l, 0l, 0l, 0l, 4, -10, 0, '\001', '\001', 0l, 'i' }, + { 0l, {"vtype"}, 18l, 0l, 0l, 0l, 1, -11, 0, '\001', '\001', 0l, 'c' }, +}; + +/* ---------------------------------------------------------------- + * RelationNameGetObjectId -- + * Returns the object identifier for a relation given its name. + * + * > The HASINDEX attribute for the relation with this name will + * > be set if it exists and if it is indicated by the call argument. + * What a load of bull. This setHasIndexAttribute is totally ignored. + * This is yet another silly routine to scan the catalogs which should + * probably be replaced by SearchSysCacheTuple. -cim 1/19/91 + * + * Note: + * Assumes relation name is valid. + * Assumes relation descriptor is valid. + * ---------------------------------------------------------------- + */ +static Oid +RelationNameGetObjectId(char *relationName, + Relation pg_class, + bool setHasIndexAttribute) +{ + HeapScanDesc pg_class_scan; + HeapTuple pg_class_tuple; + Oid relationObjectId; + Buffer buffer; + ScanKeyData key; + + /* + * If this isn't bootstrap time, we can use the system catalogs to + * speed this up. + */ + + if (!IsBootstrapProcessingMode()) { + pg_class_tuple = ClassNameIndexScan(pg_class, relationName); + if (HeapTupleIsValid(pg_class_tuple)) { + relationObjectId = pg_class_tuple->t_oid; + pfree(pg_class_tuple); + } else + relationObjectId = InvalidOid; + + return (relationObjectId); + } + + /* ---------------- + * Bootstrap time, do this the hard way. + * begin a scan of pg_class for the named relation + * ---------------- + */ + ScanKeyEntryInitialize(&key, 0, Anum_pg_class_relname, + NameEqualRegProcedure, + PointerGetDatum(relationName)); + + pg_class_scan = heap_beginscan(pg_class, 0, NowTimeQual, 1, &key); + + /* ---------------- + * if we find the named relation, fetch its relation id + * (the oid of the tuple we found). + * ---------------- + */ + pg_class_tuple = heap_getnext(pg_class_scan, 0, &buffer); + + if (! HeapTupleIsValid(pg_class_tuple)) { + relationObjectId = InvalidOid; + } else { + relationObjectId = pg_class_tuple->t_oid; + ReleaseBuffer(buffer); + } + + /* ---------------- + * cleanup and return results + * ---------------- + */ + heap_endscan(pg_class_scan); + + return + relationObjectId; +} + + +/* ---------------------------------------------------------------- + * GetHeapRelationOid + * ---------------------------------------------------------------- + */ +static Oid +GetHeapRelationOid(char *heapRelationName, char *indexRelationName) +{ + Relation pg_class; + Oid indoid; + Oid heapoid; + + /* ---------------- + * XXX ADD INDEXING HERE + * ---------------- + */ + /* ---------------- + * open pg_class and get the oid of the relation + * corresponding to the name of the index relation. + * ---------------- + */ + pg_class = heap_openr(RelationRelationName); + + indoid = RelationNameGetObjectId(indexRelationName, + pg_class, + false); + + if (OidIsValid(indoid)) + elog(WARN, "Cannot create index: '%s' already exists", + indexRelationName); + + /* ---------------- + * get the object id of the heap relation + * ---------------- + */ + heapoid = RelationNameGetObjectId(heapRelationName, + pg_class, + true); + + /* ---------------- + * check that the heap relation exists.. + * ---------------- + */ + if (! OidIsValid(heapoid)) + elog(WARN, "Cannot create index on '%s': relation does not exist", + heapRelationName); + + /* ---------------- + * close pg_class and return the heap relation oid + * ---------------- + */ + heap_close(pg_class); + + return heapoid; +} + +static TupleDesc +BuildFuncTupleDesc(FuncIndexInfo *funcInfo) +{ + HeapTuple tuple; + TupleDesc funcTupDesc; + Oid retType; + char *funcname; + int4 nargs; + Oid *argtypes; + + /* + * Allocate and zero a tuple descriptor. + */ + funcTupDesc = CreateTemplateTupleDesc(1); + funcTupDesc->attrs[0] = (AttributeTupleForm) palloc(ATTRIBUTE_TUPLE_SIZE); + memset(funcTupDesc->attrs[0], 0, ATTRIBUTE_TUPLE_SIZE); + + /* + * Lookup the function for the return type. + */ + funcname = FIgetname(funcInfo); + nargs = FIgetnArgs(funcInfo); + argtypes = FIgetArglist(funcInfo); + tuple = SearchSysCacheTuple(PRONAME, + PointerGetDatum(funcname), + Int32GetDatum(nargs), + PointerGetDatum(argtypes), + 0); + + if (!HeapTupleIsValid(tuple)) + func_error("BuildFuncTupleDesc", funcname, nargs, (int*)argtypes); + + retType = ((Form_pg_proc)GETSTRUCT(tuple))->prorettype; + + /* + * Look up the return type in pg_type for the type length. + */ + tuple = SearchSysCacheTuple(TYPOID, + ObjectIdGetDatum(retType), + 0,0,0); + if (!HeapTupleIsValid(tuple)) + elog(WARN,"Function %s return type does not exist",FIgetname(funcInfo)); + + /* + * Assign some of the attributes values. Leave the rest as 0. + */ + funcTupDesc->attrs[0]->attlen = ((TypeTupleForm)GETSTRUCT(tuple))->typlen; + funcTupDesc->attrs[0]->atttypid = retType; + funcTupDesc->attrs[0]->attnum = 1; + funcTupDesc->attrs[0]->attbyval = ((TypeTupleForm)GETSTRUCT(tuple))->typbyval; + funcTupDesc->attrs[0]->attcanindex = 0; + + /* + * make the attributes name the same as the functions + */ + namestrcpy(&funcTupDesc->attrs[0]->attname, funcname); + + return (funcTupDesc); +} + +/* ---------------------------------------------------------------- + * ConstructTupleDescriptor + * ---------------------------------------------------------------- + */ +static TupleDesc +ConstructTupleDescriptor(Oid heapoid, + Relation heapRelation, + int numatts, + AttrNumber attNums[]) +{ + TupleDesc heapTupDesc; + TupleDesc indexTupDesc; + AttrNumber atnum; /* attributeNumber[attributeOffset] */ + AttrNumber atind; + int natts; /* RelationTupleForm->relnatts */ + char *from; /* used to simplify memcpy below */ + char *to; /* used to simplify memcpy below */ + int i; + + /* ---------------- + * allocate the new tuple descriptor + * ---------------- + */ + natts = RelationGetRelationTupleForm(heapRelation)->relnatts; + + indexTupDesc = CreateTemplateTupleDesc(numatts); + + /* ---------------- + * + * ---------------- + */ + + /* ---------------- + * for each attribute we are indexing, obtain its attribute + * tuple form from either the static table of system attribute + * tuple forms or the relation tuple descriptor + * ---------------- + */ + for (i = 0; i < numatts; i += 1) { + + /* ---------------- + * get the attribute number and make sure it's valid + * ---------------- + */ + atnum = attNums[i]; + if (atnum > natts) + elog(WARN, "Cannot create index: attribute %d does not exist", + atnum); + + indexTupDesc->attrs[i] = (AttributeTupleForm) palloc(ATTRIBUTE_TUPLE_SIZE); + + /* ---------------- + * determine which tuple descriptor to copy + * ---------------- + */ + if (!AttrNumberIsForUserDefinedAttr(atnum)) { + + /* ---------------- + * here we are indexing on a system attribute (-1...-12) + * so we convert atnum into a usable index 0...11 so we can + * use it to dereference the array sysatts[] which stores + * tuple descriptor information for system attributes. + * ---------------- + */ + if (atnum <= FirstLowInvalidHeapAttributeNumber || atnum >= 0 ) + elog(WARN, "Cannot create index on system attribute: attribute number out of range (%d)", atnum); + atind = (-atnum) - 1; + + from = (char *) (& sysatts[atind]); + + } else { + /* ---------------- + * here we are indexing on a normal attribute (1...n) + * ---------------- + */ + + heapTupDesc = RelationGetTupleDescriptor(heapRelation); + atind = AttrNumberGetAttrOffset(atnum); + + from = (char *) (heapTupDesc->attrs[ atind ]); + } + + /* ---------------- + * now that we've determined the "from", let's copy + * the tuple desc data... + * ---------------- + */ + + to = (char *) (indexTupDesc->attrs[ i ]); + memcpy(to, from, ATTRIBUTE_TUPLE_SIZE); + + /* ---------------- + * now we have to drop in the proper relation descriptor + * into the copied tuple form's attrelid and we should be + * all set. + * ---------------- + */ + ((AttributeTupleForm) to)->attrelid = heapoid; + } + + return indexTupDesc; +} + +/* ---------------------------------------------------------------- + * AccessMethodObjectIdGetAccessMethodTupleForm -- + * Returns the formated access method tuple given its object identifier. + * + * XXX ADD INDEXING + * + * Note: + * Assumes object identifier is valid. + * ---------------------------------------------------------------- + */ +Form_pg_am +AccessMethodObjectIdGetAccessMethodTupleForm(Oid accessMethodObjectId) +{ + Relation pg_am_desc; + HeapScanDesc pg_am_scan; + HeapTuple pg_am_tuple; + ScanKeyData key; + Form_pg_am form; + + /* ---------------- + * form a scan key for the pg_am relation + * ---------------- + */ + ScanKeyEntryInitialize(&key, 0, ObjectIdAttributeNumber, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(accessMethodObjectId)); + + /* ---------------- + * fetch the desired access method tuple + * ---------------- + */ + pg_am_desc = heap_openr(AccessMethodRelationName); + pg_am_scan = heap_beginscan(pg_am_desc, 0, NowTimeQual, 1, &key); + + pg_am_tuple = heap_getnext(pg_am_scan, 0, (Buffer *)NULL); + + /* ---------------- + * return NULL if not found + * ---------------- + */ + if (! HeapTupleIsValid(pg_am_tuple)) { + heap_endscan(pg_am_scan); + heap_close(pg_am_desc); + return (NULL); + } + + /* ---------------- + * if found am tuple, then copy the form and return the copy + * ---------------- + */ + form = (Form_pg_am)palloc(sizeof *form); + memcpy(form, GETSTRUCT(pg_am_tuple), sizeof *form); + + heap_endscan(pg_am_scan); + heap_close(pg_am_desc); + + return (form); +} + +/* ---------------------------------------------------------------- + * ConstructIndexReldesc + * ---------------------------------------------------------------- + */ +static void +ConstructIndexReldesc(Relation indexRelation, Oid amoid) +{ + extern GlobalMemory CacheCxt; + MemoryContext oldcxt; + + /* ---------------- + * here we make certain to allocate the access method + * tuple within the cache context lest it vanish when the + * context changes + * ---------------- + */ + if (!CacheCxt) + CacheCxt = CreateGlobalMemory("Cache"); + + oldcxt = MemoryContextSwitchTo((MemoryContext)CacheCxt); + + indexRelation->rd_am = + AccessMethodObjectIdGetAccessMethodTupleForm(amoid); + + MemoryContextSwitchTo(oldcxt); + + /* ---------------- + * XXX missing the initialization of some other fields + * ---------------- + */ + + indexRelation->rd_rel->relowner = GetUserId(); + + indexRelation->rd_rel->relam = amoid; + indexRelation->rd_rel->reltuples = 1; /* XXX */ + indexRelation->rd_rel->relexpires = 0; /* XXX */ + indexRelation->rd_rel->relpreserved = 0; /* XXX */ + indexRelation->rd_rel->relkind = RELKIND_INDEX; + indexRelation->rd_rel->relarch = 'n'; /* XXX */ +} + +/* ---------------------------------------------------------------- + * UpdateRelationRelation + * ---------------------------------------------------------------- + */ +static Oid +UpdateRelationRelation(Relation indexRelation) +{ + Relation pg_class; + HeapTuple tuple; + Oid tupleOid; + Relation idescs[Num_pg_class_indices]; + + pg_class = heap_openr(RelationRelationName); + + /* XXX Natts_pg_class_fixed is a hack - see pg_class.h */ + tuple = heap_addheader(Natts_pg_class_fixed, + sizeof(*indexRelation->rd_rel), + (char *) indexRelation->rd_rel); + + /* ---------------- + * the new tuple must have the same oid as the relcache entry for the + * index. sure would be embarassing to do this sort of thing in polite + * company. + * ---------------- + */ + tuple->t_oid = indexRelation->rd_id; + heap_insert(pg_class, tuple); + + /* + * During normal processing, we need to make sure that the system + * catalog indices are correct. Bootstrap (initdb) time doesn't + * require this, because we make sure that the indices are correct + * just before exiting. + */ + + if (!IsBootstrapProcessingMode()) { + CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_class_indices, pg_class, tuple); + CatalogCloseIndices(Num_pg_class_indices, idescs); + } + + tupleOid = tuple->t_oid; + pfree(tuple); + heap_close(pg_class); + + return(tupleOid); +} + +/* ---------------------------------------------------------------- + * InitializeAttributeOids + * ---------------------------------------------------------------- + */ +static void +InitializeAttributeOids(Relation indexRelation, + int numatts, + Oid indexoid) +{ + TupleDesc tupleDescriptor; + int i; + + tupleDescriptor = RelationGetTupleDescriptor(indexRelation); + + for (i = 0; i < numatts; i += 1) + tupleDescriptor->attrs[i]->attrelid = indexoid; +} + +/* ---------------------------------------------------------------- + * AppendAttributeTuples + * + * XXX For now, only change the ATTNUM attribute value + * ---------------------------------------------------------------- + */ +static void +AppendAttributeTuples(Relation indexRelation, int numatts) +{ + Relation pg_attribute; + HeapTuple tuple; + HeapTuple newtuple; + bool hasind; + Relation idescs[Num_pg_attr_indices]; + + Datum value[ Natts_pg_attribute ]; + char nullv[ Natts_pg_attribute ]; + char replace[ Natts_pg_attribute ]; + + TupleDesc indexTupDesc; + int i; + + /* ---------------- + * open the attribute relation + * XXX ADD INDEXING + * ---------------- + */ + pg_attribute = heap_openr(AttributeRelationName); + + /* ---------------- + * initialize null[], replace[] and value[] + * ---------------- + */ + (void) memset(nullv, ' ', Natts_pg_attribute); + (void) memset(replace, ' ', Natts_pg_attribute); + + /* ---------------- + * create the first attribute tuple. + * XXX For now, only change the ATTNUM attribute value + * ---------------- + */ + replace[ Anum_pg_attribute_attnum - 1 ] = 'r'; + + value[ Anum_pg_attribute_attnum - 1 ] = Int16GetDatum(1); + + tuple = heap_addheader(Natts_pg_attribute, + sizeof *(indexRelation->rd_att->attrs[0]), + (char *)(indexRelation->rd_att->attrs[0])); + + hasind = false; + if (!IsBootstrapProcessingMode() && pg_attribute->rd_rel->relhasindex) { + hasind = true; + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); + } + + /* ---------------- + * insert the first attribute tuple. + * ---------------- + */ + tuple = heap_modifytuple(tuple, + InvalidBuffer, + pg_attribute, + value, + nullv, + replace); + + heap_insert(pg_attribute, tuple); + if (hasind) + CatalogIndexInsert(idescs, Num_pg_attr_indices, pg_attribute, tuple); + + /* ---------------- + * now we use the information in the index tuple + * descriptor to form the remaining attribute tuples. + * ---------------- + */ + indexTupDesc = RelationGetTupleDescriptor(indexRelation); + + for (i = 1; i < numatts; i += 1) { + /* ---------------- + * process the remaining attributes... + * ---------------- + */ + memmove(GETSTRUCT(tuple), + (char *)indexTupDesc->attrs[i], + sizeof (AttributeTupleForm)); + + value[ Anum_pg_attribute_attnum - 1 ] = Int16GetDatum(i + 1); + + newtuple = heap_modifytuple(tuple, + InvalidBuffer, + pg_attribute, + value, + nullv, + replace); + + heap_insert(pg_attribute, newtuple); + if (hasind) + CatalogIndexInsert(idescs, Num_pg_attr_indices, pg_attribute, newtuple); + + /* ---------------- + * ModifyHeapTuple returns a new copy of a tuple + * so we free the original and use the copy.. + * ---------------- + */ + pfree(tuple); + tuple = newtuple; + } + + /* ---------------- + * close the attribute relation and free the tuple + * ---------------- + */ + heap_close(pg_attribute); + + if (hasind) + CatalogCloseIndices(Num_pg_attr_indices, idescs); + + pfree(tuple); +} + +/* ---------------------------------------------------------------- + * UpdateIndexRelation + * ---------------------------------------------------------------- + */ +static void +UpdateIndexRelation(Oid indexoid, + Oid heapoid, + FuncIndexInfo *funcInfo, + int natts, + AttrNumber attNums[], + Oid classOids[], + Node *predicate) +{ + IndexTupleForm indexForm; + char *predString; + text *predText; + int predLen, itupLen; + Relation pg_index; + HeapTuple tuple; + int i; + + /* ---------------- + * allocate an IndexTupleForm big enough to hold the + * index-predicate (if any) in string form + * ---------------- + */ + if (predicate != NULL) { + predString = nodeToString(predicate); + predText = (text *)fmgr(F_TEXTIN, predString); + pfree(predString); + } else { + predText = (text *)fmgr(F_TEXTIN, ""); + } + predLen = VARSIZE(predText); + itupLen = predLen + sizeof(FormData_pg_index); + indexForm = (IndexTupleForm) palloc(itupLen); + + memmove((char *)& indexForm->indpred, (char *)predText, predLen); + + /* ---------------- + * store the oid information into the index tuple form + * ---------------- + */ + indexForm->indrelid = heapoid; + indexForm->indexrelid = indexoid; + indexForm->indproc = (PointerIsValid(funcInfo)) ? + FIgetProcOid(funcInfo) : InvalidOid; + + memset((char *)& indexForm->indkey[0], 0, sizeof indexForm->indkey); + memset((char *)& indexForm->indclass[0], 0, sizeof indexForm->indclass); + + /* ---------------- + * copy index key and op class information + * ---------------- + */ + for (i = 0; i < natts; i += 1) { + indexForm->indkey[i] = attNums[i]; + indexForm->indclass[i] = classOids[i]; + } + /* + * If we have a functional index, add all attribute arguments + */ + if (PointerIsValid(funcInfo)) + { + for (i=1; i < FIgetnArgs(funcInfo); i++) + indexForm->indkey[i] = attNums[i]; + } + + indexForm->indisclustered = '\0'; /* XXX constant */ + indexForm->indisarchived = '\0'; /* XXX constant */ + + /* ---------------- + * open the system catalog index relation + * ---------------- + */ + pg_index = heap_openr(IndexRelationName); + + /* ---------------- + * form a tuple to insert into pg_index + * ---------------- + */ + tuple = heap_addheader(Natts_pg_index, + itupLen, + (char *)indexForm); + + /* ---------------- + * insert the tuple into the pg_index + * XXX ADD INDEX TUPLES TOO + * ---------------- + */ + heap_insert(pg_index, tuple); + + /* ---------------- + * close the relation and free the tuple + * ---------------- + */ + heap_close(pg_index); + pfree(predText); + pfree(indexForm); + pfree(tuple); +} + +/* ---------------------------------------------------------------- + * UpdateIndexPredicate + * ---------------------------------------------------------------- + */ +void +UpdateIndexPredicate(Oid indexoid, Node *oldPred, Node *predicate) +{ + Node *newPred; + char *predString; + text *predText; + Relation pg_index; + HeapTuple tuple; + HeapTuple newtup; + ScanKeyData entry; + HeapScanDesc scan; + Buffer buffer; + int i; + Datum values[Natts_pg_index]; + char nulls[Natts_pg_index]; + char replace[Natts_pg_index]; + + /* + * Construct newPred as a CNF expression equivalent to the OR of the + * original partial-index predicate ("oldPred") and the extension + * predicate ("predicate"). + * + * This should really try to process the result to change things like + * "a>2 OR a>1" to simply "a>1", but for now all it does is make sure + * that if the extension predicate is NULL (i.e., it is being extended + * to be a complete index), then newPred will be NULL - in effect, + * changing "a>2 OR TRUE" to "TRUE". --Nels, Jan '93 + */ + newPred = NULL; + if (predicate != NULL) { + newPred = + (Node*)make_orclause(lcons(make_andclause((List*)predicate), + lcons(make_andclause((List*)oldPred), + NIL))); + newPred = (Node*)cnfify((Expr*)newPred, true); + } + + /* translate the index-predicate to string form */ + if (newPred != NULL) { + predString = nodeToString(newPred); + predText = (text *)fmgr(F_TEXTIN, predString); + pfree(predString); + } else { + predText = (text *)fmgr(F_TEXTIN, ""); + } + + /* open the index system catalog relation */ + pg_index = heap_openr(IndexRelationName); + + ScanKeyEntryInitialize(&entry, 0x0, Anum_pg_index_indexrelid, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(indexoid)); + + scan = heap_beginscan(pg_index, 0, NowTimeQual, 1, &entry); + tuple = heap_getnext(scan, 0, &buffer); + heap_endscan(scan); + + for (i = 0; i < Natts_pg_index; i++) { + nulls[i] = heap_attisnull(tuple, i+1) ? 'n' : ' '; + replace[i] = ' '; + values[i] = (Datum) NULL; + } + + replace[Anum_pg_index_indpred - 1] = 'r'; + values[Anum_pg_index_indpred - 1] = (Datum) predText; + + newtup = heap_modifytuple(tuple, buffer, pg_index, values, nulls, replace); + + (void) heap_replace(pg_index, &(newtup->t_ctid), newtup); + + heap_close(pg_index); + pfree(predText); +} + +/* ---------------------------------------------------------------- + * InitIndexStrategy + * ---------------------------------------------------------------- + */ +void +InitIndexStrategy(int numatts, + Relation indexRelation, + Oid accessMethodObjectId) +{ + IndexStrategy strategy; + RegProcedure *support; + uint16 amstrategies; + uint16 amsupport; + Oid attrelid; + Size strsize; + extern GlobalMemory CacheCxt; + + /* ---------------- + * get information from the index relation descriptor + * ---------------- + */ + attrelid = indexRelation->rd_att->attrs[0]->attrelid; + amstrategies = indexRelation->rd_am->amstrategies; + amsupport = indexRelation->rd_am->amsupport; + + /* ---------------- + * get the size of the strategy + * ---------------- + */ + strsize = AttributeNumberGetIndexStrategySize(numatts, amstrategies); + + /* ---------------- + * allocate the new index strategy structure + * + * the index strategy has to be allocated in the same + * context as the relation descriptor cache or else + * it will be lost at the end of the transaction. + * ---------------- + */ + if (!CacheCxt) + CacheCxt = CreateGlobalMemory("Cache"); + + strategy = (IndexStrategy) + MemoryContextAlloc((MemoryContext)CacheCxt, strsize); + + if (amsupport > 0) { + strsize = numatts * (amsupport * sizeof(RegProcedure)); + support = (RegProcedure *) MemoryContextAlloc((MemoryContext)CacheCxt, + strsize); + } else { + support = (RegProcedure *) NULL; + } + + /* ---------------- + * fill in the index strategy structure with information + * from the catalogs. Note: we use heap override mode + * in order to be allowed to see the correct information in the + * catalogs, even though our transaction has not yet committed. + * ---------------- + */ + setheapoverride(1); + + IndexSupportInitialize(strategy, support, + attrelid, accessMethodObjectId, + amstrategies, amsupport, numatts); + + setheapoverride(0); + + /* ---------------- + * store the strategy information in the index reldesc + * ---------------- + */ + RelationSetIndexSupport(indexRelation, strategy, support); +} + + +/* ---------------------------------------------------------------- + * index_create + * ---------------------------------------------------------------- + */ +void +index_create(char *heapRelationName, + char *indexRelationName, + FuncIndexInfo *funcInfo, + Oid accessMethodObjectId, + int numatts, + AttrNumber attNums[], + Oid classObjectId[], + uint16 parameterCount, + Datum parameter[], + Node *predicate) +{ + Relation heapRelation; + Relation indexRelation; + TupleDesc indexTupDesc; + Oid heapoid; + Oid indexoid; + PredInfo *predInfo; + + /* ---------------- + * check parameters + * ---------------- + */ + if (numatts < 1) + elog(WARN, "must index at least one attribute"); + + /* ---------------- + * get heap relation oid and open the heap relation + * XXX ADD INDEXING + * ---------------- + */ + heapoid = GetHeapRelationOid(heapRelationName, indexRelationName); + + heapRelation = heap_open(heapoid); + + /* ---------------- + * write lock heap to guarantee exclusive access + * ---------------- + */ + + RelationSetLockForWrite(heapRelation); + + /* ---------------- + * construct new tuple descriptor + * ---------------- + */ + if (PointerIsValid(funcInfo)) + indexTupDesc = BuildFuncTupleDesc(funcInfo); + else + indexTupDesc = ConstructTupleDescriptor(heapoid, + heapRelation, + numatts, + attNums); + + /* ---------------- + * create the index relation + * ---------------- + */ + indexRelation = heap_creatr(indexRelationName, + DEFAULT_SMGR, + indexTupDesc); + + /* ---------------- + * construct the index relation descriptor + * + * XXX should have a proper way to create cataloged relations + * ---------------- + */ + ConstructIndexReldesc(indexRelation, accessMethodObjectId); + + /* ---------------- + * add index to catalogs + * (append RELATION tuple) + * ---------------- + */ + indexoid = UpdateRelationRelation(indexRelation); + + /* ---------------- + * Now get the index procedure (only relevant for functional indices). + * ---------------- + */ + + if (PointerIsValid(funcInfo)) + { + HeapTuple proc_tup; + + proc_tup = SearchSysCacheTuple(PRONAME, + PointerGetDatum(FIgetname(funcInfo)), + Int32GetDatum(FIgetnArgs(funcInfo)), + PointerGetDatum(FIgetArglist(funcInfo)), + 0); + + if (!HeapTupleIsValid(proc_tup)) { + func_error("index_create", FIgetname(funcInfo), + FIgetnArgs(funcInfo), + (int*) FIgetArglist(funcInfo)); + } + FIgetProcOid(funcInfo) = proc_tup->t_oid; + } + + /* ---------------- + * now update the object id's of all the attribute + * tuple forms in the index relation's tuple descriptor + * ---------------- + */ + InitializeAttributeOids(indexRelation, numatts, indexoid); + + /* ---------------- + * append ATTRIBUTE tuples + * ---------------- + */ + AppendAttributeTuples(indexRelation, numatts); + + /* ---------------- + * update pg_index + * (append INDEX tuple) + * + * Note that this stows away a representation of "predicate". + * (Or, could define a rule to maintain the predicate) --Nels, Feb '92 + * ---------------- + */ + UpdateIndexRelation(indexoid, heapoid, funcInfo, + numatts, attNums, classObjectId, predicate); + + predInfo = (PredInfo*)palloc(sizeof(PredInfo)); + predInfo->pred = predicate; + predInfo->oldPred = NULL; + + /* ---------------- + * initialize the index strategy + * ---------------- + */ + InitIndexStrategy(numatts, indexRelation, accessMethodObjectId); + + /* + * If this is bootstrap (initdb) time, then we don't actually + * fill in the index yet. We'll be creating more indices and classes + * later, so we delay filling them in until just before we're done + * with bootstrapping. Otherwise, we call the routine that constructs + * the index. The heap and index relations are closed by index_build(). + */ + if (IsBootstrapProcessingMode()) { + index_register(heapRelationName, indexRelationName, numatts, attNums, + parameterCount, parameter, funcInfo, predInfo); + } else { + heapRelation = heap_openr(heapRelationName); + index_build(heapRelation, indexRelation, numatts, attNums, + parameterCount, parameter, funcInfo, predInfo); + } +} + +/* ---------------------------------------------------------------- + * index_destroy + * + * XXX break into modules like index_create + * ---------------------------------------------------------------- + */ +void +index_destroy(Oid indexId) +{ + Relation indexRelation; + Relation catalogRelation; + HeapTuple tuple; + HeapScanDesc scan; + ScanKeyData entry; + + Assert(OidIsValid(indexId)); + + indexRelation = index_open(indexId); + + /* ---------------- + * fix RELATION relation + * ---------------- + */ + catalogRelation = heap_openr(RelationRelationName); + + ScanKeyEntryInitialize(&entry, 0x0, ObjectIdAttributeNumber, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(indexId));; + + scan = heap_beginscan(catalogRelation, 0, NowTimeQual, 1, &entry); + tuple = heap_getnext(scan, 0, (Buffer *)NULL); + + AssertState(HeapTupleIsValid(tuple)); + + heap_delete(catalogRelation, &tuple->t_ctid); + heap_endscan(scan); + heap_close(catalogRelation); + + /* ---------------- + * fix ATTRIBUTE relation + * ---------------- + */ + catalogRelation = heap_openr(AttributeRelationName); + + entry.sk_attno = Anum_pg_attribute_attrelid; + + scan = heap_beginscan(catalogRelation, 0, NowTimeQual, 1, &entry); + + while (tuple = heap_getnext(scan, 0, (Buffer *)NULL), + HeapTupleIsValid(tuple)) { + + heap_delete(catalogRelation, &tuple->t_ctid); + } + heap_endscan(scan); + heap_close(catalogRelation); + + /* ---------------- + * fix INDEX relation + * ---------------- + */ + catalogRelation = heap_openr(IndexRelationName); + + entry.sk_attno = Anum_pg_index_indexrelid; + + scan = heap_beginscan(catalogRelation, 0, NowTimeQual, 1, &entry); + tuple = heap_getnext(scan, 0, (Buffer *)NULL); + if (! HeapTupleIsValid(tuple)) { + elog(NOTICE, "IndexRelationDestroy: %s's INDEX tuple missing", + RelationGetRelationName(indexRelation)); + } + heap_delete(catalogRelation, &tuple->t_ctid); + heap_endscan(scan); + heap_close(catalogRelation); + + /* + * physically remove the file + */ + if (FileNameUnlink(relpath(indexRelation->rd_rel->relname.data)) < 0) + elog(WARN, "amdestroyr: unlink: %m"); + + index_close(indexRelation); +} + +/* ---------------------------------------------------------------- + * index_build support + * ---------------------------------------------------------------- + */ +/* ---------------- + * FormIndexDatum + * ---------------- + */ +void +FormIndexDatum(int numberOfAttributes, + AttrNumber attributeNumber[], + HeapTuple heapTuple, + TupleDesc heapDescriptor, + Buffer buffer, + Datum *datum, + char *nullv, + FuncIndexInfoPtr fInfo) +{ + AttrNumber i; + int offset; + bool isNull; + + /* ---------------- + * for each attribute we need from the heap tuple, + * get the attribute and stick it into the datum and + * null arrays. + * ---------------- + */ + + for (i = 1; i <= numberOfAttributes; i += 1) { + offset = AttrNumberGetAttrOffset(i); + + datum[ offset ] = + PointerGetDatum( GetIndexValue(heapTuple, + heapDescriptor, + offset, + attributeNumber, + fInfo, + &isNull, + buffer) ); + + nullv[ offset ] = (isNull) ? 'n' : ' '; + } +} + + +/* ---------------- + * UpdateStats + * ---------------- + */ +void +UpdateStats(Oid relid, long reltuples, bool hasindex) +{ + Relation whichRel; + Relation pg_class; + HeapScanDesc pg_class_scan; + HeapTuple htup; + HeapTuple newtup; + long relpages; + Buffer buffer; + int i; + Form_pg_class rd_rel; + Relation idescs[Num_pg_class_indices]; + + static ScanKeyData key[1] = { + { 0, ObjectIdAttributeNumber, ObjectIdEqualRegProcedure } + }; + Datum values[Natts_pg_class]; + char nulls[Natts_pg_class]; + char replace[Natts_pg_class]; + + fmgr_info(ObjectIdEqualRegProcedure, (func_ptr *) &key[0].sk_func, + &key[0].sk_nargs); + + /* ---------------- + * This routine handles updates for both the heap and index relation + * statistics. In order to guarantee that we're able to *see* the index + * relation tuple, we bump the command counter id here. The index + * relation tuple was created in the current transaction. + * ---------------- + */ + CommandCounterIncrement(); + + /* ---------------- + * CommandCounterIncrement() flushes invalid cache entries, including + * those for the heap and index relations for which we're updating + * statistics. Now that the cache is flushed, it's safe to open the + * relation again. We need the relation open in order to figure out + * how many blocks it contains. + * ---------------- + */ + + whichRel = RelationIdGetRelation(relid); + + if (!RelationIsValid(whichRel)) + elog(WARN, "UpdateStats: cannot open relation id %d", relid); + + /* ---------------- + * Find the RELATION relation tuple for the given relation. + * ---------------- + */ + pg_class = heap_openr(RelationRelationName); + if (! RelationIsValid(pg_class)) { + elog(WARN, "UpdateStats: could not open RELATION relation"); + } + key[0].sk_argument = ObjectIdGetDatum(relid); + + pg_class_scan = + heap_beginscan(pg_class, 0, NowTimeQual, 1, key); + + if (! HeapScanIsValid(pg_class_scan)) { + heap_close(pg_class); + elog(WARN, "UpdateStats: cannot scan RELATION relation"); + } + + /* if the heap_open above succeeded, then so will this heap_getnext() */ + htup = heap_getnext(pg_class_scan, 0, &buffer); + heap_endscan(pg_class_scan); + + /* ---------------- + * update statistics + * ---------------- + */ + relpages = RelationGetNumberOfBlocks(whichRel); + + /* + * We shouldn't have to do this, but we do... Modify the reldesc + * in place with the new values so that the cache contains the + * latest copy. + */ + + whichRel->rd_rel->relhasindex = hasindex; + whichRel->rd_rel->relpages = relpages; + whichRel->rd_rel->reltuples = reltuples; + + for (i = 0; i < Natts_pg_class; i++) { + nulls[i] = heap_attisnull(htup, i+1) ? 'n' : ' '; + replace[i] = ' '; + values[i] = (Datum) NULL; + } + + /* + * If reltuples wasn't supplied take an educated guess. + */ + if (reltuples == 0) + reltuples = relpages*NTUPLES_PER_PAGE(whichRel->rd_rel->relnatts); + + if (IsBootstrapProcessingMode()) { + + /* + * At bootstrap time, we don't need to worry about concurrency + * or visibility of changes, so we cheat. + */ + + rd_rel = (Form_pg_class) GETSTRUCT(htup); + rd_rel->relpages = relpages; + rd_rel->reltuples = reltuples; + rd_rel->relhasindex = hasindex; + } else { + /* during normal processing, work harder */ + replace[Anum_pg_class_relpages - 1] = 'r'; + values[Anum_pg_class_relpages - 1] = (Datum)relpages; + replace[Anum_pg_class_reltuples - 1] = 'r'; + values[Anum_pg_class_reltuples - 1] = (Datum)reltuples; + replace[Anum_pg_class_relhasindex - 1] = 'r'; + values[Anum_pg_class_relhasindex - 1] = CharGetDatum(hasindex); + + newtup = heap_modifytuple(htup, buffer, pg_class, values, + nulls, replace); + (void) heap_replace(pg_class, &(newtup->t_ctid), newtup); + CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_class_indices, pg_class, newtup); + CatalogCloseIndices(Num_pg_class_indices, idescs); + } + + heap_close(pg_class); + heap_close(whichRel); +} + + +/* ------------------------- + * FillDummyExprContext + * Sets up dummy ExprContext and TupleTableSlot objects for use + * with ExecQual. + * ------------------------- + */ +void +FillDummyExprContext(ExprContext *econtext, + TupleTableSlot *slot, + TupleDesc tupdesc, + Buffer buffer) +{ + econtext->ecxt_scantuple = slot; + econtext->ecxt_innertuple = NULL; + econtext->ecxt_outertuple = NULL; + econtext->ecxt_param_list_info = NULL; + econtext->ecxt_range_table = NULL; + + slot->ttc_tupleDescriptor = tupdesc; + slot->ttc_buffer = buffer; + slot->ttc_shouldFree = false; + +} + + +/* ---------------- + * DefaultBuild + * ---------------- + */ +static void +DefaultBuild(Relation heapRelation, + Relation indexRelation, + int numberOfAttributes, + AttrNumber attributeNumber[], + IndexStrategy indexStrategy, /* not used */ + uint16 parameterCount, /* not used */ + Datum parameter[], /* not used */ + FuncIndexInfoPtr funcInfo, + PredInfo *predInfo) +{ + HeapScanDesc scan; + HeapTuple heapTuple; + Buffer buffer; + + IndexTuple indexTuple; + TupleDesc heapDescriptor; + TupleDesc indexDescriptor; + Datum *datum; + char *nullv; + long reltuples, indtuples; + ExprContext *econtext; + TupleTable tupleTable; + TupleTableSlot *slot; + Node *predicate; + Node *oldPred; + + InsertIndexResult insertResult; + + /* ---------------- + * more & better checking is needed + * ---------------- + */ + Assert(OidIsValid(indexRelation->rd_rel->relam)); /* XXX */ + + /* ---------------- + * get the tuple descriptors from the relations so we know + * how to form the index tuples.. + * ---------------- + */ + heapDescriptor = RelationGetTupleDescriptor(heapRelation); + indexDescriptor = RelationGetTupleDescriptor(indexRelation); + + /* ---------------- + * datum and null are arrays in which we collect the index attributes + * when forming a new index tuple. + * ---------------- + */ + datum = (Datum *) palloc(numberOfAttributes * sizeof *datum); + nullv = (char *) palloc(numberOfAttributes * sizeof *nullv); + + /* + * If this is a predicate (partial) index, we will need to evaluate the + * predicate using ExecQual, which requires the current tuple to be in a + * slot of a TupleTable. In addition, ExecQual must have an ExprContext + * referring to that slot. Here, we initialize dummy TupleTable and + * ExprContext objects for this purpose. --Nels, Feb '92 + */ + + predicate = predInfo->pred; + oldPred = predInfo->oldPred; + +#ifndef OMIT_PARTIAL_INDEX + if (predicate != NULL || oldPred != NULL) { + tupleTable = ExecCreateTupleTable(1); + slot = ExecAllocTableSlot(tupleTable); + econtext = makeNode(ExprContext); + FillDummyExprContext(econtext, slot, heapDescriptor, buffer); + } +#endif /* OMIT_PARTIAL_INDEX */ + + /* ---------------- + * Ok, begin our scan of the base relation. + * ---------------- + */ + scan = heap_beginscan(heapRelation, /* relation */ + 0, /* start at end */ + NowTimeQual, /* time range */ + 0, /* number of keys */ + (ScanKey) NULL); /* scan key */ + + reltuples = indtuples = 0; + + /* ---------------- + * for each tuple in the base relation, we create an index + * tuple and add it to the index relation. We keep a running + * count of the number of tuples so that we can update pg_class + * with correct statistics when we're done building the index. + * ---------------- + */ + while (heapTuple = heap_getnext(scan, 0, &buffer), + HeapTupleIsValid(heapTuple)) { + + reltuples++; + + /* + * If oldPred != NULL, this is an EXTEND INDEX command, so skip + * this tuple if it was already in the existing partial index + */ + if (oldPred != NULL) { +#ifndef OMIT_PARTIAL_INDEX + /*SetSlotContents(slot, heapTuple); */ + slot->val = heapTuple; + if (ExecQual((List*)oldPred, econtext) == true) { + indtuples++; + continue; + } +#endif /* OMIT_PARTIAL_INDEX */ + } + + /* Skip this tuple if it doesn't satisfy the partial-index predicate */ + if (predicate != NULL) { +#ifndef OMIT_PARTIAL_INDEX + /*SetSlotContents(slot, heapTuple); */ + slot->val = heapTuple; + if (ExecQual((List*)predicate, econtext) == false) + continue; +#endif /* OMIT_PARTIAL_INDEX */ + } + + indtuples++; + + /* ---------------- + * FormIndexDatum fills in its datum and null parameters + * with attribute information taken from the given heap tuple. + * ---------------- + */ + FormIndexDatum(numberOfAttributes, /* num attributes */ + attributeNumber, /* array of att nums to extract */ + heapTuple, /* tuple from base relation */ + heapDescriptor, /* heap tuple's descriptor */ + buffer, /* buffer used in the scan */ + datum, /* return: array of attributes */ + nullv, /* return: array of char's */ + funcInfo); + + indexTuple = index_formtuple(indexDescriptor, + datum, + nullv); + + indexTuple->t_tid = heapTuple->t_ctid; + + insertResult = index_insert(indexRelation, indexTuple); + + if (insertResult) pfree(insertResult); + pfree(indexTuple); + } + + heap_endscan(scan); + + if (predicate != NULL || oldPred != NULL) { +#ifndef OMIT_PARTIAL_INDEX + ExecDestroyTupleTable(tupleTable, false); +#endif /* OMIT_PARTIAL_INDEX */ + } + + pfree(nullv); + pfree(datum); + + /* + * Okay, now update the reltuples and relpages statistics for both + * the heap relation and the index. These statistics are used by + * the planner to choose a scan type. They are maintained generally + * by the vacuum daemon, but we update them here to make the index + * useful as soon as possible. + */ + UpdateStats(heapRelation->rd_id, reltuples, true); + UpdateStats(indexRelation->rd_id, indtuples, false); + if (oldPred != NULL) { + if (indtuples == reltuples) predicate = NULL; + UpdateIndexPredicate(indexRelation->rd_id, oldPred, predicate); + } +} + +/* ---------------- + * index_build + * ---------------- + */ +void +index_build(Relation heapRelation, + Relation indexRelation, + int numberOfAttributes, + AttrNumber attributeNumber[], + uint16 parameterCount, + Datum parameter[], + FuncIndexInfo *funcInfo, + PredInfo *predInfo) +{ + RegProcedure procedure; + + /* ---------------- + * sanity checks + * ---------------- + */ + Assert(RelationIsValid(indexRelation)); + Assert(PointerIsValid(indexRelation->rd_am)); + + procedure = indexRelation->rd_am->ambuild; + + /* ---------------- + * use the access method build procedure if supplied.. + * ---------------- + */ + if (RegProcedureIsValid(procedure)) + (void) fmgr(procedure, + heapRelation, + indexRelation, + numberOfAttributes, + attributeNumber, + RelationGetIndexStrategy(indexRelation), + parameterCount, + parameter, + funcInfo, + predInfo); + else + DefaultBuild(heapRelation, + indexRelation, + numberOfAttributes, + attributeNumber, + RelationGetIndexStrategy(indexRelation), + parameterCount, + parameter, + funcInfo, + predInfo); +} + + diff --git a/src/backend/catalog/index.h b/src/backend/catalog/index.h new file mode 100644 index 00000000000..1734f866a07 --- /dev/null +++ b/src/backend/catalog/index.h @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------- + * + * index.h-- + * prototypes for index.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: index.h,v 1.1.1.1 1996/07/09 06:21:15 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef INDEX_H +#define INDEX_H + +#include "access/funcindex.h" +#include "access/itup.h" +#include "nodes/execnodes.h" + + +extern Form_pg_am +AccessMethodObjectIdGetAccessMethodTupleForm(Oid accessMethodObjectId); + +extern void +UpdateIndexPredicate(Oid indexoid, Node *oldPred, Node *predicate); + +extern void InitIndexStrategy(int numatts, + Relation indexRelation, + Oid accessMethodObjectId); + +extern void index_create(char *heapRelationName, + char* indexRelationName, + FuncIndexInfo *funcInfo, + Oid accessMethodObjectId, + int numatts, + AttrNumber attNums[], + Oid classObjectId[], + uint16 parameterCount, + Datum parameter[], + Node *predicate); + +extern void index_destroy(Oid indexId); + +extern void FormIndexDatum(int numberOfAttributes, + AttrNumber attributeNumber[], HeapTuple heapTuple, + TupleDesc heapDescriptor, Buffer buffer, Datum *datum, + char *nullv, FuncIndexInfoPtr fInfo); + +extern void UpdateStats(Oid relid, long reltuples, bool hasindex); + +extern void FillDummyExprContext(ExprContext *econtext, TupleTableSlot *slot, + TupleDesc tupdesc, Buffer buffer); + +extern void index_build(Relation heapRelation, Relation indexRelation, + int numberOfAttributes, AttrNumber attributeNumber[], + uint16 parameterCount, Datum parameter[], FuncIndexInfo *funcInfo, + PredInfo *predInfo); + +#endif /* INDEX_H */ diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c new file mode 100644 index 00000000000..74bf48a443b --- /dev/null +++ b/src/backend/catalog/indexing.c @@ -0,0 +1,561 @@ +/*------------------------------------------------------------------------- + * + * indexing.c-- + * This file contains routines to support indices defined on system + * catalogs. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/catalog/indexing.c,v 1.1.1.1 1996/07/09 06:21:15 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" +#include "utils/builtins.h" +#include "utils/rel.h" +#include "utils/elog.h" +#include "utils/oidcompos.h" +#include "utils/palloc.h" +#include "access/htup.h" +#include "access/heapam.h" +#include "access/genam.h" +#include "access/attnum.h" +#include "access/funcindex.h" +#include "access/skey.h" +#include "storage/buf.h" +#include "storage/bufmgr.h" +#include "nodes/execnodes.h" +#include "catalog/catalog.h" +#include "catalog/catname.h" +#include "catalog/pg_index.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "catalog/pg_class.h" +#include "catalog/pg_attribute.h" +#include "utils/syscache.h" +#include "catalog/indexing.h" +#include "catalog/index.h" + +/* + * Names of indices on the following system catalogs: + * + * pg_attribute + * pg_proc + * pg_type + * pg_naming + * pg_class + */ +/* +static NameData AttributeNameIndexData = { "pg_attnameind" }; +static NameData AttributeNumIndexData = { "pg_attnumind" }; +static NameData AttributeRelidIndexData= { "pg_attrelidind" }; +static NameData ProcedureNameIndexData = { "pg_procnameind" }; +static NameData ProcedureOidIndexData = { "pg_procidind" }; +static NameData ProcedureSrcIndexData = { "pg_procsrcind" }; +static NameData TypeNameIndexData = { "pg_typenameind" }; +static NameData TypeOidIndexData = { "pg_typeidind" }; +static NameData ClassNameIndexData = { "pg_classnameind" }; +static NameData ClassOidIndexData = { "pg_classoidind" }; + +Name AttributeNameIndex = &AttributeNameIndexData; +Name AttributeNumIndex = &AttributeNumIndexData; +Name AttributeRelidIndex= &AttributeRelidIndexData; +Name ProcedureNameIndex = &ProcedureNameIndexData; +Name ProcedureOidIndex = &ProcedureOidIndexData; +Name ProcedureSrcIndex = &ProcedureSrcIndexData; +Name TypeNameIndex = &TypeNameIndexData; +Name TypeOidIndex = &TypeOidIndexData; +Name ClassNameIndex = &ClassNameIndexData; +Name ClassOidIndex = &ClassOidIndexData; +char *Name_pg_attr_indices[Num_pg_attr_indices] = {AttributeNameIndexData.data, + AttributeNumIndexData.data, + AttributeRelidIndexData.data}; +char *Name_pg_proc_indices[Num_pg_proc_indices] = {ProcedureNameIndexData.data, + ProcedureOidIndexData.data, + ProcedureSrcIndexData.data};char *Name_pg_type_indices[Num_pg_type_indices] = {TypeNameIndexData.data, + TypeOidIndexData.data}; +char *Name_pg_class_indices[Num_pg_class_indices]= {ClassNameIndexData.data, + ClassOidIndexData.data}; +*/ + +char *Name_pg_attr_indices[Num_pg_attr_indices] = {AttributeNameIndex, + AttributeNumIndex, + AttributeRelidIndex}; +char *Name_pg_proc_indices[Num_pg_proc_indices] = { ProcedureNameIndex, + ProcedureOidIndex, + ProcedureSrcIndex}; +char *Name_pg_type_indices[Num_pg_type_indices] = { TypeNameIndex, + TypeOidIndex}; +char *Name_pg_class_indices[Num_pg_class_indices]= { ClassNameIndex, + ClassOidIndex}; + + +static HeapTuple CatalogIndexFetchTuple(Relation heapRelation, + Relation idesc, + ScanKey skey); + + +/* + * Changes (appends) to catalogs can (and does) happen at various places + * throughout the code. We need a generic routine that will open all of + * the indices defined on a given catalog a return the relation descriptors + * associated with them. + */ +void +CatalogOpenIndices(int nIndices, char *names[], Relation idescs[]) +{ + int i; + + for (i=0; i<nIndices; i++) + { + idescs[i] = index_openr(names[i]); + } +} + +/* + * This is the inverse routine to CatalogOpenIndices() + */ +void +CatalogCloseIndices(int nIndices, Relation *idescs) +{ + int i; + + for (i=0; i<nIndices; i++) + index_close(idescs[i]); +} + + +/* + * For the same reasons outlined above CatalogOpenIndices() we need a routine + * that takes a new catalog tuple and inserts an associated index tuple into + * each catalog index. + */ +void +CatalogIndexInsert(Relation *idescs, + int nIndices, + Relation heapRelation, + HeapTuple heapTuple) +{ + HeapTuple pgIndexTup; + TupleDesc heapDescriptor; + IndexTupleForm pgIndexP; + IndexTuple newIndxTup; + Datum datum; + int natts; + AttrNumber *attnumP; + FuncIndexInfo finfo, *finfoP; + char nulls[INDEX_MAX_KEYS]; + int i; + + heapDescriptor = RelationGetTupleDescriptor(heapRelation); + + for (i=0; i<nIndices; i++) + { + TupleDesc indexDescriptor; + InsertIndexResult indexRes; + + indexDescriptor = RelationGetTupleDescriptor(idescs[i]); + pgIndexTup = SearchSysCacheTuple(INDEXRELID, + Int32GetDatum(idescs[i]->rd_id), + 0,0,0); + Assert(pgIndexTup); + pgIndexP = (IndexTupleForm)GETSTRUCT(pgIndexTup); + + /* + * Compute the number of attributes we are indexing upon. + * very important - can't assume one if this is a functional + * index. + */ + for (attnumP=(&pgIndexP->indkey[0]), natts=0; + *attnumP != InvalidAttrNumber; + attnumP++, natts++) + ; + + if (pgIndexP->indproc != InvalidOid) + { + FIgetnArgs(&finfo) = natts; + natts = 1; + FIgetProcOid(&finfo) = pgIndexP->indproc; + *(FIgetname(&finfo)) = '\0'; + finfoP = &finfo; + } + else + finfoP = (FuncIndexInfo *)NULL; + + FormIndexDatum(natts, + (AttrNumber *)&pgIndexP->indkey[0], + heapTuple, + heapDescriptor, + InvalidBuffer, + &datum, + nulls, + finfoP); + + newIndxTup = (IndexTuple)index_formtuple(indexDescriptor, + &datum,nulls); + Assert(newIndxTup); + /* + * Doing this structure assignment makes me quake in my boots when I + * think about portability. + */ + newIndxTup->t_tid = heapTuple->t_ctid; + + indexRes = index_insert(idescs[i], newIndxTup); + if (indexRes) pfree(indexRes); + } +} + +/* + * This is needed at initialization when reldescs for some of the crucial + * system catalogs are created and nailed into the cache. + */ +bool +CatalogHasIndex(char *catName, Oid catId) +{ + Relation pg_class; + HeapTuple htup; + Form_pg_class pgRelP; + int i; + + Assert(IsSystemRelationName(catName)); + + /* + * If we're bootstraping we don't have pg_class (or any indices). + */ + if (IsBootstrapProcessingMode()) + return false; + + if (IsInitProcessingMode()) { + for (i = 0; IndexedCatalogNames[i] != NULL; i++) { + if ( strcmp(IndexedCatalogNames[i], catName) == 0) + return (true); + } + return (false); + } + + pg_class = heap_openr(RelationRelationName); + htup = ClassOidIndexScan(pg_class, catId); + heap_close(pg_class); + + if (! HeapTupleIsValid(htup)) { + elog(NOTICE, "CatalogHasIndex: no relation with oid %d", catId); + return false; + } + + pgRelP = (Form_pg_class)GETSTRUCT(htup); + return (pgRelP->relhasindex); +} + +/* + * CatalogIndexFetchTuple() -- Get a tuple that satisfies a scan key + * from a catalog relation. + * + * Since the index may contain pointers to dead tuples, we need to + * iterate until we find a tuple that's valid and satisfies the scan + * key. + */ +static HeapTuple +CatalogIndexFetchTuple(Relation heapRelation, + Relation idesc, + ScanKey skey) +{ + IndexScanDesc sd; + RetrieveIndexResult indexRes; + HeapTuple tuple; + Buffer buffer; + + sd = index_beginscan(idesc, false, 1, skey); + tuple = (HeapTuple)NULL; + + do { + indexRes = index_getnext(sd, ForwardScanDirection); + if (indexRes) { + ItemPointer iptr; + + iptr = &indexRes->heap_iptr; + tuple = heap_fetch(heapRelation, NowTimeQual, iptr, &buffer); + pfree(indexRes); + } else + break; + } while (!HeapTupleIsValid(tuple)); + + if (HeapTupleIsValid(tuple)) { + tuple = heap_copytuple(tuple); + ReleaseBuffer(buffer); + } + + index_endscan(sd); + if (sd->opaque) + pfree(sd->opaque); + pfree(sd); + return (tuple); +} + +/* + * The remainder of the file is for individual index scan routines. Each + * index should be scanned according to how it was defined during bootstrap + * (that is, functional or normal) and what arguments the cache lookup + * requires. Each routine returns the heap tuple that qualifies. + */ +HeapTuple +AttributeNameIndexScan(Relation heapRelation, + Oid relid, + char *attname) +{ + Relation idesc; + ScanKeyData skey; + OidName keyarg; + HeapTuple tuple; + + keyarg = mkoidname(relid, attname); + ScanKeyEntryInitialize(&skey, + (bits16)0x0, + (AttrNumber)1, + (RegProcedure)OidNameEqRegProcedure, + (Datum)keyarg); + + idesc = index_openr(AttributeNameIndex); + tuple = CatalogIndexFetchTuple(heapRelation, idesc, &skey); + + index_close(idesc); + pfree(keyarg); + + return tuple; +} + +HeapTuple +AttributeNumIndexScan(Relation heapRelation, + Oid relid, + AttrNumber attnum) +{ + Relation idesc; + ScanKeyData skey; + OidInt2 keyarg; + HeapTuple tuple; + + keyarg = mkoidint2(relid, (uint16)attnum); + ScanKeyEntryInitialize(&skey, + (bits16)0x0, + (AttrNumber)1, + (RegProcedure)OidInt2EqRegProcedure, + (Datum)keyarg); + + idesc = index_openr(AttributeNumIndex); + tuple = CatalogIndexFetchTuple(heapRelation, idesc, &skey); + + index_close(idesc); + pfree(keyarg); + + return tuple; +} + +HeapTuple +ProcedureOidIndexScan(Relation heapRelation, Oid procId) +{ + Relation idesc; + ScanKeyData skey; + HeapTuple tuple; + + ScanKeyEntryInitialize(&skey, + (bits16)0x0, + (AttrNumber)1, + (RegProcedure)ObjectIdEqualRegProcedure, + (Datum)procId); + + idesc = index_openr(ProcedureOidIndex); + tuple = CatalogIndexFetchTuple(heapRelation, idesc, &skey); + + index_close(idesc); + + return tuple; +} + +HeapTuple +ProcedureNameIndexScan(Relation heapRelation, + char *procName, + int nargs, + Oid *argTypes) +{ + Relation idesc; + ScanKeyData skey; + HeapTuple tuple; + IndexScanDesc sd; + RetrieveIndexResult indexRes; + Buffer buffer; + Form_pg_proc pgProcP; + bool bufferUsed = FALSE; + + ScanKeyEntryInitialize(&skey, + (bits16)0x0, + (AttrNumber)1, + (RegProcedure)NameEqualRegProcedure, + (Datum)procName); + + idesc = index_openr(ProcedureNameIndex); + + sd = index_beginscan(idesc, false, 1, &skey); + + /* + * for now, we do the work usually done by CatalogIndexFetchTuple + * by hand, so that we can check that the other keys match. when + * multi-key indices are added, they will be used here. + */ + do { + tuple = (HeapTuple)NULL; + if (bufferUsed) { + ReleaseBuffer(buffer); + bufferUsed = FALSE; + } + + indexRes = index_getnext(sd, ForwardScanDirection); + if (indexRes) { + ItemPointer iptr; + + iptr = &indexRes->heap_iptr; + tuple = heap_fetch(heapRelation, NowTimeQual, iptr, &buffer); + pfree(indexRes); + if (HeapTupleIsValid(tuple)) { + pgProcP = (Form_pg_proc)GETSTRUCT(tuple); + bufferUsed = TRUE; + } + } else + break; + } while (!HeapTupleIsValid(tuple) || + pgProcP->pronargs != nargs || + !oid8eq(&(pgProcP->proargtypes[0]), argTypes)); + + if (HeapTupleIsValid(tuple)) { + tuple = heap_copytuple(tuple); + ReleaseBuffer(buffer); + } + + index_endscan(sd); + index_close(idesc); + + return tuple; +} + +HeapTuple +ProcedureSrcIndexScan(Relation heapRelation, text *procSrc) +{ + Relation idesc; + IndexScanDesc sd; + ScanKeyData skey; + RetrieveIndexResult indexRes; + HeapTuple tuple; + Buffer buffer; + + ScanKeyEntryInitialize(&skey, + (bits16)0x0, + (AttrNumber)Anum_pg_proc_prosrc, + (RegProcedure)TextEqualRegProcedure, + (Datum)procSrc); + + idesc = index_openr(ProcedureSrcIndex); + sd = index_beginscan(idesc, false, 1, &skey); + + indexRes = index_getnext(sd, ForwardScanDirection); + if (indexRes) { + ItemPointer iptr; + + iptr = &indexRes->heap_iptr; + tuple = heap_fetch(heapRelation, NowTimeQual, iptr, &buffer); + pfree(indexRes); + } else + tuple = (HeapTuple)NULL; + + if (HeapTupleIsValid(tuple)) { + tuple = heap_copytuple(tuple); + ReleaseBuffer(buffer); + } + + index_endscan(sd); + + return tuple; +} + +HeapTuple +TypeOidIndexScan(Relation heapRelation, Oid typeId) +{ + Relation idesc; + ScanKeyData skey; + HeapTuple tuple; + + ScanKeyEntryInitialize(&skey, + (bits16)0x0, + (AttrNumber)1, + (RegProcedure)ObjectIdEqualRegProcedure, + (Datum)typeId); + + idesc = index_openr(TypeOidIndex); + tuple = CatalogIndexFetchTuple(heapRelation, idesc, &skey); + + index_close(idesc); + + return tuple; +} + +HeapTuple +TypeNameIndexScan(Relation heapRelation, char *typeName) +{ + Relation idesc; + ScanKeyData skey; + HeapTuple tuple; + + ScanKeyEntryInitialize(&skey, + (bits16)0x0, + (AttrNumber)1, + (RegProcedure)NameEqualRegProcedure, + (Datum)typeName); + + idesc = index_openr(TypeNameIndex); + tuple = CatalogIndexFetchTuple(heapRelation, idesc, &skey); + + index_close(idesc); + + return tuple; +} + +HeapTuple +ClassNameIndexScan(Relation heapRelation, char *relName) +{ + Relation idesc; + ScanKeyData skey; + HeapTuple tuple; + + ScanKeyEntryInitialize(&skey, + (bits16)0x0, + (AttrNumber)1, + (RegProcedure)NameEqualRegProcedure, + (Datum)relName); + + idesc = index_openr(ClassNameIndex); + + tuple = CatalogIndexFetchTuple(heapRelation, idesc, &skey); + + index_close(idesc); + return tuple; +} + +HeapTuple +ClassOidIndexScan(Relation heapRelation, Oid relId) +{ + Relation idesc; + ScanKeyData skey; + HeapTuple tuple; + + ScanKeyEntryInitialize(&skey, + (bits16)0x0, + (AttrNumber)1, + (RegProcedure)ObjectIdEqualRegProcedure, + (Datum)relId); + + idesc = index_openr(ClassOidIndex); + tuple = CatalogIndexFetchTuple(heapRelation, idesc, &skey); + + index_close(idesc); + + return tuple; +} diff --git a/src/backend/catalog/indexing.h b/src/backend/catalog/indexing.h new file mode 100644 index 00000000000..c1a83cbaf34 --- /dev/null +++ b/src/backend/catalog/indexing.h @@ -0,0 +1,103 @@ +/*------------------------------------------------------------------------- + * + * indexing.h-- + * This include provides some definitions to support indexing + * on system catalogs + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: indexing.h,v 1.1.1.1 1996/07/09 06:21:15 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef INDEXING_H +#define INDEXING_H + +#include "utils/rel.h" + +/* + * Some definitions for indices on pg_attribute + */ +#define Num_pg_attr_indices 3 +#define Num_pg_proc_indices 3 +#define Num_pg_type_indices 2 +#define Num_pg_class_indices 2 + + +/* + * Names of indices on system catalogs + */ +#define AttributeNameIndex "pg_attnameind" +#define AttributeNumIndex "pg_attnumind" +#define AttributeRelidIndex "pg_attrelidind" +#define ProcedureNameIndex "pg_procnameind" +#define ProcedureOidIndex "pg_procidind" +#define ProcedureSrcIndex "pg_procsrcind" +#define TypeNameIndex "pg_typenameind" +#define TypeOidIndex "pg_typeidind" +#define ClassNameIndex "pg_classnameind" +#define ClassOidIndex "pg_classoidind" + +extern char *Name_pg_attr_indices[]; +extern char *Name_pg_proc_indices[]; +extern char *Name_pg_type_indices[]; +extern char *Name_pg_class_indices[]; + +extern char *IndexedCatalogNames[]; + +/* + * indexing.c prototypes + * + * Functions for each index to perform the necessary scan on a cache miss. + */ +extern void CatalogOpenIndices(int nIndices, char *names[], Relation idescs[]); +extern void CatalogCloseIndices(int nIndices, Relation *idescs); +extern void CatalogIndexInsert(Relation *idescs, + int nIndices, + Relation heapRelation, + HeapTuple heapTuple); +extern bool CatalogHasIndex(char *catName, Oid catId); + +extern HeapTuple AttributeNameIndexScan(Relation heapRelation, + Oid relid, + char *attname); + +extern HeapTuple AttributeNumIndexScan(Relation heapRelation, + Oid relid, + AttrNumber attnum); +extern HeapTuple ProcedureOidIndexScan(Relation heapRelation, Oid procId); +extern HeapTuple ProcedureNameIndexScan(Relation heapRelation, + char *procName, int nargs, Oid *argTypes); +extern HeapTuple ProcedureSrcIndexScan(Relation heapRelation, text *procSrc); +extern HeapTuple TypeOidIndexScan(Relation heapRelation, Oid typeId); +extern HeapTuple TypeNameIndexScan(Relation heapRelation, char *typeName); +extern HeapTuple ClassNameIndexScan(Relation heapRelation, char *relName); +extern HeapTuple ClassOidIndexScan(Relation heapRelation, Oid relId); + + +/* + * What follows are lines processed by genbki.sh to create the statements + * the bootstrap parser will turn into DefineIndex commands. + * + * The keyword is DECLARE_INDEX every thing after that is just like in a + * normal specification of the 'define index' POSTQUEL command. + */ +DECLARE_INDEX(pg_attnameind on pg_attribute using btree (mkoidname(attrelid, attname) oidname_ops)); +DECLARE_INDEX(pg_attnumind on pg_attribute using btree (mkoidint2(attrelid, attnum) oidint2_ops)); +DECLARE_INDEX(pg_attrelidind on pg_attribute using btree (attrelid oid_ops)); + +DECLARE_INDEX(pg_procidind on pg_proc using btree (Oid oid_ops)); +DECLARE_INDEX(pg_procnameind on pg_proc using btree (proname name_ops)); +DECLARE_INDEX(pg_procsrcind on pg_proc using btree (prosrc text_ops)); + +DECLARE_INDEX(pg_typeidind on pg_type using btree (Oid oid_ops)); +DECLARE_INDEX(pg_typenameind on pg_type using btree (typname name_ops)); + +DECLARE_INDEX(pg_classnameind on pg_class using btree (relname name_ops)); +DECLARE_INDEX(pg_classoidind on pg_class using btree (Oid oid_ops)); + +/* now build indices in the initialization scripts */ +BUILD_INDICES + +#endif /* INDEXING_H */ diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c new file mode 100644 index 00000000000..7fe895e0f0c --- /dev/null +++ b/src/backend/catalog/pg_aggregate.c @@ -0,0 +1,325 @@ +/*------------------------------------------------------------------------- + * + * pg_aggregate.c-- + * routines to support manipulation of the pg_aggregate relation + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/catalog/pg_aggregate.c,v 1.1.1.1 1996/07/09 06:21:16 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <string.h> +#include "postgres.h" + +#include "access/heapam.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "access/htup.h" +#include "access/tupdesc.h" +#include "utils/rel.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/builtins.h" +#include "fmgr.h" + +#include "catalog/catname.h" +#include "utils/syscache.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "catalog/pg_aggregate.h" + +/* ---------------- + * AggregateCreate + * + * aggregates overloading has been added. Instead of the full + * overload support we have for functions, aggregate overloading only + * applies to exact basetype matches. That is, we don't check the + * the inheritance hierarchy + * + * OLD COMMENTS: + * Currently, redefining aggregates using the same name is not + * supported. In such a case, a warning is printed that the + * aggregate already exists. If such is not the case, a new tuple + * is created and inserted in the aggregate relation. The fields + * of this tuple are aggregate name, owner id, 2 transition functions + * (called aggtransfn1 and aggtransfn2), final function (aggfinalfn), + * type of data on which aggtransfn1 operates (aggbasetype), return + * types of the two transition functions (aggtranstype1 and + * aggtranstype2), final return type (aggfinaltype), and initial values + * for the two state transition functions (agginitval1 and agginitval2). + * All types and functions must have been defined + * prior to defining the aggregate. + * + * --------------- + */ +void +AggregateCreate(char *aggName, + char *aggtransfn1Name, + char *aggtransfn2Name, + char *aggfinalfnName, + char *aggbasetypeName, + char *aggtransfn1typeName, + char *aggtransfn2typeName, + char *agginitval1, + char *agginitval2) +{ + register i; + Relation aggdesc; + HeapTuple tup; + char nulls[Natts_pg_aggregate]; + Datum values[Natts_pg_aggregate]; + Form_pg_proc proc; + Oid xfn1 = InvalidOid; + Oid xfn2 = InvalidOid; + Oid ffn = InvalidOid; + Oid xbase = InvalidOid; + Oid xret1 = InvalidOid; + Oid xret2 = InvalidOid; + Oid fret = InvalidOid; + Oid fnArgs[8]; + TupleDesc tupDesc; + + memset(fnArgs, 0, 8 * sizeof(Oid)); + + /* sanity checks */ + if (!aggName) + elog(WARN, "AggregateCreate: no aggregate name supplied"); + + if (!aggtransfn1Name && !aggtransfn2Name) + elog(WARN, "AggregateCreate: aggregate must have at least one transition function"); + + tup = SearchSysCacheTuple(TYPNAME, + PointerGetDatum(aggbasetypeName), + 0,0,0); + if(!HeapTupleIsValid(tup)) + elog(WARN, "AggregateCreate: Type '%s' undefined",aggbasetypeName); + xbase = tup->t_oid; + + if (aggtransfn1Name) { + tup = SearchSysCacheTuple(TYPNAME, + PointerGetDatum(aggtransfn1typeName), + 0,0,0); + if(!HeapTupleIsValid(tup)) + elog(WARN, "AggregateCreate: Type '%s' undefined", + aggtransfn1typeName); + xret1 = tup->t_oid; + + fnArgs[0] = xret1; + fnArgs[1] = xbase; + tup = SearchSysCacheTuple(PRONAME, + PointerGetDatum(aggtransfn1Name), + Int32GetDatum(2), + PointerGetDatum(fnArgs), + 0); + if(!HeapTupleIsValid(tup)) + elog(WARN, "AggregateCreate: '%s('%s', '%s') does not exist", + aggtransfn1Name, aggtransfn1typeName, aggbasetypeName); + if (((Form_pg_proc) GETSTRUCT(tup))->prorettype != xret1) + elog(WARN, "AggregateCreate: return type of '%s' is not '%s'", + aggtransfn1Name, + aggtransfn1typeName); + xfn1 = tup->t_oid; + if (!OidIsValid(xfn1) || !OidIsValid(xret1) || + !OidIsValid(xbase)) + elog(WARN, "AggregateCreate: bogus function '%s'", aggfinalfnName); + } + + if (aggtransfn2Name) { + tup = SearchSysCacheTuple(TYPNAME, + PointerGetDatum(aggtransfn2typeName), + 0,0,0); + if(!HeapTupleIsValid(tup)) + elog(WARN, "AggregateCreate: Type '%s' undefined", + aggtransfn2typeName); + xret2 = tup->t_oid; + + fnArgs[0] = xret2; + fnArgs[1] = 0; + tup = SearchSysCacheTuple(PRONAME, + PointerGetDatum(aggtransfn2Name), + Int32GetDatum(1), + PointerGetDatum(fnArgs), + 0); + if(!HeapTupleIsValid(tup)) + elog(WARN, "AggregateCreate: '%s'('%s') does not exist", + aggtransfn2Name, aggtransfn2typeName); + if (((Form_pg_proc) GETSTRUCT(tup))->prorettype != xret2) + elog(WARN, "AggregateCreate: return type of '%s' is not '%s'", + aggtransfn2Name, aggtransfn2typeName); + xfn2 = tup->t_oid; + if (!OidIsValid(xfn2) || !OidIsValid(xret2)) + elog(WARN, "AggregateCreate: bogus function '%s'",aggfinalfnName); + } + + tup = SearchSysCacheTuple(AGGNAME, PointerGetDatum(aggName), + ObjectIdGetDatum(xbase), + 0,0); + if (HeapTupleIsValid(tup)) + elog(WARN, + "AggregateCreate: aggregate '%s' with base type '%s' already exists", + aggName, aggbasetypeName); + + /* more sanity checks */ + if (aggtransfn1Name && aggtransfn2Name && !aggfinalfnName) + elog(WARN, "AggregateCreate: Aggregate must have final function with both transition functions"); + + if ((!aggtransfn1Name || !aggtransfn2Name) && aggfinalfnName) + elog(WARN, "AggregateCreate: Aggregate cannot have final function without both transition functions"); + + if (aggfinalfnName) { + fnArgs[0] = xret1; + fnArgs[1] = xret2; + tup = SearchSysCacheTuple(PRONAME, + PointerGetDatum(aggfinalfnName), + Int32GetDatum(2), + PointerGetDatum(fnArgs), + 0); + if(!HeapTupleIsValid(tup)) + elog(WARN, "AggregateCreate: '%s'('%s','%s') does not exist", + aggfinalfnName, aggtransfn1typeName, aggtransfn2typeName); + ffn = tup->t_oid; + proc = (Form_pg_proc) GETSTRUCT(tup); + fret = proc->prorettype; + if (!OidIsValid(ffn) || !OidIsValid(fret)) + elog(WARN, "AggregateCreate: bogus function '%s'", aggfinalfnName); + } + + /* + * If transition function 2 is defined, it must have an initial value, + * whereas transition function 1 does not, which allows man and min + * aggregates to return NULL if they are evaluated on empty sets. + */ + if (OidIsValid(xfn2) && !agginitval2) + elog(WARN, "AggregateCreate: transition function 2 MUST have an initial value"); + + /* initialize nulls and values */ + for(i=0; i < Natts_pg_aggregate; i++) { + nulls[i] = ' '; + values[i] = (Datum)NULL; + } + values[Anum_pg_aggregate_aggname-1] = PointerGetDatum(aggName); + values[Anum_pg_aggregate_aggowner-1] = + Int32GetDatum(GetUserId()); + values[Anum_pg_aggregate_aggtransfn1-1] = + ObjectIdGetDatum(xfn1); + values[Anum_pg_aggregate_aggtransfn2-1] = + ObjectIdGetDatum(xfn2); + values[Anum_pg_aggregate_aggfinalfn-1] = + ObjectIdGetDatum(ffn); + + values[Anum_pg_aggregate_aggbasetype-1] = + ObjectIdGetDatum(xbase); + if (!OidIsValid(xfn1)) { + values[Anum_pg_aggregate_aggtranstype1-1] = + ObjectIdGetDatum(InvalidOid); + values[Anum_pg_aggregate_aggtranstype2-1] = + ObjectIdGetDatum(xret2); + values[Anum_pg_aggregate_aggfinaltype-1] = + ObjectIdGetDatum(xret2); + } + else if (!OidIsValid(xfn2)) { + values[Anum_pg_aggregate_aggtranstype1-1] = + ObjectIdGetDatum(xret1); + values[Anum_pg_aggregate_aggtranstype2-1] = + ObjectIdGetDatum(InvalidOid); + values[Anum_pg_aggregate_aggfinaltype-1] = + ObjectIdGetDatum(xret1); + } + else { + values[Anum_pg_aggregate_aggtranstype1-1] = + ObjectIdGetDatum(xret1); + values[Anum_pg_aggregate_aggtranstype2-1] = + ObjectIdGetDatum(xret2); + values[Anum_pg_aggregate_aggfinaltype-1] = + ObjectIdGetDatum(fret); + } + + if (agginitval1) + values[Anum_pg_aggregate_agginitval1-1] = PointerGetDatum(textin(agginitval1)); + else + nulls[Anum_pg_aggregate_agginitval1-1] = 'n'; + + if (agginitval2) + values[Anum_pg_aggregate_agginitval2-1] = PointerGetDatum(textin(agginitval2)); + else + nulls[Anum_pg_aggregate_agginitval2-1] = 'n'; + + if (!RelationIsValid(aggdesc = heap_openr(AggregateRelationName))) + elog(WARN, "AggregateCreate: could not open '%s'", + AggregateRelationName); + + tupDesc = aggdesc->rd_att; + if (!HeapTupleIsValid(tup = heap_formtuple(tupDesc, + values, + nulls))) + elog(WARN, "AggregateCreate: heap_formtuple failed"); + if (!OidIsValid(heap_insert(aggdesc, tup))) + elog(WARN, "AggregateCreate: heap_insert failed"); + heap_close(aggdesc); + +} + +char * +AggNameGetInitVal(char *aggName, Oid basetype, int xfuncno, bool *isNull) +{ + HeapTuple tup; + Relation aggRel; + int initValAttno; + Oid transtype; + text *textInitVal; + char *strInitVal, *initVal; + extern char *textout(); + + Assert(PointerIsValid(aggName)); + Assert(PointerIsValid(isNull)); + Assert(xfuncno == 1 || xfuncno == 2); + + tup = SearchSysCacheTuple(AGGNAME, + PointerGetDatum(aggName), + PointerGetDatum(basetype), + 0,0); + if (!HeapTupleIsValid(tup)) + elog(WARN, "AggNameGetInitVal: cache lookup failed for aggregate '%s'", + aggName); + if (xfuncno == 1) { + transtype = ((Form_pg_aggregate) GETSTRUCT(tup))->aggtranstype1; + initValAttno = Anum_pg_aggregate_agginitval1; + } + else if (xfuncno == 2) { + transtype = ((Form_pg_aggregate) GETSTRUCT(tup))->aggtranstype2; + initValAttno = Anum_pg_aggregate_agginitval2; + } + + aggRel = heap_openr(AggregateRelationName); + if (!RelationIsValid(aggRel)) + elog(WARN, "AggNameGetInitVal: could not open \"%-.*s\"", + AggregateRelationName); + /* + * must use fastgetattr in case one or other of the init values is NULL + */ + textInitVal = (text *) fastgetattr(tup, initValAttno, + RelationGetTupleDescriptor(aggRel), + isNull); + if (!PointerIsValid(textInitVal)) + *isNull = true; + if (*isNull) { + heap_close(aggRel); + return((char *) NULL); + } + strInitVal = textout(textInitVal); + heap_close(aggRel); + + tup = SearchSysCacheTuple(TYPOID, ObjectIdGetDatum(transtype), + 0,0,0); + if (!HeapTupleIsValid(tup)) { + pfree(strInitVal); + elog(WARN, "AggNameGetInitVal: cache lookup failed on aggregate transition function return type"); + } + initVal = fmgr(((TypeTupleForm) GETSTRUCT(tup))->typinput, strInitVal, -1); + pfree(strInitVal); + return(initVal); +} diff --git a/src/backend/catalog/pg_aggregate.h b/src/backend/catalog/pg_aggregate.h new file mode 100644 index 00000000000..7ed983506b0 --- /dev/null +++ b/src/backend/catalog/pg_aggregate.h @@ -0,0 +1,132 @@ +/*------------------------------------------------------------------------- + * + * pg_aggregate.h-- + * definition of the system "aggregate" relation (pg_aggregate) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_aggregate.h,v 1.1.1.1 1996/07/09 06:21:16 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_AGGREGATE_H +#define PG_AGGREGATE_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------------------------------------------------------- + * pg_aggregate definition. + * + * cpp turns this into typedef struct FormData_pg_aggregate + * + * aggname name of the aggregate + * aggtransfn1 transition function 1 + * aggtransfn2 transition function 2 + * aggfinalfn final function + * aggbasetype type of data on which aggregate operates + * aggtranstype1 output types for xition func 1 + * aggtranstype2 output types for xition func 2 + * aggfinaltype output type for final func + * agginitval1 initial aggregate value + * agginitval2 initial value for transition state 2 + * ---------------------------------------------------------------- + */ +CATALOG(pg_aggregate) { + NameData aggname; + Oid aggowner; + regproc aggtransfn1; + regproc aggtransfn2; + regproc aggfinalfn; + Oid aggbasetype; + Oid aggtranstype1; + Oid aggtranstype2; + Oid aggfinaltype; + text agginitval1; /* VARIABLE LENGTH FIELD */ + text agginitval2; /* VARIABLE LENGTH FIELD */ +} FormData_pg_aggregate; + +/* ---------------- + * Form_pg_aggregate corresponds to a pointer to a tuple with + * the format of pg_aggregate relation. + * ---------------- + */ +typedef FormData_pg_aggregate *Form_pg_aggregate; + +/* ---------------- + * compiler constants for pg_aggregate + * ---------------- + */ + +#define Natts_pg_aggregate 11 +#define Anum_pg_aggregate_aggname 1 +#define Anum_pg_aggregate_aggowner 2 +#define Anum_pg_aggregate_aggtransfn1 3 +#define Anum_pg_aggregate_aggtransfn2 4 +#define Anum_pg_aggregate_aggfinalfn 5 +#define Anum_pg_aggregate_aggbasetype 6 +#define Anum_pg_aggregate_aggtranstype1 7 +#define Anum_pg_aggregate_aggtranstype2 8 +#define Anum_pg_aggregate_aggfinaltype 9 +#define Anum_pg_aggregate_agginitval1 10 +#define Anum_pg_aggregate_agginitval2 11 + + +/* ---------------- + * initial contents of pg_aggregate + * --------------- + */ + +DATA(insert OID = 0 ( avg PGUID int4pl int4inc int4div 23 23 23 23 0 0 )); +DATA(insert OID = 0 ( avg PGUID int2pl int2inc int2div 21 21 21 21 0 0 )); +DATA(insert OID = 0 ( avg PGUID float4pl float4inc float4div 700 700 700 700 0.0 0.0 )); +DATA(insert OID = 0 ( avg PGUID float8pl float8inc float8div 701 701 701 701 0.0 0.0 )); + +DATA(insert OID = 0 ( sum PGUID int4pl - - 23 23 0 23 0 _null_ )); +DATA(insert OID = 0 ( sum PGUID int2pl - - 21 21 0 21 0 _null_ )); +DATA(insert OID = 0 ( sum PGUID float4pl - - 700 700 0 700 0.0 _null_ )); +DATA(insert OID = 0 ( sum PGUID float8pl - - 701 701 0 701 0.0 _null_ )); + +DATA(insert OID = 0 ( max PGUID int4larger - - 23 23 0 23 _null_ _null_ )); +DATA(insert OID = 0 ( max PGUID int2larger - - 21 21 0 21 _null_ _null_ )); +DATA(insert OID = 0 ( max PGUID float4larger - - 700 700 0 700 _null_ _null_ )); +DATA(insert OID = 0 ( max PGUID float8larger - - 701 701 0 701 _null_ _null_ )); + +DATA(insert OID = 0 ( min PGUID int4smaller - - 23 23 0 23 _null_ _null_ )); +DATA(insert OID = 0 ( min PGUID int2smaller - - 21 21 0 21 _null_ _null_ )); +DATA(insert OID = 0 ( min PGUID float4smaller - - 700 700 0 700 _null_ _null_ )); +DATA(insert OID = 0 ( min PGUID float8smaller - - 701 701 0 701 _null_ _null_ )); + +DATA(insert OID = 0 ( count PGUID - int4inc - 0 0 23 23 _null_ 0 )); + +/* + * prototypes for fucnctions in pg_aggregate.c + */ +extern void AggregateCreate(char *aggName, + char *aggtransfn1Name, + char *aggtransfn2Name, + char *aggfinalfnName, + char *aggbasetypeName, + char *aggtransfn1typeName, + char *aggtransfn2typeName, + char *agginitval1, + char *agginitval2); +extern char *AggNameGetInitVal(char *aggName, Oid basetype, + int xfuncno, bool *isNull); + +#endif /* PG_AGGREGATE_H */ + + + + diff --git a/src/backend/catalog/pg_am.h b/src/backend/catalog/pg_am.h new file mode 100644 index 00000000000..0f36e7c4332 --- /dev/null +++ b/src/backend/catalog/pg_am.h @@ -0,0 +1,115 @@ +/*------------------------------------------------------------------------- + * + * pg_am.h-- + * definition of the system "am" relation (pg_am) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_am.h,v 1.1.1.1 1996/07/09 06:21:16 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + * XXX do NOT break up DATA() statements into multiple lines! + * the scripts are not as smart as you might think... + * + *------------------------------------------------------------------------- + */ +#ifndef PG_AM_H +#define PG_AM_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_am definition. cpp turns this into + * typedef struct FormData_pg_am + * ---------------- + */ +CATALOG(pg_am) { + NameData amname; + Oid amowner; + char amkind; + int2 amstrategies; + int2 amsupport; + regproc amgettuple; + regproc aminsert; + regproc amdelete; + regproc amgetattr; + regproc amsetlock; + regproc amsettid; + regproc amfreetuple; + regproc ambeginscan; + regproc amrescan; + regproc amendscan; + regproc ammarkpos; + regproc amrestrpos; + regproc amopen; + regproc amclose; + regproc ambuild; + regproc amcreate; + regproc amdestroy; +} FormData_pg_am; + +/* ---------------- + * Form_pg_am corresponds to a pointer to a tuple with + * the format of pg_am relation. + * ---------------- + */ +typedef FormData_pg_am *Form_pg_am; + +/* ---------------- + * compiler constants for pg_am + * ---------------- + */ +#define Natts_pg_am 22 +#define Anum_pg_am_amname 1 +#define Anum_pg_am_amowner 2 +#define Anum_pg_am_amkind 3 +#define Anum_pg_am_amstrategies 4 +#define Anum_pg_am_amsupport 5 +#define Anum_pg_am_amgettuple 6 +#define Anum_pg_am_aminsert 7 +#define Anum_pg_am_amdelete 8 +#define Anum_pg_am_amgetattr 9 +#define Anum_pg_am_amsetlock 10 +#define Anum_pg_am_amsettid 11 +#define Anum_pg_am_amfreetuple 12 +#define Anum_pg_am_ambeginscan 13 +#define Anum_pg_am_amrescan 14 +#define Anum_pg_am_amendscan 15 +#define Anum_pg_am_ammarkpos 16 +#define Anum_pg_am_amrestrpos 17 +#define Anum_pg_am_amopen 18 +#define Anum_pg_am_amclose 19 +#define Anum_pg_am_ambuild 20 +#define Anum_pg_am_amcreate 21 +#define Anum_pg_am_amdestroy 22 + +/* ---------------- + * initial contents of pg_am + * ---------------- + */ + +DATA(insert OID = 405 ( hash PGUID "o" 1 1 hashgettuple hashinsert hashdelete - - - - hashbeginscan hashrescan hashendscan hashmarkpos hashrestrpos - - hashbuild - - )); +DATA(insert OID = 402 ( rtree PGUID "o" 8 3 rtgettuple rtinsert rtdelete - - - - rtbeginscan rtrescan rtendscan rtmarkpos rtrestrpos - - rtbuild - - )); +DATA(insert OID = 403 ( btree PGUID "o" 5 1 btgettuple btinsert btdelete - - - - btbeginscan btrescan btendscan btmarkpos btrestrpos - - btbuild - - )); +#define BTREE_AM_OID 403 + +BKI_BEGIN +#ifdef NOBTREE +BKI_END +DATA(insert OID = 404 ( nobtree PGUID "o" 5 1 nobtgettuple nobtinsert nobtdelete - - - - nobtbeginscan nobtrescan nobtendscan nobtmarkpos nobtrestrpos - - nobtbuild - - )); +BKI_BEGIN +#endif /* NOBTREE */ +BKI_END + +#endif /* PG_AM_H */ diff --git a/src/backend/catalog/pg_amop.h b/src/backend/catalog/pg_amop.h new file mode 100644 index 00000000000..e9d12127aab --- /dev/null +++ b/src/backend/catalog/pg_amop.h @@ -0,0 +1,546 @@ +/*------------------------------------------------------------------------- + * + * pg_amop.h-- + * definition of the system "amop" relation (pg_amop) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_amop.h,v 1.1.1.1 1996/07/09 06:21:16 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_AMOP_H +#define PG_AMOP_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" +#include "access/istrat.h" + +/* ---------------- + * pg_amop definition. cpp turns this into + * typedef struct FormData_pg_amop + * ---------------- + */ +CATALOG(pg_amop) { + Oid amopid; + Oid amopclaid; + Oid amopopr; + int2 amopstrategy; + regproc amopselect; + regproc amopnpages; +} FormData_pg_amop; + +/* ---------------- + * Form_pg_amop corresponds to a pointer to a tuple with + * the format of pg_amop relation. + * ---------------- + */ +typedef FormData_pg_amop *Form_pg_amop; + +/* ---------------- + * compiler constants for pg_amop + * ---------------- + */ +/* #define Name_pg_amop "pg_amop" */ +#define Natts_pg_amop 6 +#define Anum_pg_amop_amopid 1 +#define Anum_pg_amop_amopclaid 2 +#define Anum_pg_amop_amopopr 3 +#define Anum_pg_amop_amopstrategy 4 +#define Anum_pg_amop_amopselect 5 +#define Anum_pg_amop_amopnpages 6 + +/* ---------------- + * initial contents of pg_amop + * ---------------- + */ + +/* + * rtree box_ops + */ + +DATA(insert OID = 0 ( 402 422 493 1 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 422 494 2 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 422 500 3 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 422 495 4 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 422 496 5 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 422 499 6 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 422 498 7 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 422 497 8 rtsel rtnpage )); + +/* + * rtree bigbox_ops + */ + +DATA(insert OID = 0 ( 402 433 493 1 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 433 494 2 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 433 500 3 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 433 495 4 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 433 496 5 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 433 499 6 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 433 498 7 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 433 497 8 rtsel rtnpage )); + +/* + * rtree poly_ops (supports polygons) + */ + +DATA(insert OID = 0 ( 402 434 485 1 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 434 486 2 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 434 487 3 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 434 488 4 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 434 489 5 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 434 490 6 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 434 491 7 rtsel rtnpage )); +DATA(insert OID = 0 ( 402 434 492 8 rtsel rtnpage )); + +/* + * nbtree int2_ops + */ + +DATA(insert OID = 0 ( 403 421 95 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 421 522 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 421 94 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 421 524 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 421 520 5 btreesel btreenpage )); + +/* + * nbtree float8_ops + */ + +DATA(insert OID = 0 ( 403 423 672 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 423 673 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 423 670 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 423 675 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 423 674 5 btreesel btreenpage )); + +/* + * nbtree int24_ops + */ + +DATA(insert OID = 0 ( 403 424 534 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 424 540 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 424 532 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 424 542 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 424 536 5 btreesel btreenpage )); + +/* + * nbtree int42_ops + */ + +DATA(insert OID = 0 ( 403 425 535 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 425 541 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 425 533 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 425 543 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 425 537 5 btreesel btreenpage )); + +/* + * nbtree int4_ops + */ + +DATA(insert OID = 0 ( 403 426 97 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 426 523 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 426 96 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 426 525 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 426 521 5 btreesel btreenpage )); + +/* + * nbtree oid_ops + */ + +DATA(insert OID = 0 ( 403 427 609 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 427 611 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 427 607 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 427 612 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 427 610 5 btreesel btreenpage )); + +/* + * nbtree float4_ops + */ + +DATA(insert OID = 0 ( 403 428 622 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 428 624 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 428 620 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 428 625 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 428 623 5 btreesel btreenpage )); + +/* + * nbtree char_ops + */ + +DATA(insert OID = 0 ( 403 429 631 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 429 632 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 429 92 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 429 634 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 429 633 5 btreesel btreenpage )); + +/* + * nbtree char2_ops + */ + +DATA(insert OID = 0 ( 403 406 418 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 406 457 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 406 412 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 406 463 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 406 460 5 btreesel btreenpage )); + +/* + * nbtree char4_ops + */ + +DATA(insert OID = 0 ( 403 407 419 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 407 458 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 407 413 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 407 464 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 407 461 5 btreesel btreenpage )); + +/* + * nbtree char8_ops + */ + +DATA(insert OID = 0 ( 403 408 420 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 408 459 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 408 414 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 408 465 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 408 462 5 btreesel btreenpage )); + +/* + * nbtree name_ops + */ + +DATA(insert OID = 0 ( 403 409 660 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 409 661 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 409 93 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 409 663 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 409 662 5 btreesel btreenpage )); + +/* + * nbtree char16_ops + */ + +DATA(insert OID = 0 ( 403 430 645 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 430 646 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 430 99 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 430 648 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 430 647 5 btreesel btreenpage )); + +/* + * nbtree text_ops + */ + +DATA(insert OID = 0 ( 403 431 664 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 431 665 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 431 98 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 431 667 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 431 666 5 btreesel btreenpage )); + +/* + * nbtree abstime_ops + */ + +DATA(insert OID = 0 ( 403 432 562 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 432 564 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 432 560 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 432 565 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 432 563 5 btreesel btreenpage )); + +/* + * nbtree oidint4_ops + */ + +DATA(insert OID = 0 ( 403 435 930 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 435 931 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 435 932 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 435 933 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 435 934 5 btreesel btreenpage )); + +/* + * nbtree oidint2_ops + */ + +DATA(insert OID = 0 ( 403 437 830 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 437 831 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 437 832 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 437 833 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 437 834 5 btreesel btreenpage )); + +/* + * nbtree oidname_ops + */ + +DATA(insert OID = 0 ( 403 436 676 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 436 677 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 436 678 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 436 679 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 436 680 5 btreesel btreenpage )); + +/* + * nbtree bpchar_ops + */ + +DATA(insert OID = 0 ( 403 1076 1058 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1076 1059 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1076 1054 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1076 1061 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1076 1060 5 btreesel btreenpage )); + +/* + * nbtree varchar_ops + */ + +DATA(insert OID = 0 ( 403 1077 1066 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1077 1067 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1077 1062 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1077 1069 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1077 1068 5 btreesel btreenpage )); + +/* + * nbtree date_ops + */ + +DATA(insert OID = 0 ( 403 1114 1095 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1114 1096 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1114 1093 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1114 1098 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1114 1097 5 btreesel btreenpage )); + + +/* + * nbtree time_ops + */ + +DATA(insert OID = 0 ( 403 1115 1110 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1115 1111 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1115 1108 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1115 1113 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 403 1115 1112 5 btreesel btreenpage )); + +BKI_BEGIN +#ifdef NOBTREE +BKI_END +/* + * nobtree int2_ops + */ + +DATA(insert OID = 0 ( 404 421 95 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 421 522 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 421 94 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 421 524 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 421 520 5 btreesel btreenpage )); + +/* + * nobtree float8_ops + */ + +DATA(insert OID = 0 ( 404 423 672 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 423 673 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 423 670 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 423 675 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 423 674 5 btreesel btreenpage )); + +/* + * nobtree int24_ops + */ + +DATA(insert OID = 0 ( 404 424 534 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 424 540 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 424 532 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 424 542 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 424 536 5 btreesel btreenpage )); + +/* + * nobtree int42_ops + */ + +DATA(insert OID = 0 ( 404 425 535 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 425 541 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 425 533 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 425 543 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 425 537 5 btreesel btreenpage )); + +/* + * nobtree int4_ops + */ + +DATA(insert OID = 0 ( 404 426 97 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 426 523 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 426 96 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 426 525 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 426 521 5 btreesel btreenpage )); + +/* + * nobtree oid_ops + */ + +DATA(insert OID = 0 ( 404 427 609 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 427 611 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 427 607 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 427 612 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 427 610 5 btreesel btreenpage )); + +/* + * nobtree float4_ops + */ + +DATA(insert OID = 0 ( 404 428 622 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 428 624 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 428 620 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 428 625 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 428 623 5 btreesel btreenpage )); + +/* + * nobtree char_ops + */ + +DATA(insert OID = 0 ( 404 429 631 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 429 632 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 429 92 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 429 634 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 429 633 5 btreesel btreenpage )); + +/* + * nobtree char2_ops + */ + +DATA(insert OID = 0 ( 404 406 418 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 406 457 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 406 412 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 406 463 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 406 460 5 btreesel btreenpage )); + +/* + * nobtree char4_ops + */ + +DATA(insert OID = 0 ( 404 407 419 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 407 458 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 407 413 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 407 464 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 407 461 5 btreesel btreenpage )); + +/* + * nobtree char8_ops + */ + +DATA(insert OID = 0 ( 404 408 420 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 408 459 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 408 414 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 408 465 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 408 462 5 btreesel btreenpage )); + +/* + * nobtree char16_ops + */ + +DATA(insert OID = 0 ( 404 430 645 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 430 646 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 430 99 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 430 648 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 430 647 5 btreesel btreenpage )); + +/* + * nobtree name_ops + */ + +DATA(insert OID = 0 ( 404 409 660 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 409 661 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 409 93 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 409 663 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 409 662 5 btreesel btreenpage )); + +/* + * nobtree text_ops + */ + +DATA(insert OID = 0 ( 404 431 664 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 431 665 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 431 98 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 431 667 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 431 666 5 btreesel btreenpage )); + +/* + * nobtree abstime_ops + */ + +DATA(insert OID = 0 ( 404 432 562 1 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 432 564 2 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 432 560 3 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 432 565 4 btreesel btreenpage )); +DATA(insert OID = 0 ( 404 432 563 5 btreesel btreenpage )); + +BKI_BEGIN +#endif /* NOBTREE */ +BKI_END + +/* + * hash table int2_ops + */ +DATA(insert OID = 0 ( 405 421 94 1 btreesel btreenpage )); +/* + * hash table float8_ops + */ +DATA(insert OID = 0 ( 405 423 670 1 btreesel btreenpage )); +/* + * hash table int4_ops + */ +DATA(insert OID = 0 ( 405 426 96 1 hashsel hashnpage )); +/* + * hash table oid_ops + */ +DATA(insert OID = 0 ( 405 427 607 1 hashsel hashnpage )); +/* + * hash table float4_ops + */ +DATA(insert OID = 0 ( 405 428 620 1 hashsel hashnpage )); +/* + * hash table char_ops + */ +DATA(insert OID = 0 ( 405 429 92 1 hashsel hashnpage )); +/* + * hash table char2_ops + */ +DATA(insert OID = 0 ( 405 406 412 1 hashsel hashnpage )); +/* + * hash table char4_ops + */ +DATA(insert OID = 0 ( 405 407 413 1 hashsel hashnpage )); +/* + * hash table char8_ops + */ +DATA(insert OID = 0 ( 405 408 414 1 hashsel hashnpage )); +/* + * hash table char16_ops + */ +DATA(insert OID = 0 ( 405 430 99 1 hashsel hashnpage )); +/* + * hash table name_ops + */ +DATA(insert OID = 0 ( 405 409 93 1 hashsel hashnpage )); +/* + * hash table text_ops + */ +DATA(insert OID = 0 ( 405 431 98 1 hashsel hashnpage )); + +/* + * hash table bpchar_ops + */ +DATA(insert OID = 0 ( 405 1076 1054 1 hashsel hashnpage )); + +/* + * hash table varchar_ops + */ +DATA(insert OID = 0 ( 405 1077 1062 1 hashsel hashnpage )); + + +#endif /* PG_AMOP_H */ diff --git a/src/backend/catalog/pg_amproc.h b/src/backend/catalog/pg_amproc.h new file mode 100644 index 00000000000..cacc2b72701 --- /dev/null +++ b/src/backend/catalog/pg_amproc.h @@ -0,0 +1,134 @@ +/*------------------------------------------------------------------------- + * + * pg_amproc.h-- + * definition of the system "amproc" relation (pg_amproce) + * along with the relation's initial contents. The amproc + * catalog is used to store procedures used by indexed access + * methods that aren't associated with operators. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_amproc.h,v 1.1.1.1 1996/07/09 06:21:16 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_AMPROC_H +#define PG_AMPROC_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_amproc definition. cpp turns this into + * typedef struct FormData_pg_amproc + * ---------------- + */ +CATALOG(pg_amproc) { + Oid amid; + Oid amopclaid; + Oid amproc; + int2 amprocnum; +} FormData_pg_amproc; + +/* ---------------- + * Form_pg_amproc corresponds to a pointer to a tuple with + * the format of pg_amproc relation. + * ---------------- + */ +typedef FormData_pg_amproc *Form_pg_amproc; + +/* ---------------- + * compiler constants for pg_amproc + * ---------------- + */ +#define Natts_pg_amproc 4 +#define Anum_pg_amproc_amid 1 +#define Anum_pg_amproc_amopclaid 2 +#define Anum_pg_amproc_amproc 3 +#define Anum_pg_amproc_amprocnum 4 + +/* ---------------- + * initial contents of pg_amproc + * ---------------- + */ + +DATA(insert OID = 0 (402 422 193 1)); +DATA(insert OID = 0 (402 422 194 2)); +DATA(insert OID = 0 (402 422 195 3)); +DATA(insert OID = 0 (402 433 193 1)); +DATA(insert OID = 0 (402 433 194 2)); +DATA(insert OID = 0 (402 433 196 3)); +DATA(insert OID = 0 (402 434 197 1)); +DATA(insert OID = 0 (402 434 198 2)); +DATA(insert OID = 0 (402 434 199 3)); +DATA(insert OID = 0 (403 421 350 1)); +DATA(insert OID = 0 (403 423 355 1)); +DATA(insert OID = 0 (403 424 353 1)); +DATA(insert OID = 0 (403 425 352 1)); +DATA(insert OID = 0 (403 426 351 1)); +DATA(insert OID = 0 (403 427 356 1)); +DATA(insert OID = 0 (403 428 354 1)); +DATA(insert OID = 0 (403 429 358 1)); +DATA(insert OID = 0 (403 406 689 1)); +DATA(insert OID = 0 (403 407 690 1)); +DATA(insert OID = 0 (403 408 691 1)); +DATA(insert OID = 0 (403 409 359 1)); +DATA(insert OID = 0 (403 430 374 1)); +DATA(insert OID = 0 (403 431 360 1)); +DATA(insert OID = 0 (403 432 357 1)); +DATA(insert OID = 0 (403 435 928 1)); +DATA(insert OID = 0 (403 436 948 1)); +DATA(insert OID = 0 (403 437 828 1)); +DATA(insert OID = 0 (403 1076 1078 1)); +DATA(insert OID = 0 (403 1077 1079 1)); +DATA(insert OID = 0 (403 1114 1092 1)); +DATA(insert OID = 0 (403 1115 1107 1)); + +BKI_BEGIN +#ifdef NOBTREE +BKI_END +DATA(insert OID = 0 (404 421 350 1)); +DATA(insert OID = 0 (404 423 355 1)); +DATA(insert OID = 0 (404 424 353 1)); +DATA(insert OID = 0 (404 425 352 1)); +DATA(insert OID = 0 (404 426 351 1)); +DATA(insert OID = 0 (404 427 356 1)); +DATA(insert OID = 0 (404 428 354 1)); +DATA(insert OID = 0 (404 429 358 1)); +DATA(insert OID = 0 (404 406 689 1)); +DATA(insert OID = 0 (404 407 690 1)); +DATA(insert OID = 0 (404 408 691 1)); +DATA(insert OID = 0 (404 409 359 1)); +DATA(insert OID = 0 (404 430 374 1)); +DATA(insert OID = 0 (404 431 360 1)); +DATA(insert OID = 0 (404 432 357 1)); +BKI_BEGIN +#endif /* NOBTREE */ +BKI_END + +DATA(insert OID = 0 (405 421 449 1)); +DATA(insert OID = 0 (405 423 452 1)); +DATA(insert OID = 0 (405 426 450 1)); +DATA(insert OID = 0 (405 427 453 1)); +DATA(insert OID = 0 (405 428 451 1)); +DATA(insert OID = 0 (405 429 454 1)); +DATA(insert OID = 0 (405 406 692 1)); +DATA(insert OID = 0 (405 407 693 1)); +DATA(insert OID = 0 (405 408 694 1)); +DATA(insert OID = 0 (405 409 455 1)); +DATA(insert OID = 0 (405 430 499 1)); +DATA(insert OID = 0 (405 431 456 1)); +DATA(insert OID = 0 (405 1076 1080 1)); +DATA(insert OID = 0 (405 1077 1081 1)); + +#endif /* PG_AMPROC_H */ diff --git a/src/backend/catalog/pg_attribute.h b/src/backend/catalog/pg_attribute.h new file mode 100644 index 00000000000..d8133177d52 --- /dev/null +++ b/src/backend/catalog/pg_attribute.h @@ -0,0 +1,512 @@ +/*------------------------------------------------------------------------- + * + * pg_attribute.h-- + * definition of the system "attribute" relation (pg_attribute) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_attribute.h,v 1.1.1.1 1996/07/09 06:21:16 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + * utils/cache/relcache.c requires some hard-coded tuple descriptors + * for some of the system catalogs so if the schema for any of + * these changes, be sure and change the appropriate Schema_xxx + * macros! -cim 2/5/91 + * + * fastgetattr() now uses attcacheoff to cache byte offsets of + * attributes in heap tuples. The data actually stored in + * pg_attribute (-1) indicates no cached value. But when we copy + * these tuples into a tuple descriptor, we may then update attcacheoff + * in the copies. This speeds up the attribute walking process. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_ATTRIBUTE_H +#define PG_ATTRIBUTE_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" +#include "access/attnum.h" + +/* ---------------- + * pg_attribute definition. cpp turns this into + * typedef struct FormData_pg_attribute + * + * If you change the following, make sure you change the structs for + * system attributes in heap.c and index.c also. + * ---------------- + */ +CATALOG(pg_attribute) BOOTSTRAP { + Oid attrelid; + NameData attname; + Oid atttypid; + Oid attdefrel; + int4 attnvals; + Oid atttyparg; /* type arg for arrays/spquel/procs */ + int2 attlen; + int2 attnum; + int2 attbound; + bool attbyval; + bool attcanindex; + Oid attproc; /* spquel? */ + int4 attnelems; + int4 attcacheoff; + bool attisset; + char attalign; /* alignment (c=char, s=short, i=int, d=double) */ +} FormData_pg_attribute; + +/* + * someone should figure out how to do this properly. (The problem is + * the size of the C struct is not the same as the size of the tuple.) + */ +#define ATTRIBUTE_TUPLE_SIZE \ + (offsetof(FormData_pg_attribute,attalign) + sizeof(char)) + +/* ---------------- + * Form_pg_attribute corresponds to a pointer to a tuple with + * the format of pg_attribute relation. + * ---------------- + */ +typedef FormData_pg_attribute *AttributeTupleForm; + +/* ---------------- + * compiler constants for pg_attribute + * ---------------- + */ + +#define Natts_pg_attribute 16 +#define Anum_pg_attribute_attrelid 1 +#define Anum_pg_attribute_attname 2 +#define Anum_pg_attribute_atttypid 3 +#define Anum_pg_attribute_attdefrel 4 +#define Anum_pg_attribute_attnvals 5 +#define Anum_pg_attribute_atttyparg 6 +#define Anum_pg_attribute_attlen 7 +#define Anum_pg_attribute_attnum 8 +#define Anum_pg_attribute_attbound 9 +#define Anum_pg_attribute_attbyval 10 +#define Anum_pg_attribute_attcanindex 11 +#define Anum_pg_attribute_attproc 12 +#define Anum_pg_attribute_attnelems 13 +#define Anum_pg_attribute_attcacheoff 14 +#define Anum_pg_attribute_attisset 15 +#define Anum_pg_attribute_attalign 16 + + +/* ---------------- + * SCHEMA_ macros for declaring hardcoded tuple descriptors. + * these are used in utils/cache/relcache.c + * ---------------- +#define SCHEMA_NAME(x) CppConcat(Name_,x) +#define SCHEMA_DESC(x) CppConcat(Desc_,x) +#define SCHEMA_NATTS(x) CppConcat(Natts_,x) +#define SCHEMA_DEF(x) \ + FormData_pg_attribute \ + SCHEMA_DESC(x) [ SCHEMA_NATTS(x) ] = \ + { \ + CppConcat(Schema_,x) \ + } + */ + +/* ---------------- + * initial contents of pg_attribute + * ---------------- + */ + +/* ---------------- + * pg_type schema + * ---------------- + */ +#define Schema_pg_type \ +{ 71l, {"typname"}, 19l, 71l, 0l, 0l, NAMEDATALEN, 1, 0, '\0', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 71l, {"typowner"}, 26l, 71l, 0l, 0l, 4, 2, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 71l, {"typlen"}, 21l, 71l, 0l, 0l, 2, 3, 0, '\001', '\001', 0l, 0l, -1l, '\0', 's' }, \ +{ 71l, {"typprtlen"}, 21l, 71l, 0l, 0l, 2, 4, 0, '\001', '\001', 0l, 0l, -1l, '\0', 's' }, \ +{ 71l, {"typbyval"}, 16l, 71l, 0l, 0l, 1, 5, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 71l, {"typtype"}, 18l, 71l, 0l, 0l, 1, 6, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 71l, {"typisdefined"}, 16l, 71l, 0l, 0l, 1, 7, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 71l, {"typdelim"}, 18l, 71l, 0l, 0l, 1, 8, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 71l, {"typrelid"}, 26l, 71l, 0l, 0l, 4, 9, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 71l, {"typelem"}, 26l, 71l, 0l, 0l, 4, 10, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 71l, {"typinput"}, 24l, 71l, 0l, 0l, 4, 11, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 71l, {"typoutput"}, 24l, 71l, 0l, 0l, 4, 12, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 71l, {"typreceive"}, 24l, 71l, 0l, 0l, 4, 13, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 71l, {"typsend"}, 24l, 71l, 0l, 0l, 4, 14, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 71l, {"typalign"}, 18l, 71l, 0l, 0l, 1, 15, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 71l, {"typdefault"}, 25l, 71l, 0l, 0l, -1, 16, 0, '\0', '\001', 0l, 0l, -1l, '\0', 'i' } + +DATA(insert OID = 0 ( 71 typname 19 0 0 0 NAMEDATALEN 1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 typowner 26 0 0 0 4 2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 typlen 21 0 0 0 2 3 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 71 typprtlen 21 0 0 0 2 4 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 71 typbyval 16 0 0 0 1 5 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 71 typtype 18 0 0 0 1 6 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 71 typisdefined 16 0 0 0 1 7 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 71 typdelim 18 0 0 0 1 8 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 71 typrelid 26 0 0 0 4 9 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 typelem 26 0 0 0 4 10 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 typinput 26 0 0 0 4 11 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 typoutput 26 0 0 0 4 12 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 typreceive 26 0 0 0 4 13 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 typsend 26 0 0 0 4 14 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 typalign 18 0 0 0 1 15 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 71 typdefault 25 0 0 0 -1 16 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 ctid 27 0 0 0 6 -1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 oid 26 0 0 0 4 -2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 xmin 28 0 0 0 4 -3 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 cmin 29 0 0 0 2 -4 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 71 xmax 28 0 0 0 4 -5 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 cmax 29 0 0 0 2 -6 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 71 chain 27 0 0 0 6 -7 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 anchor 27 0 0 0 6 -8 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 tmax 702 0 0 0 4 -9 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 tmin 702 0 0 0 4 -10 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 71 vtype 18 0 0 0 1 -11 0 t t 0 0 -1 f c)); + +/* ---------------- + * pg_database + * ---------------- + */ +DATA(insert OID = 0 ( 88 datname 19 0 0 0 NAMEDATALEN 1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 88 datdba 26 0 0 0 4 2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 88 datpath 25 0 0 0 -1 3 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 88 ctid 27 0 0 0 6 -1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 88 oid 26 0 0 0 4 -2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 88 xmin 28 0 0 0 4 -3 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 88 cmin 29 0 0 0 2 -4 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 88 xmax 28 0 0 0 4 -5 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 88 cmax 29 0 0 0 2 -6 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 88 chain 27 0 0 0 6 -7 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 88 anchor 27 0 0 0 6 -8 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 88 tmax 702 0 0 0 4 -9 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 88 tmin 702 0 0 0 4 -10 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 88 vtype 18 0 0 0 1 -11 0 t t 0 0 -1 f c)); + +/* ---------------- + * pg_demon + * ---------------- + */ +DATA(insert OID = 0 ( 76 demserid 26 0 0 0 4 1 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 76 demname 19 0 0 0 NAMEDATALEN 2 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 76 demowner 26 0 0 0 4 3 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 76 demcode 24 0 0 0 4 4 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 76 ctid 27 0 0 0 6 -1 0 f t 0 0 -1 f i)); + +DATA(insert OID = 0 ( 76 oid 26 0 0 0 4 -2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 76 xmin 28 0 0 0 4 -3 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 76 cmin 29 0 0 0 2 -4 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 76 xmax 28 0 0 0 4 -5 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 76 cmax 29 0 0 0 2 -6 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 76 chain 27 0 0 0 6 -7 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 76 anchor 27 0 0 0 6 -8 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 76 tmax 702 0 0 0 4 -9 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 76 tmin 702 0 0 0 4 -10 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 76 vtype 18 0 0 0 1 -11 0 t t 0 0 -1 f c)); + +/* ---------------- + * pg_proc + * ---------------- + */ +#define Schema_pg_proc \ +{ 81l, {"proname"}, 19l, 81l, 0l, 0l, NAMEDATALEN, 1, 0, '\0', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 81l, {"proowner"}, 26l, 81l, 0l, 0l, 4, 2, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 81l, {"prolang"}, 26l, 81l, 0l, 0l, 4, 3, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 81l, {"proisinh"}, 16l, 81l, 0l, 0l, 1, 4, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 81l, {"proistrusted"}, 16l, 81l, 0l, 0l, 1, 5, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 81l, {"proiscachable"}, 16l, 81l, 0l, 0l, 1, 6, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 81l, {"pronargs"}, 21l, 81l, 0l, 0l, 2, 7, 0, '\001', '\001', 0l, 0l, -1l, '\0', 's' }, \ +{ 81l, {"proretset"}, 16l, 81l, 0l, 0l, 1, 8, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 81l, {"prorettype"}, 26l, 81l, 0l, 0l, 4, 9, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 81l, {"proargtypes"}, 30l, 81l, 0l, 0l, 32, 10, 0, '\0', '\001', 0l, 0l, \ + -1l, '\0', 'i' }, \ +{ 81l, {"probyte_pct"}, 23l, 81l, 0l, 0l, 4, 11, 0, '\001', '\001', 0l, 0l, \ + -1l, '\0', 'i' }, \ +{ 81l, {"properbyte_cpu"}, 23l, 81l, 0l, 0l, 4, 12, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 81l, {"propercall_cpu"}, 23l, 81l, 0l, 0l, 4, 13, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 81l, {"prooutin_ratio"}, 23l, 81l, 0l, 0l, 4, 14, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 81l, {"prosrc"}, 25l, 81l, 0l, 0l, -1, 15, 0, '\0', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 81l, {"probin"}, 17l, 81l, 0l, 0l, -1, 16, 0, '\0', '\001', 0l, 0l, -1l, '\0', 'i' } + +DATA(insert OID = 0 ( 81 proname 19 0 0 0 NAMEDATALEN 1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 proowner 26 0 0 0 4 2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 prolang 26 0 0 0 4 3 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 proisinh 16 0 0 0 1 4 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 81 proistrusted 16 0 0 0 1 5 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 81 proiscachable 16 0 0 0 1 6 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 81 pronargs 21 0 0 0 2 7 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 81 proretset 16 0 0 0 1 8 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 81 prorettype 26 0 0 0 4 9 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 proargtypes 30 0 0 0 32 10 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 probyte_pct 23 0 0 0 4 11 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 properbyte_cpu 23 0 0 0 4 12 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 propercall_cpu 23 0 0 0 4 13 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 prooutin_ratio 23 0 0 0 4 14 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 prosrc 25 0 0 0 -1 15 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 probin 17 0 0 0 -1 16 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 ctid 27 0 0 0 6 -1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 oid 26 0 0 0 4 -2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 xmin 28 0 0 0 4 -3 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 cmin 29 0 0 0 2 -4 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 81 xmax 28 0 0 0 4 -5 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 cmax 29 0 0 0 2 -6 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 81 chain 27 0 0 0 6 -7 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 anchor 27 0 0 0 6 -8 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 tmax 702 0 0 0 4 -9 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 tmin 702 0 0 0 4 -10 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 81 vtype 18 0 0 0 1 -11 0 t t 0 0 -1 f c)); + +/* ---------------- + * pg_server + * ---------------- + */ +DATA(insert OID = 0 ( 82 sername 19 0 0 0 NAMEDATALEN 1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 82 serpid 21 0 0 0 2 2 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 82 serport 21 0 0 0 2 3 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 82 ctid 27 0 0 0 6 -1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 82 oid 26 0 0 0 4 -2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 82 xmin 28 0 0 0 4 -3 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 82 cmin 29 0 0 0 2 -4 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 82 xmax 28 0 0 0 4 -5 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 82 cmax 29 0 0 0 2 -6 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 82 chain 27 0 0 0 6 -7 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 82 anchor 27 0 0 0 6 -8 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 82 tmax 702 0 0 0 4 -9 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 82 tmin 702 0 0 0 4 -10 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 82 vtype 18 0 0 0 1 -11 0 t t 0 0 -1 f c)); + +/* ---------------- + * pg_user + * ---------------- + */ +DATA(insert OID = 0 ( 86 usename 19 0 0 0 NAMEDATALEN 1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 86 usesysid 23 0 0 0 4 2 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 86 usecreatedb 16 0 0 0 1 3 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 86 usetrace 16 0 0 0 1 4 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 86 usesuper 16 0 0 0 1 5 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 86 usecatupd 16 0 0 0 1 6 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 86 ctid 27 0 0 0 6 -1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 86 oid 26 0 0 0 4 -2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 86 xmin 28 0 0 0 4 -3 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 86 cmin 29 0 0 0 2 -4 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 86 xmax 28 0 0 0 4 -5 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 86 cmax 29 0 0 0 2 -6 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 86 chain 27 0 0 0 6 -7 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 86 anchor 27 0 0 0 6 -8 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 86 tmax 702 0 0 0 4 -9 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 86 tmin 702 0 0 0 4 -10 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 86 vtype 18 0 0 0 1 -11 0 t t 0 0 -1 f c)); + +/* ---------------- + * pg_group + * ---------------- + */ +DATA(insert OID = 0 ( 87 groname 19 0 0 0 NAMEDATALEN 1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 87 grosysid 23 0 0 0 4 2 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 87 grolist 1007 0 0 0 -1 3 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 87 ctid 27 0 0 0 6 -1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 87 oid 26 0 0 0 4 -2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 87 xmin 28 0 0 0 4 -3 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 87 cmin 29 0 0 0 2 -4 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 87 xmax 28 0 0 0 4 -5 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 87 cmax 29 0 0 0 2 -6 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 87 chain 27 0 0 0 6 -7 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 87 anchor 27 0 0 0 6 -8 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 87 tmax 702 0 0 0 4 -9 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 87 tmin 702 0 0 0 4 -10 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 87 vtype 18 0 0 0 1 -11 0 t t 0 0 -1 f c)); + +/* ---------------- + * pg_attribute + * ---------------- + */ +#define Schema_pg_attribute \ +{ 75l, {"attrelid"}, 26l, 75l, 0l, 0l, 4, 1, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 75l, {"attname"}, 19l, 75l, 0l, 0l, NAMEDATALEN, 2, 0, '\0', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 75l, {"atttypid"}, 26l, 75l, 0l, 0l, 4, 3, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 75l, {"attdefrel"}, 26l, 75l, 0l, 0l, 4, 4, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 75l, {"attnvals"}, 23l, 75l, 0l, 0l, 4, 5, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 75l, {"atttyparg"}, 26l, 75l, 0l, 0l, 4, 6, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 75l, {"attlen"}, 21l, 75l, 0l, 0l, 2, 7, 0, '\001', '\001', 0l, 0l, -1l, '\0', 's' }, \ +{ 75l, {"attnum"}, 21l, 75l, 0l, 0l, 2, 8, 0, '\001', '\001', 0l, 0l, -1l, '\0', 's' }, \ +{ 75l, {"attbound"}, 21l, 75l, 0l, 0l, 2, 9, 0, '\001', '\001', 0l, 0l, -1l, '\0', 's' }, \ +{ 75l, {"attbyval"}, 16l, 75l, 0l, 0l, 1, 10, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 75l, {"attcanindex"}, 16l, 75l, 0l, 0l, 1, 11, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 75l, {"attproc"}, 26l, 75l, 0l, 0l, 4, 12, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 75l, {"attnelems"}, 23l, 75l, 0l, 0l, 4, 13, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 75l, {"attcacheoff"}, 23l, 75l, 0l, 0l, 4, 14, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 75l, {"attisset"}, 16l, 75l, 0l, 0l, 1, 15, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 75l, {"attalign"}, 18l, 75l, 0l, 0l, 1, 16, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' } + +DATA(insert OID = 0 ( 75 attrelid 26 0 0 0 4 1 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 attname 19 0 0 0 NAMEDATALEN 2 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 atttypid 26 0 0 0 4 3 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 attdefrel 26 0 0 0 4 4 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 attnvals 23 0 0 0 4 5 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 atttyparg 26 0 0 0 4 6 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 attlen 21 0 0 0 2 7 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 75 attnum 21 0 0 0 2 8 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 75 attbound 21 0 0 0 2 9 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 75 attbyval 16 0 0 0 1 10 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 75 attcanindex 16 0 0 0 1 11 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 75 attproc 26 0 0 0 4 12 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 attnelems 23 0 0 0 4 13 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 attcacheoff 23 0 0 0 4 14 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 attisset 16 0 0 0 1 15 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 75 attalign 18 0 0 0 1 16 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 75 ctid 27 0 0 0 6 -1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 oid 26 0 0 0 4 -2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 xmin 28 0 0 0 4 -3 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 cmin 29 0 0 0 2 -4 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 75 xmax 28 0 0 0 4 -5 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 cmax 29 0 0 0 2 -6 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 75 chain 27 0 0 0 6 -7 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 anchor 27 0 0 0 6 -8 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 tmax 702 0 0 0 4 -9 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 tmin 702 0 0 0 4 -10 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 75 vtype 18 0 0 0 1 -11 0 t t 0 0 -1 f c)); + +/* ---------------- + * pg_class + * ---------------- + */ +#define Schema_pg_class \ +{ 83l, {"relname"}, 19l, 83l, 0l, 0l, NAMEDATALEN, 1, 0, '\0', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 83l, {"reltype"}, 26l, 83l, 0l, 0l, 4, 2, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 83l, {"relowner"}, 26l, 83l, 0l, 0l, 4, 2, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 83l, {"relam"}, 26l, 83l, 0l, 0l, 4, 3, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 83l, {"relpages"}, 23, 83l, 0l, 0l, 4, 4, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 83l, {"reltuples"}, 23, 83l, 0l, 0l, 4, 5, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 83l, {"relexpires"}, 702, 83l, 0l, 0l, 4, 6, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 83l, {"relpreserved"}, 703, 83l, 0l, 0l, 4, 7, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 83l, {"relhasindex"}, 16, 83l, 0l, 0l, 1, 8, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 83l, {"relisshared"}, 16, 83l, 0l, 0l, 1, 9, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 83l, {"relkind"}, 18, 83l, 0l, 0l, 1, 10, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 83l, {"relarch"}, 18, 83l, 0l, 0l, 1, 11, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 83l, {"relnatts"}, 21, 83l, 0l, 0l, 2, 12, 0, '\001', '\001', 0l, 0l, -1l, '\0', 's' }, \ +{ 83l, {"relsmgr"}, 210l, 83l, 0l, 0l, 2, 13, 0, '\001', '\001', 0l, 0l, -1l, '\0', 's' }, \ +{ 83l, {"relkey"}, 22, 83l, 0l, 0l, 16, 14, 0, '\0', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 83l, {"relkeyop"}, 30, 83l, 0l, 0l, 32, 15, 0, '\0', '\001', 0l, 0l, -1l, '\0', 'i' }, \ +{ 83l, {"relhasrules"}, 16, 83l, 0l, 0l, 1, 16, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'c' }, \ +{ 83l, {"relacl"}, 1034l, 83l, 0l, 0l, -1, 17, 0, '\0', '\001', 0l, 0l, -1l, '\0', 'i' } + +DATA(insert OID = 0 ( 83 relname 19 0 0 0 NAMEDATALEN 1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 reltype 26 0 0 0 4 2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 relowner 26 0 0 0 4 2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 relam 26 0 0 0 4 3 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 relpages 23 0 0 0 4 4 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 reltuples 23 0 0 0 4 5 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 relexpires 702 0 0 0 4 6 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 relpreserved 702 0 0 0 4 7 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 relhasindex 16 0 0 0 1 8 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 83 relisshared 16 0 0 0 1 9 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 83 relkind 18 0 0 0 1 10 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 83 relarch 18 0 0 0 1 11 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 83 relnatts 21 0 0 0 2 12 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 83 relsmgr 210 0 0 0 2 13 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 83 relkey 22 0 0 0 16 14 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 relkeyop 30 0 0 0 32 15 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 relhasrules 16 0 0 0 1 16 0 t t 0 0 -1 f c)); +DATA(insert OID = 0 ( 83 relacl 1034 0 0 0 -1 17 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 ctid 27 0 0 0 6 -1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 oid 26 0 0 0 4 -2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 xmin 28 0 0 0 4 -3 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 cmin 29 0 0 0 2 -4 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 83 xmax 28 0 0 0 4 -5 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 cmax 29 0 0 0 2 -6 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 83 chain 27 0 0 0 6 -7 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 anchor 27 0 0 0 6 -8 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 tmax 702 0 0 0 4 -9 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 tmin 702 0 0 0 4 -10 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 83 vtype 18 0 0 0 1 -11 0 t t 0 0 -1 f c)); + +/* ---------------- + * pg_magic + * ---------------- + */ +DATA(insert OID = 0 ( 80 magname 19 0 0 0 NAMEDATALEN 1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 80 magvalue 19 0 0 0 NAMEDATALEN 2 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 80 ctid 27 0 0 0 6 -1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 80 oid 26 0 0 0 4 -2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 80 xmin 28 0 0 0 4 -3 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 80 cmin 29 0 0 0 2 -4 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 80 xmax 28 0 0 0 4 -5 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 80 cmax 29 0 0 0 2 -6 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 80 chain 27 0 0 0 6 -7 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 80 anchor 27 0 0 0 6 -8 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 80 tmax 702 0 0 0 4 -9 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 80 tmin 702 0 0 0 4 -10 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 80 vtype 18 0 0 0 1 -11 0 t t 0 0 -1 f c)); + +/* ---------------- + * pg_defaults + * ---------------- + */ +DATA(insert OID = 0 ( 89 defname 19 0 0 0 NAMEDATALEN 1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 89 defvalue 19 0 0 0 NAMEDATALEN 2 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 89 ctid 27 0 0 0 6 -1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 89 oid 26 0 0 0 4 -2 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 89 xmin 28 0 0 0 4 -3 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 89 cmin 29 0 0 0 2 -4 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 89 xmax 28 0 0 0 4 -5 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 89 cmax 29 0 0 0 2 -6 0 t t 0 0 -1 f s)); +DATA(insert OID = 0 ( 89 chain 27 0 0 0 6 -7 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 89 anchor 27 0 0 0 6 -8 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 89 tmax 702 0 0 0 4 -9 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 89 tmin 702 0 0 0 4 -10 0 t t 0 0 -1 f i)); +DATA(insert OID = 0 ( 89 vtype 18 0 0 0 1 -11 0 t t 0 0 -1 f c)); + + +/* ---------------- + * pg_hosts - this relation is used to store host based authentication + * info + * + * ---------------- + */ +DATA(insert OID = 0 ( 101 dbName 19 0 0 0 NAMEDATALEN 1 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 101 address 25 0 0 0 -1 2 0 f t 0 0 -1 f i)); +DATA(insert OID = 0 ( 101 mask 25 0 0 0 -1 3 0 f t 0 0 -1 f i)); + +/* ---------------- + * pg_variable - this relation is modified by special purpose access + * method code. The following is garbage but is needed + * so that the reldesc code works properly. + * ---------------- + */ +#define Schema_pg_variable \ +{ 90l, {"varfoo"}, 26l, 90l, 0l, 0l, 4, 1, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' } + +DATA(insert OID = 0 ( 90 varfoo 26 0 0 0 4 1 0 t t 0 0 -1 f i)); + +/* ---------------- + * pg_log - this relation is modified by special purpose access + * method code. The following is garbage but is needed + * so that the reldesc code works properly. + * ---------------- + */ +#define Schema_pg_log \ +{ 99l, {"logfoo"}, 26l, 99l, 0l, 0l, 4, 1, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' } + +DATA(insert OID = 0 ( 99 logfoo 26 0 0 0 4 1 0 t t 0 0 -1 f i)); + +/* ---------------- + * pg_time - this relation is modified by special purpose access + * method code. The following is garbage but is needed + * so that the reldesc code works properly. + * ---------------- + */ +#define Schema_pg_time \ +{ 100l, {"timefoo"}, 26l, 100l, 0l, 0l, 4, 1, 0, '\001', '\001', 0l, 0l, -1l, '\0', 'i' } + +DATA(insert OID = 0 ( 100 timefoo 26 0 0 0 4 1 0 t t 0 0 -1 f i)); + +#endif /* PG_ATTRIBUTE_H */ diff --git a/src/backend/catalog/pg_class.h b/src/backend/catalog/pg_class.h new file mode 100644 index 00000000000..b1adb68be47 --- /dev/null +++ b/src/backend/catalog/pg_class.h @@ -0,0 +1,162 @@ +/*------------------------------------------------------------------------- + * + * pg_class.h-- + * definition of the system "relation" relation (pg_class) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_class.h,v 1.1.1.1 1996/07/09 06:21:16 scrappy Exp $ + * + * NOTES + * ``pg_relation'' is being replaced by ``pg_class''. currently + * we are only changing the name in the catalogs but someday the + * code will be changed too. -cim 2/26/90 + * [it finally happens. -ay 11/5/94] + * + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_RELATION_H +#define PG_RELATION_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" +#include "utils/nabstime.h" + +/* ---------------- + * pg_class definition. cpp turns this into + * typedef struct FormData_pg_class + * + * Note: the #if 0, #endif around the BKI_BEGIN.. END block + * below keeps cpp from seeing what is meant for the + * genbki script: pg_relation is now called pg_class, but + * only in the catalogs -cim 2/26/90 + * ---------------- + */ + +/* ---------------- + * This structure is actually variable-length (the last attribute is + * a POSTGRES array). Hence, sizeof(FormData_pg_class) does not + * describe the fixed-length or actual size of the structure. + * FormData_pg_class.relacl may not be correctly aligned, either, + * if aclitem and struct varlena don't align together. Hence, + * you MUST use heap_getattr() to get the relacl field. + * ---------------- + */ +CATALOG(pg_class) BOOTSTRAP { + NameData relname; + Oid reltype; + Oid relowner; + Oid relam; + int4 relpages; + int4 reltuples; + int4 relexpires; /* really used as a abstime, but fudge it for now*/ + int4 relpreserved;/*really used as a reltime, but fudge it for now*/ + bool relhasindex; + bool relisshared; + char relkind; + char relarch; /* 'h' = heavy, 'l' = light, 'n' = no archival*/ + int2 relnatts; + int2 relsmgr; + int28 relkey; /* not used */ + oid8 relkeyop; /* not used */ + bool relhasrules; + aclitem relacl[1]; /* this is here for the catalog */ +} FormData_pg_class; + +#define CLASS_TUPLE_SIZE \ + (offsetof(FormData_pg_class,relhasrules) + sizeof(bool)) + +/* ---------------- + * Form_pg_class corresponds to a pointer to a tuple with + * the format of pg_class relation. + * ---------------- + */ +typedef FormData_pg_class *Form_pg_class; + +/* ---------------- + * compiler constants for pg_class + * ---------------- + */ + +/* ---------------- + * Natts_pg_class_fixed is used to tell routines that insert new + * pg_class tuples (as opposed to replacing old ones) that there's no + * relacl field. + * ---------------- + */ +#define Natts_pg_class_fixed 17 +#define Natts_pg_class 18 +#define Anum_pg_class_relname 1 +#define Anum_pg_class_reltype 2 +#define Anum_pg_class_relowner 3 +#define Anum_pg_class_relam 4 +#define Anum_pg_class_relpages 5 +#define Anum_pg_class_reltuples 6 +#define Anum_pg_class_relexpires 7 +#define Anum_pg_class_relpreserved 8 +#define Anum_pg_class_relhasindex 9 +#define Anum_pg_class_relisshared 10 +#define Anum_pg_class_relkind 11 +#define Anum_pg_class_relarch 12 +#define Anum_pg_class_relnatts 13 +#define Anum_pg_class_relsmgr 14 +#define Anum_pg_class_relkey 15 +#define Anum_pg_class_relkeyop 16 +#define Anum_pg_class_relhasrules 17 +#define Anum_pg_class_relacl 18 + +/* ---------------- + * initial contents of pg_class + * ---------------- + */ + +DATA(insert OID = 71 ( pg_type 71 PGUID 0 0 0 0 0 f f r n 16 0 - - f _null_ )); +DATA(insert OID = 75 ( pg_attribute 75 PGUID 0 0 0 0 0 f f r n 16 0 - - f _null_ )); +DATA(insert OID = 76 ( pg_demon 76 PGUID 0 0 0 0 0 f t r n 4 0 - - f _null_ )); +DATA(insert OID = 80 ( pg_magic 80 PGUID 0 0 0 0 0 f t r n 2 0 - - f _null_ )); +DATA(insert OID = 81 ( pg_proc 81 PGUID 0 0 0 0 0 f f r n 16 0 - - f _null_ )); +DATA(insert OID = 82 ( pg_server 82 PGUID 0 0 0 0 0 f t r n 3 0 - - f _null_ )); +DATA(insert OID = 83 ( pg_class 83 PGUID 0 0 0 0 0 f f r n 17 0 - - f _null_ )); +DATA(insert OID = 86 ( pg_user 86 PGUID 0 0 0 0 0 f t r n 6 0 - - f _null_ )); +DATA(insert OID = 87 ( pg_group 87 PGUID 0 0 0 0 0 f t s n 3 0 - - f _null_ )); +DATA(insert OID = 88 ( pg_database 88 PGUID 0 0 0 0 0 f t r n 3 0 - - f _null_ )); +DATA(insert OID = 89 ( pg_defaults 89 PGUID 0 0 0 0 0 f t r n 2 0 - - f _null_ )); +DATA(insert OID = 90 ( pg_variable 90 PGUID 0 0 0 0 0 f t s n 2 0 - - f _null_ )); +DATA(insert OID = 99 ( pg_log 99 PGUID 0 0 0 0 0 f t s n 1 0 - - f _null_ )); +DATA(insert OID = 100 ( pg_time 100 PGUID 0 0 0 0 0 f t s n 1 0 - - f _null_ )); +DATA(insert OID = 101 ( pg_hosts 101 PGUID 0 0 0 0 0 f t s n 3 0 - - f _null_ )); + +#define RelOid_pg_type 71 +#define RelOid_pg_demon 76 +#define RelOid_pg_attribute 75 +#define RelOid_pg_magic 80 +#define RelOid_pg_proc 81 +#define RelOid_pg_server 82 +#define RelOid_pg_class 83 +#define RelOid_pg_user 86 +#define RelOid_pg_group 87 +#define RelOid_pg_database 88 +#define RelOid_pg_defaults 89 +#define RelOid_pg_variable 90 +#define RelOid_pg_log 99 +#define RelOid_pg_time 100 +#define RelOid_pg_hosts 101 + +#define MAX_SYSTEM_RELOID 101 + +#define RELKIND_INDEX 'i' /* secondary index */ +#define RELKIND_RELATION 'r' /* cataloged heap */ +#define RELKIND_SPECIAL 's' /* special (non-heap) */ +#define RELKIND_UNCATALOGED 'u' /* temporary heap */ + +#endif /* PG_RELATION_H */ diff --git a/src/backend/catalog/pg_database.h b/src/backend/catalog/pg_database.h new file mode 100644 index 00000000000..78a657e8d3b --- /dev/null +++ b/src/backend/catalog/pg_database.h @@ -0,0 +1,57 @@ +/*------------------------------------------------------------------------- + * + * pg_database.h-- + * definition of the system "database" relation (pg_database) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_database.h,v 1.1.1.1 1996/07/09 06:21:16 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_DATABASE_H +#define PG_DATABASE_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_database definition. cpp turns this into + * typedef struct FormData_pg_database + * ---------------- + */ +CATALOG(pg_database) BOOTSTRAP { + NameData datname; + Oid datdba; + text datpath; /* VARIABLE LENGTH FIELD */ +} FormData_pg_database; + +/* ---------------- + * Form_pg_database corresponds to a pointer to a tuple with + * the format of pg_database relation. + * ---------------- + */ +typedef FormData_pg_database *Form_pg_database; + +/* ---------------- + * compiler constants for pg_database + * ---------------- + */ +#define Natts_pg_database 3 +#define Anum_pg_database_datname 1 +#define Anum_pg_database_datdba 2 +#define Anum_pg_database_datpath 3 + + +#endif /* PG_DATABASE_H */ diff --git a/src/backend/catalog/pg_defaults.h b/src/backend/catalog/pg_defaults.h new file mode 100644 index 00000000000..66efb7b3d4b --- /dev/null +++ b/src/backend/catalog/pg_defaults.h @@ -0,0 +1,55 @@ +/*------------------------------------------------------------------------- + * + * pg_defaults.h-- + * definition of the system "defaults" relation (pg_defaults) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_defaults.h,v 1.1.1.1 1996/07/09 06:21:16 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_DEFAULTS_H +#define PG_DEFAULTS_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_defaults definition. cpp turns this into + * typedef struct FormData_pg_defaults + * ---------------- + */ +CATALOG(pg_defaults) BOOTSTRAP { + NameData defname; + NameData defvalue; +} FormData_pg_defaults; + +/* ---------------- + * Form_pg_defaults corresponds to a pointer to a tuple with + * the format of pg_defaults relation. + * ---------------- + */ +typedef FormData_pg_defaults *Form_pg_defaults; + +/* ---------------- + * compiler constants for pg_defaults + * ---------------- + */ +#define Natts_pg_defaults 2 +#define Anum_pg_defaults_defname 1 +#define Anum_pg_defaults_defvalue 2 + + +#endif /* PG_DEFAULTS_H */ diff --git a/src/backend/catalog/pg_demon.h b/src/backend/catalog/pg_demon.h new file mode 100644 index 00000000000..1089f571527 --- /dev/null +++ b/src/backend/catalog/pg_demon.h @@ -0,0 +1,58 @@ +/*------------------------------------------------------------------------- + * + * pg_demon.h-- + * definition of the system "demon" relation (pg_demon) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_demon.h,v 1.1.1.1 1996/07/09 06:21:16 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_DEMON_H +#define PG_DEMON_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_demon definition. cpp turns this into + * typedef struct FormData_pg_demon + * ---------------- + */ +CATALOG(pg_demon) BOOTSTRAP { + Oid demserid; + NameData demname; + Oid demowner; + regproc demcode; +} FormData_pg_demon; + +/* ---------------- + * Form_pg_demon corresponds to a pointer to a tuple with + * the format of pg_demon relation. + * ---------------- + */ +typedef FormData_pg_demon *Form_pg_demon; + +/* ---------------- + * compiler constants for pg_demon + * ---------------- + */ +#define Natts_pg_demon 4 +#define Anum_pg_demon_demserid 1 +#define Anum_pg_demon_demname 2 +#define Anum_pg_demon_demowner 3 +#define Anum_pg_demon_demcode 4 + +#endif /* PG_DEMON_H */ diff --git a/src/backend/catalog/pg_group.h b/src/backend/catalog/pg_group.h new file mode 100644 index 00000000000..76d51bec4b3 --- /dev/null +++ b/src/backend/catalog/pg_group.h @@ -0,0 +1,42 @@ +/*------------------------------------------------------------------------- + * + * pg_group.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_group.h,v 1.1.1.1 1996/07/09 06:21:16 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_GROUP_H +#define PG_GROUP_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +CATALOG(pg_group) BOOTSTRAP { + NameData groname; + int4 grosysid; + int4 grolist[1]; +} FormData_pg_group; +/* VARIABLE LENGTH STRUCTURE */ + +typedef FormData_pg_group *Form_pg_group; + +#define Natts_pg_group 1 +#define Anum_pg_group_groname 1 +#define Anum_pg_group_grosysid 2 +#define Anum_pg_group_grolist 3 + +#endif /* PG_GROUP_H */ diff --git a/src/backend/catalog/pg_hosts.h b/src/backend/catalog/pg_hosts.h new file mode 100644 index 00000000000..3924c264d00 --- /dev/null +++ b/src/backend/catalog/pg_hosts.h @@ -0,0 +1,44 @@ +/*------------------------------------------------------------------------- + * + * pg_hosts.h-- + * + * the pg_hosts system catalog provides host-based access to the + * backend. Only those hosts that are in the pg_hosts + * + * currently, this table is not used, instead file-based host authentication + * is used + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_hosts.h,v 1.1.1.1 1996/07/09 06:21:16 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + *------------------------------------------------------------------------- + */ + +#ifndef PG_HOSTS_H +#define PG_HOSTS_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +CATALOG(pg_hosts) BOOTSTRAP { + NameData dbName; + text address; + text mask; +} FormData_pg_hosts; + +typedef FormData_pg_hosts *Form_pg_hosts; +#define Natts_pg_hosts 3 +#define Anum_pg_hosts_dbName 1 +#define Anum_pg_hosts_address 2 +#define Anum_pg_hosts_mask 3 + +#endif /* PG_HOSTS_H */ diff --git a/src/backend/catalog/pg_index.h b/src/backend/catalog/pg_index.h new file mode 100644 index 00000000000..da75b025bcb --- /dev/null +++ b/src/backend/catalog/pg_index.h @@ -0,0 +1,71 @@ +/*------------------------------------------------------------------------- + * + * pg_index.h-- + * definition of the system "index" relation (pg_index) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_index.h,v 1.1.1.1 1996/07/09 06:21:17 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_INDEX_H +#define PG_INDEX_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_index definition. cpp turns this into + * typedef struct FormData_pg_index. The oid of the index relation + * is stored in indexrelid; the oid of the indexed relation is stored + * in indrelid. + * ---------------- + */ +CATALOG(pg_index) { + Oid indexrelid; + Oid indrelid; + Oid indproc; /* registered procedure for functional index */ + int28 indkey; + oid8 indclass; + bool indisclustered; + bool indisarchived; + text indpred; /* query plan for partial index predicate */ +} FormData_pg_index; + +#define INDEX_MAX_KEYS 8 /* maximum number of keys in an index definition */ + +/* ---------------- + * Form_pg_index corresponds to a pointer to a tuple with + * the format of pg_index relation. + * ---------------- + */ +typedef FormData_pg_index *IndexTupleForm; + +/* ---------------- + * compiler constants for pg_index + * ---------------- + */ +#define Natts_pg_index 8 +#define Anum_pg_index_indexrelid 1 +#define Anum_pg_index_indrelid 2 +#define Anum_pg_index_indproc 3 +#define Anum_pg_index_indkey 4 +#define Anum_pg_index_indclass 5 +#define Anum_pg_index_indisclustered 6 +#define Anum_pg_index_indisarchived 7 +#define Anum_pg_index_indpred 8 + + +#endif /* PG_INDEX_H */ diff --git a/src/backend/catalog/pg_inheritproc.h b/src/backend/catalog/pg_inheritproc.h new file mode 100644 index 00000000000..1527e992868 --- /dev/null +++ b/src/backend/catalog/pg_inheritproc.h @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------- + * + * pg_inheritproc.h-- + * definition of the system "inheritproc" relation (pg_inheritproc) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_inheritproc.h,v 1.1.1.1 1996/07/09 06:21:17 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_INHERITPROC_H +#define PG_INHERITPROC_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_inheritproc definition. cpp turns this into + * typedef struct FormData_pg_inheritproc + * ---------------- + */ +CATALOG(pg_inheritproc) { + NameData inhproname; + Oid inhargrel; + Oid inhdefrel; + Oid inhproc; +} FormData_pg_inheritproc; + +/* ---------------- + * Form_pg_inheritproc corresponds to a pointer to a tuple with + * the format of pg_inheritproc relation. + * ---------------- + */ +typedef FormData_pg_inheritproc *Form_pg_inheritproc; + +/* ---------------- + * compiler constants for pg_inheritproc + * ---------------- + */ +#define Natts_pg_inheritproc 4 +#define Anum_pg_inheritproc_inhproname 1 +#define Anum_pg_inheritproc_inhargrel 2 +#define Anum_pg_inheritproc_inhdefrel 3 +#define Anum_pg_inheritproc_inhproc 4 + + +#endif /* PG_INHERITPROC_H */ diff --git a/src/backend/catalog/pg_inherits.h b/src/backend/catalog/pg_inherits.h new file mode 100644 index 00000000000..1caa1cd0178 --- /dev/null +++ b/src/backend/catalog/pg_inherits.h @@ -0,0 +1,57 @@ +/*------------------------------------------------------------------------- + * + * pg_inherits.h-- + * definition of the system "inherits" relation (pg_inherits) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_inherits.h,v 1.1.1.1 1996/07/09 06:21:17 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_INHERITS_H +#define PG_INHERITS_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_inherits definition. cpp turns this into + * typedef struct FormData_pg_inherits + * ---------------- + */ +CATALOG(pg_inherits) { + Oid inhrel; + Oid inhparent; + int4 inhseqno; +} FormData_pg_inherits; + +/* ---------------- + * Form_pg_inherits corresponds to a pointer to a tuple with + * the format of pg_inherits relation. + * ---------------- + */ +typedef FormData_pg_inherits *InheritsTupleForm; + +/* ---------------- + * compiler constants for pg_inherits + * ---------------- + */ +#define Natts_pg_inherits 3 +#define Anum_pg_inherits_inhrel 1 +#define Anum_pg_inherits_inhparent 2 +#define Anum_pg_inherits_inhseqno 3 + + +#endif /* PG_INHERITS_H */ diff --git a/src/backend/catalog/pg_ipl.h b/src/backend/catalog/pg_ipl.h new file mode 100644 index 00000000000..df90cd42cef --- /dev/null +++ b/src/backend/catalog/pg_ipl.h @@ -0,0 +1,57 @@ +/*------------------------------------------------------------------------- + * + * pg_ipl.h-- + * definition of the system "ipl" relation (pg_ipl) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_ipl.h,v 1.1.1.1 1996/07/09 06:21:17 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_IPL_H +#define PG_IPL_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_ipl definition. cpp turns this into + * typedef struct FormData_pg_ipl + * ---------------- + */ +CATALOG(pg_ipl) { + Oid iplrel; + Oid iplipl; + int4 iplseqno; +} FormData_pg_ipl; + +/* ---------------- + * Form_pg_ipl corresponds to a pointer to a tuple with + * the format of pg_ipl relation. + * ---------------- + */ +typedef FormData_pg_ipl *Form_pg_ipl; + +/* ---------------- + * compiler constants for pg_ipl + * ---------------- + */ +#define Natts_pg_ipl 3 +#define Anum_pg_ipl_iplrel 1 +#define Anum_pg_ipl_iplipl 2 +#define Anum_pg_ipl_iplseqno 3 + + +#endif /* PG_IPL_H */ diff --git a/src/backend/catalog/pg_language.h b/src/backend/catalog/pg_language.h new file mode 100644 index 00000000000..7e5a31af7ae --- /dev/null +++ b/src/backend/catalog/pg_language.h @@ -0,0 +1,75 @@ +/*------------------------------------------------------------------------- + * + * pg_language.h-- + * definition of the system "language" relation (pg_language) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_language.h,v 1.1.1.1 1996/07/09 06:21:17 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_LANGUAGE_H +#define PG_LANGUAGE_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_language definition. cpp turns this into + * typedef struct FormData_pg_language + * ---------------- + */ +CATALOG(pg_language) { + NameData lanname; + text lancompiler; /* VARIABLE LENGTH FIELD */ +} FormData_pg_language; + +/* ---------------- + * Form_pg_language corresponds to a pointer to a tuple with + * the format of pg_language relation. + * ---------------- + */ +typedef FormData_pg_language *Form_pg_language; + +/* ---------------- + * compiler constants for pg_language + * ---------------- + */ +#define Natts_pg_language 2 +#define Anum_pg_language_lanname 1 +#define Anum_pg_language_lancompiler 2 + +/* ---------------- + * initial contents of pg_language + * ---------------- + */ + +DATA(insert OID = 11 ( internal "n/a" )); +#define INTERNALlanguageId 11 +DATA(insert OID = 12 ( lisp "/usr/ucb/liszt" )); +DATA(insert OID = 13 ( "C" "/bin/cc" )); +#define ClanguageId 13 +DATA(insert OID = 14 ( "sql" "postgres")); +#define SQLlanguageId 14 + + +#endif /* PG_LANGUAGE_H */ + + + + + + + diff --git a/src/backend/catalog/pg_listener.h b/src/backend/catalog/pg_listener.h new file mode 100644 index 00000000000..05e077ec53b --- /dev/null +++ b/src/backend/catalog/pg_listener.h @@ -0,0 +1,56 @@ +/*------------------------------------------------------------------------- + * + * pg_listener.h-- + * Asynchronous notification + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_listener.h,v 1.1.1.1 1996/07/09 06:21:17 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_LISTENER_H +#define PG_LISTENER_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------------------------------------------------------- + * pg_listener definition. + * + * cpp turns this into typedef struct FormData_pg_listener + * ---------------------------------------------------------------- + */ + +CATALOG(pg_listener) { + NameData relname; + int4 listenerpid; + int4 notification; +} FormData_pg_listener; + +/* ---------------- + * compiler constants for pg_listener + * ---------------- + */ +#define Natts_pg_listener 3 +#define Anum_pg_listener_relname 1 +#define Anum_pg_listener_pid 2 +#define Anum_pg_listener_notify 3 + +/* ---------------- + * initial contents of pg_listener are NOTHING. + * ---------------- + */ + + +#endif /* PG_LISTENER_H */ diff --git a/src/backend/catalog/pg_log.h b/src/backend/catalog/pg_log.h new file mode 100644 index 00000000000..987825a7769 --- /dev/null +++ b/src/backend/catalog/pg_log.h @@ -0,0 +1,40 @@ +/*------------------------------------------------------------------------- + * + * pg_log.h-- + * the system log relation "pg_log" is not a "heap" relation. + * it is automatically created by the transam/ code and the + * information here is all bogus and is just here to make the + * relcache code happy. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_log.h,v 1.1.1.1 1996/07/09 06:21:17 scrappy Exp $ + * + * NOTES + * The structures and macros used by the transam/ code + * to access pg_log should some day go here -cim 6/18/90 + * + *------------------------------------------------------------------------- + */ +#ifndef PG_LOG_H +#define PG_LOG_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +CATALOG(pg_log) BOOTSTRAP { + Oid logfoo; +} FormData_pg_log; + +typedef FormData_pg_log *Form_pg_log; + +#define Natts_pg_log 1 +#define Anum_pg_log_logfoo 1 + +#endif /* PG_LOG_H */ diff --git a/src/backend/catalog/pg_magic.h b/src/backend/catalog/pg_magic.h new file mode 100644 index 00000000000..c5e0d98491d --- /dev/null +++ b/src/backend/catalog/pg_magic.h @@ -0,0 +1,54 @@ +/*------------------------------------------------------------------------- + * + * pg_magic.h-- + * definition of the system "magic" relation (pg_magic) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_magic.h,v 1.1.1.1 1996/07/09 06:21:17 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_MAGIC_H +#define PG_MAGIC_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_magic definition. cpp turns this into + * typedef struct FormData_pg_magic + * ---------------- + */ +CATALOG(pg_magic) BOOTSTRAP { + NameData magname; + NameData magvalue; +} FormData_pg_magic; + +/* ---------------- + * Form_pg_magic corresponds to a pointer to a tuple with + * the format of pg_magic relation. + * ---------------- + */ +typedef FormData_pg_magic *Form_pg_magic; + +/* ---------------- + * compiler constants for pg_magic + * ---------------- + */ +#define Natts_pg_magic 2 +#define Anum_pg_magic_magname 1 +#define Anum_pg_magic_magvalue 2 + +#endif /* PG_MAGIC_H */ diff --git a/src/backend/catalog/pg_opclass.h b/src/backend/catalog/pg_opclass.h new file mode 100644 index 00000000000..46aecd35c62 --- /dev/null +++ b/src/backend/catalog/pg_opclass.h @@ -0,0 +1,85 @@ +/*------------------------------------------------------------------------- + * + * pg_opclass.h-- + * definition of the system "opclass" relation (pg_opclass) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_opclass.h,v 1.1.1.1 1996/07/09 06:21:17 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_OPCLASS_H +#define PG_OPCLASS_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_opclass definition. cpp turns this into + * typedef struct FormData_pg_opclass + * ---------------- + */ + +CATALOG(pg_opclass) { + NameData opcname; +} FormData_pg_opclass; + +/* ---------------- + * Form_pg_opclass corresponds to a pointer to a tuple with + * the format of pg_opclass relation. + * ---------------- + */ +typedef FormData_pg_opclass *Form_pg_opclass; + +/* ---------------- + * compiler constants for pg_opclass + * ---------------- + */ +#define Natts_pg_opclass 1 +#define Anum_pg_opclass_opcname 1 + +/* ---------------- + * initial contents of pg_opclass + * ---------------- + */ + +DATA(insert OID = 406 ( char2_ops )); +DATA(insert OID = 407 ( char4_ops )); +DATA(insert OID = 408 ( char8_ops )); +DATA(insert OID = 409 ( name_ops )); +DATA(insert OID = 421 ( int2_ops )); +DATA(insert OID = 422 ( box_ops )); +DATA(insert OID = 423 ( float8_ops )); +DATA(insert OID = 424 ( int24_ops )); +DATA(insert OID = 425 ( int42_ops )); +DATA(insert OID = 426 ( int4_ops )); +#define INT4_OPS_OID 426 +DATA(insert OID = 427 ( oid_ops )); +DATA(insert OID = 428 ( float4_ops )); +DATA(insert OID = 429 ( char_ops )); +DATA(insert OID = 430 ( char16_ops )); +DATA(insert OID = 431 ( text_ops )); +DATA(insert OID = 432 ( abstime_ops )); +DATA(insert OID = 433 ( bigbox_ops)); +DATA(insert OID = 434 ( poly_ops)); +DATA(insert OID = 435 ( oidint4_ops)); +DATA(insert OID = 436 ( oidname_ops)); +DATA(insert OID = 437 ( oidint2_ops)); +DATA(insert OID = 1076 ( bpchar_ops)); +DATA(insert OID = 1077 ( varchar_ops)); +DATA(insert OID = 1114 ( date_ops)); +DATA(insert OID = 1115 ( time_ops)); + +#endif /* PG_OPCLASS_H */ diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c new file mode 100644 index 00000000000..27842971299 --- /dev/null +++ b/src/backend/catalog/pg_operator.c @@ -0,0 +1,1077 @@ +/*------------------------------------------------------------------------- + * + * pg_operator.c-- + * routines to support manipulation of the pg_operator relation + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/catalog/pg_operator.c,v 1.1.1.1 1996/07/09 06:21:17 scrappy Exp $ + * + * NOTES + * these routines moved here from commands/define.c and somewhat cleaned up. + * + *------------------------------------------------------------------------- + */ +#include <string.h> +#include "postgres.h" + +#include "access/heapam.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "access/htup.h" +#include "utils/rel.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "parser/catalog_utils.h" + +#include "catalog/catname.h" +#include "utils/syscache.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_proc.h" +#include "storage/bufmgr.h" + +#include "fmgr.h" + +static Oid OperatorGetWithOpenRelation(Relation pg_operator_desc, + char *operatorName, + Oid leftObjectId, + Oid rightObjectId ); +static Oid OperatorGet(char *operatorName, + char *leftTypeName, + char *rightTypeName ); + +static Oid OperatorShellMakeWithOpenRelation(Relation pg_operator_desc, + char *operatorName, + Oid leftObjectId, + Oid rightObjectId ); +static Oid OperatorShellMake(char *operatorName, + char *leftTypeName, + char *rightTypeName ); + +static void OperatorDef(char *operatorName, + int definedOK, + char *leftTypeName, + char *rightTypeName, + char *procedureName, + uint16 precedence, + bool isLeftAssociative, + char *commutatorName, + char *negatorName, + char *restrictionName, + char *oinName, + bool canHash, + char *leftSortName, + char *rightSortName ); +static void OperatorUpd(Oid baseId , Oid commId , Oid negId ); + +/* ---------------------------------------------------------------- + * OperatorGetWithOpenRelation + * + * preforms a scan on pg_operator for an operator tuple + * with given name and left/right type oids. + * ---------------------------------------------------------------- + * pg_operator_desc -- reldesc for pg_operator + * operatorName -- name of operator to fetch + * leftObjectId -- left oid of operator to fetch + * rightObjectId -- right oid of operator to fetch + */ +static Oid +OperatorGetWithOpenRelation(Relation pg_operator_desc, + char *operatorName, + Oid leftObjectId, + Oid rightObjectId) +{ + HeapScanDesc pg_operator_scan; + Oid operatorObjectId; + HeapTuple tup; + + static ScanKeyData opKey[3] = { + { 0, Anum_pg_operator_oprname, NameEqualRegProcedure }, + { 0, Anum_pg_operator_oprleft, ObjectIdEqualRegProcedure }, + { 0, Anum_pg_operator_oprright, ObjectIdEqualRegProcedure }, + }; + + fmgr_info(NameEqualRegProcedure, + &opKey[0].sk_func, &opKey[0].sk_nargs); + fmgr_info(ObjectIdEqualRegProcedure, + &opKey[1].sk_func, &opKey[1].sk_nargs); + fmgr_info(ObjectIdEqualRegProcedure, + &opKey[2].sk_func, &opKey[2].sk_nargs); + + /* ---------------- + * form scan key + * ---------------- + */ + opKey[0].sk_argument = PointerGetDatum(operatorName); + opKey[1].sk_argument = ObjectIdGetDatum(leftObjectId); + opKey[2].sk_argument = ObjectIdGetDatum(rightObjectId); + + /* ---------------- + * begin the scan + * ---------------- + */ + pg_operator_scan = heap_beginscan(pg_operator_desc, + 0, + SelfTimeQual, + 3, + opKey); + + /* ---------------- + * fetch the operator tuple, if it exists, and determine + * the proper return oid value. + * ---------------- + */ + tup = heap_getnext(pg_operator_scan, 0, (Buffer *) 0); + operatorObjectId = HeapTupleIsValid(tup) ? tup->t_oid : InvalidOid; + + /* ---------------- + * close the scan and return the oid. + * ---------------- + */ + heap_endscan(pg_operator_scan); + + return + operatorObjectId; +} + +/* ---------------------------------------------------------------- + * OperatorGet + * + * finds the operator associated with the specified name + * and left and right type names. + * ---------------------------------------------------------------- + */ +static Oid +OperatorGet(char *operatorName, + char *leftTypeName, + char *rightTypeName) +{ + Relation pg_operator_desc; + + Oid operatorObjectId; + Oid leftObjectId = InvalidOid; + Oid rightObjectId = InvalidOid; + bool leftDefined = false; + bool rightDefined = false; + + /* ---------------- + * look up the operator types. + * + * Note: types must be defined before operators + * ---------------- + */ + if (leftTypeName) { + leftObjectId = TypeGet(leftTypeName, &leftDefined); + + if (!OidIsValid(leftObjectId) || !leftDefined) + elog(WARN, "OperatorGet: left type '%s' nonexistent",leftTypeName); + } + + if (rightTypeName) { + rightObjectId = TypeGet(rightTypeName, &rightDefined); + + if (!OidIsValid(rightObjectId) || !rightDefined) + elog(WARN, "OperatorGet: right type '%s' nonexistent", + rightTypeName); + } + + if (!((OidIsValid(leftObjectId) && leftDefined) || + (OidIsValid(rightObjectId) && rightDefined))) + elog(WARN, "OperatorGet: no argument types??"); + + /* ---------------- + * open the pg_operator relation + * ---------------- + */ + pg_operator_desc = heap_openr(OperatorRelationName); + + /* ---------------- + * get the oid for the operator with the appropriate name + * and left/right types. + * ---------------- + */ + operatorObjectId = OperatorGetWithOpenRelation(pg_operator_desc, + operatorName, + leftObjectId, + rightObjectId); + + /* ---------------- + * close the relation and return the operator oid. + * ---------------- + */ + heap_close(pg_operator_desc); + + return + operatorObjectId; +} + +/* ---------------------------------------------------------------- + * OperatorShellMakeWithOpenRelation + * + * ---------------------------------------------------------------- + */ +static Oid +OperatorShellMakeWithOpenRelation(Relation pg_operator_desc, + char *operatorName, + Oid leftObjectId, + Oid rightObjectId) +{ + register int i; + HeapTuple tup; + Datum values[ Natts_pg_operator ]; + char nulls[ Natts_pg_operator ]; + Oid operatorObjectId; + TupleDesc tupDesc; + + /* ---------------- + * initialize our nulls[] and values[] arrays + * ---------------- + */ + for (i = 0; i < Natts_pg_operator; ++i) { + nulls[i] = ' '; + values[i] = (Datum)NULL; /* redundant, but safe */ + } + + /* ---------------- + * initialize values[] with the type name and + * ---------------- + */ + i = 0; + values[i++] = PointerGetDatum(operatorName); + values[i++] = ObjectIdGetDatum(InvalidOid); + values[i++] = (Datum) (uint16) 0; + + values[i++] = (Datum)'b'; /* fill oprkind with a bogus value */ + + values[i++] = (Datum) (bool) 0; + values[i++] = (Datum) (bool) 0; + values[i++] = ObjectIdGetDatum(leftObjectId); /* <-- left oid */ + values[i++] = ObjectIdGetDatum(rightObjectId); /* <-- right oid */ + values[i++] = ObjectIdGetDatum(InvalidOid); + values[i++] = ObjectIdGetDatum(InvalidOid); + values[i++] = ObjectIdGetDatum(InvalidOid); + values[i++] = ObjectIdGetDatum(InvalidOid); + values[i++] = ObjectIdGetDatum(InvalidOid); + values[i++] = ObjectIdGetDatum(InvalidOid); + values[i++] = ObjectIdGetDatum(InvalidOid); + values[i++] = ObjectIdGetDatum(InvalidOid); + + /* ---------------- + * create a new operator tuple + * ---------------- + */ + tupDesc = pg_operator_desc->rd_att; + + tup = heap_formtuple(tupDesc, + values, + nulls); + + /* ---------------- + * insert our "shell" operator tuple and + * close the relation + * ---------------- + */ + heap_insert(pg_operator_desc, tup); + operatorObjectId = tup->t_oid; + + /* ---------------- + * free the tuple and return the operator oid + * ---------------- + */ + pfree(tup); + + return + operatorObjectId; +} + +/* ---------------------------------------------------------------- + * OperatorShellMake + * + * Specify operator name and left and right type names, + * fill an operator struct with this info and NULL's, + * call heap_insert and return the Oid + * to the caller. + * ---------------------------------------------------------------- + */ +static Oid +OperatorShellMake(char *operatorName, + char *leftTypeName, + char *rightTypeName) +{ + Relation pg_operator_desc; + Oid operatorObjectId; + + Oid leftObjectId = InvalidOid; + Oid rightObjectId = InvalidOid; + bool leftDefined = false; + bool rightDefined = false; + + /* ---------------- + * get the left and right type oid's for this operator + * ---------------- + */ + if (leftTypeName) + leftObjectId = TypeGet(leftTypeName, &leftDefined); + + if (rightTypeName) + rightObjectId = TypeGet(rightTypeName, &rightDefined); + + if (!((OidIsValid(leftObjectId) && leftDefined) || + (OidIsValid(rightObjectId) && rightDefined))) + elog(WARN, "OperatorShellMake: no valid argument types??"); + + /* ---------------- + * open pg_operator + * ---------------- + */ + pg_operator_desc = heap_openr(OperatorRelationName); + + /* ---------------- + * add a "shell" operator tuple to the operator relation + * and recover the shell tuple's oid. + * ---------------- + */ + operatorObjectId = + OperatorShellMakeWithOpenRelation(pg_operator_desc, + operatorName, + leftObjectId, + rightObjectId); + /* ---------------- + * close the operator relation and return the oid. + * ---------------- + */ + heap_close(pg_operator_desc); + + return + operatorObjectId; +} + +/* -------------------------------- + * OperatorDef + * + * This routine gets complicated because it allows the user to + * specify operators that do not exist. For example, if operator + * "op" is being defined, the negator operator "negop" and the + * commutator "commop" can also be defined without specifying + * any information other than their names. Since in order to + * add "op" to the PG_OPERATOR catalog, all the Oid's for these + * operators must be placed in the fields of "op", a forward + * declaration is done on the commutator and negator operators. + * This is called creating a shell, and its main effect is to + * create a tuple in the PG_OPERATOR catalog with minimal + * information about the operator (just its name and types). + * Forward declaration is used only for this purpose, it is + * not available to the user as it is for type definition. + * + * Algorithm: + * + * check if operator already defined + * if so issue error if not definedOk, this is a duplicate + * but if definedOk, save the Oid -- filling in a shell + * get the attribute types from relation descriptor for pg_operator + * assign values to the fields of the operator: + * operatorName + * owner id (simply the user id of the caller) + * precedence + * operator "kind" either "b" for binary or "l" for left unary + * isLeftAssociative boolean + * canHash boolean + * leftTypeObjectId -- type must already be defined + * rightTypeObjectId -- this is optional, enter ObjectId=0 if none specified + * resultType -- defer this, since it must be determined from + * the pg_procedure catalog + * commutatorObjectId -- if this is NULL, enter ObjectId=0 + * else if this already exists, enter it's ObjectId + * else if this does not yet exist, and is not + * the same as the main operatorName, then create + * a shell and enter the new ObjectId + * else if this does not exist but IS the same + * name as the main operator, set the ObjectId=0. + * Later OperatorCreate will make another call + * to OperatorDef which will cause this field + * to be filled in (because even though the names + * will be switched, they are the same name and + * at this point this ObjectId will then be defined) + * negatorObjectId -- same as for commutatorObjectId + * leftSortObjectId -- same as for commutatorObjectId + * rightSortObjectId -- same as for commutatorObjectId + * operatorProcedure -- must access the pg_procedure catalog to get the + * ObjectId of the procedure that actually does the operator + * actions this is required. Do an amgetattr to find out the + * return type of the procedure + * restrictionProcedure -- must access the pg_procedure catalog to get + * the ObjectId but this is optional + * joinProcedure -- same as restrictionProcedure + * now either insert or replace the operator into the pg_operator catalog + * if the operator shell is being filled in + * access the catalog in order to get a valid buffer + * create a tuple using ModifyHeapTuple + * get the t_ctid from the modified tuple and call RelationReplaceHeapTuple + * else if a new operator is being created + * create a tuple using heap_formtuple + * call heap_insert + * -------------------------------- + * "X" indicates an optional argument (i.e. one that can be NULL) + * operatorName; -- operator name + * definedOK; -- operator can already have an oid? + * leftTypeName; -- X left type name + * rightTypeName; -- X right type name + * procedureName; -- procedure oid for operator code + * precedence; -- operator precedence + * isLeftAssociative; -- operator is left associative? + * commutatorName; -- X commutator operator name + * negatorName; -- X negator operator name + * restrictionName; -- X restriction sel. procedure name + * joinName; -- X join sel. procedure name + * canHash; -- possible hash operator? + * leftSortName; -- X left sort operator + * rightSortName; -- X right sort operator + */ +static void +OperatorDef(char *operatorName, + int definedOK, + char *leftTypeName, + char *rightTypeName, + char *procedureName, + uint16 precedence, + bool isLeftAssociative, + char *commutatorName, + char *negatorName, + char *restrictionName, + char *joinName, + bool canHash, + char *leftSortName, + char *rightSortName) +{ + register i, j; + Relation pg_operator_desc; + + HeapScanDesc pg_operator_scan; + HeapTuple tup; + Buffer buffer; + ItemPointerData itemPointerData; + char nulls[ Natts_pg_operator ]; + char replaces[ Natts_pg_operator ]; + Datum values[ Natts_pg_operator ]; + Oid other_oid; + Oid operatorObjectId; + Oid leftTypeId = InvalidOid; + Oid rightTypeId = InvalidOid; + Oid commutatorId = InvalidOid; + Oid negatorId = InvalidOid; + bool leftDefined = false; + bool rightDefined = false; + char *name[4]; + Oid typeId[8]; + int nargs; + TupleDesc tupDesc; + + static ScanKeyData opKey[3] = { + { 0, Anum_pg_operator_oprname, NameEqualRegProcedure }, + { 0, Anum_pg_operator_oprleft, ObjectIdEqualRegProcedure }, + { 0, Anum_pg_operator_oprright, ObjectIdEqualRegProcedure }, + }; + + fmgr_info(NameEqualRegProcedure, + &opKey[0].sk_func, &opKey[0].sk_nargs); + fmgr_info(ObjectIdEqualRegProcedure, + &opKey[1].sk_func, &opKey[1].sk_nargs); + fmgr_info(ObjectIdEqualRegProcedure, + &opKey[2].sk_func, &opKey[2].sk_nargs); + + operatorObjectId = OperatorGet(operatorName, + leftTypeName, + rightTypeName); + + if (OidIsValid(operatorObjectId) && !definedOK) + elog(WARN, "OperatorDef: operator \"%-.*s\" already defined", + NAMEDATALEN, operatorName); + + if (leftTypeName) + leftTypeId = TypeGet(leftTypeName, &leftDefined); + + if (rightTypeName) + rightTypeId = TypeGet(rightTypeName, &rightDefined); + + if (!((OidIsValid(leftTypeId && leftDefined)) || + (OidIsValid(rightTypeId && rightDefined)))) + elog(WARN, "OperatorGet: no argument types??"); + + for (i = 0; i < Natts_pg_operator; ++i) { + values[i] = (Datum)NULL; + replaces[i] = 'r'; + nulls[i] = ' '; + } + + /* ---------------- + * Look up registered procedures -- find the return type + * of procedureName to place in "result" field. + * Do this before shells are created so we don't + * have to worry about deleting them later. + * ---------------- + */ + memset(typeId, 0, 8 * sizeof(Oid)); + if (!leftTypeName) { + typeId[0] = rightTypeId; + nargs = 1; + } + else if (!rightTypeName) { + typeId[0] = leftTypeId; + nargs = 1; + } + else { + typeId[0] = leftTypeId; + typeId[1] = rightTypeId; + nargs = 2; + } + tup = SearchSysCacheTuple(PRONAME, + PointerGetDatum(procedureName), + Int32GetDatum(nargs), + PointerGetDatum(typeId), + 0); + + if (!PointerIsValid(tup)) + func_error("OperatorDef", procedureName, nargs, (int*)typeId); + + values[ Anum_pg_operator_oprcode-1 ] = ObjectIdGetDatum(tup->t_oid); + values[ Anum_pg_operator_oprresult-1 ] = + ObjectIdGetDatum(((Form_pg_proc) + GETSTRUCT(tup))->prorettype); + + /* ---------------- + * find restriction + * ---------------- + */ + if (restrictionName) { /* optional */ + memset(typeId, 0, 8 * sizeof(Oid)); + typeId[0] = OIDOID; /* operator OID */ + typeId[1] = OIDOID; /* relation OID */ + typeId[2] = INT2OID; /* attribute number */ + typeId[3] = 0; /* value - can be any type */ + typeId[4] = INT4OID; /* flags - left or right selectivity */ + tup = SearchSysCacheTuple(PRONAME, + PointerGetDatum(restrictionName), + Int32GetDatum(5), + ObjectIdGetDatum(typeId), + 0); + if (!HeapTupleIsValid(tup)) + func_error("OperatorDef", restrictionName, 5, (int*)typeId); + + values[ Anum_pg_operator_oprrest-1 ] = ObjectIdGetDatum(tup->t_oid); + } else + values[ Anum_pg_operator_oprrest-1 ] = ObjectIdGetDatum(InvalidOid); + + /* ---------------- + * find join - only valid for binary operators + * ---------------- + */ + if (joinName) { /* optional */ + memset(typeId, 0, 8 * sizeof(Oid)); + typeId[0] = OIDOID; /* operator OID */ + typeId[1] = OIDOID; /* relation OID 1 */ + typeId[2] = INT2OID; /* attribute number 1 */ + typeId[3] = OIDOID; /* relation OID 2 */ + typeId[4] = INT2OID; /* attribute number 2 */ + + tup = SearchSysCacheTuple(PRONAME, + PointerGetDatum(joinName), + Int32GetDatum(5), + Int32GetDatum(typeId), + 0); + if (!HeapTupleIsValid(tup)) + func_error("OperatorDef", joinName, 5, (int*)typeId); + + values[Anum_pg_operator_oprjoin-1] = ObjectIdGetDatum(tup->t_oid); + } else + values[Anum_pg_operator_oprjoin-1] = ObjectIdGetDatum(InvalidOid); + + /* ---------------- + * set up values in the operator tuple + * ---------------- + */ + i = 0; + values[i++] = PointerGetDatum(operatorName); + values[i++] = Int32GetDatum(GetUserId()); + values[i++] = UInt16GetDatum(precedence); + values[i++] = leftTypeName ? (rightTypeName ? 'b' : 'r') : 'l'; + values[i++] = Int8GetDatum(isLeftAssociative); + values[i++] = Int8GetDatum(canHash); + values[i++] = ObjectIdGetDatum(leftTypeId); + values[i++] = ObjectIdGetDatum(rightTypeId); + + ++i; /* Skip "prorettype", this was done above */ + + /* + * Set up the other operators. If they do not currently exist, + * set up shells in order to get ObjectId's and call OperatorDef + * again later to fill in the shells. + */ + name[0] = commutatorName; + name[1] = negatorName; + name[2] = leftSortName; + name[3] = rightSortName; + + for (j = 0; j < 4; ++j) { + if (name[j]) { + + /* for the commutator, switch order of arguments */ + if (j == 0) { + other_oid = OperatorGet(name[j], rightTypeName,leftTypeName); + commutatorId = other_oid; + } else { + other_oid = OperatorGet(name[j], leftTypeName,rightTypeName); + if (j == 1) + negatorId = other_oid; + } + + if (OidIsValid(other_oid)) /* already in catalogs */ + values[i++] = ObjectIdGetDatum(other_oid); + else if (strcmp(operatorName, name[j]) != 0) { + /* not in catalogs, different from operator */ + + /* for the commutator, switch order of arguments */ + if (j == 0) { + other_oid = OperatorShellMake(name[j], + rightTypeName, + leftTypeName); + } else { + other_oid = OperatorShellMake(name[j], + leftTypeName, + rightTypeName); + } + + if (!OidIsValid(other_oid)) + elog(WARN, + "OperatorDef: can't create operator '%s'", + name[j]); + values[i++] = ObjectIdGetDatum(other_oid); + + } else /* not in catalogs, same as operator ??? */ + values[i++] = ObjectIdGetDatum(InvalidOid); + + } else /* new operator is optional */ + values[i++] = ObjectIdGetDatum(InvalidOid); + } + + /* last three fields were filled in first */ + + /* + * If we are adding to an operator shell, get its t_ctid and a + * buffer. + */ + pg_operator_desc = heap_openr(OperatorRelationName); + + if (operatorObjectId) { + opKey[0].sk_argument = PointerGetDatum(operatorName); + opKey[1].sk_argument = ObjectIdGetDatum(leftTypeId); + opKey[2].sk_argument = ObjectIdGetDatum(rightTypeId); + + pg_operator_scan = heap_beginscan(pg_operator_desc, + 0, + SelfTimeQual, + 3, + opKey); + + tup = heap_getnext(pg_operator_scan, 0, &buffer); + if (HeapTupleIsValid(tup)) { + tup = heap_modifytuple(tup, + buffer, + pg_operator_desc, + values, + nulls, + replaces); + + ItemPointerCopy(&tup->t_ctid, &itemPointerData); + setheapoverride(true); + (void) heap_replace(pg_operator_desc, &itemPointerData, tup); + setheapoverride(false); + } else + elog(WARN, "OperatorDef: no operator %d", other_oid); + + heap_endscan(pg_operator_scan); + + } else { + tupDesc = pg_operator_desc->rd_att; + tup = heap_formtuple(tupDesc, values, nulls); + + heap_insert(pg_operator_desc, tup); + operatorObjectId = tup->t_oid; + } + + heap_close(pg_operator_desc); + + /* + * It's possible that we're creating a skeleton operator here for + * the commute or negate attributes of a real operator. If we are, + * then we're done. If not, we may need to update the negator and + * commutator for this attribute. The reason for this is that the + * user may want to create two operators (say < and >=). When he + * defines <, if he uses >= as the negator or commutator, he won't + * be able to insert it later, since (for some reason) define operator + * defines it for him. So what he does is to define > without a + * negator or commutator. Then he defines >= with < as the negator + * and commutator. As a side effect, this will update the > tuple + * if it has no commutator or negator defined. + * + * Alstublieft, Tom Vijlbrief. + */ + if (!definedOK) + OperatorUpd(operatorObjectId, commutatorId, negatorId); +} + +/* ---------------------------------------------------------------- + * OperatorUpd + * + * For a given operator, look up its negator and commutator operators. + * If they are defined, but their negator and commutator operators + * (respectively) are not, then use the new operator for neg and comm. + * This solves a problem for users who need to insert two new operators + * which are the negator or commutator of each other. + * ---------------------------------------------------------------- + */ +static void +OperatorUpd(Oid baseId, Oid commId, Oid negId) +{ + register i; + Relation pg_operator_desc; + HeapScanDesc pg_operator_scan; + HeapTuple tup; + Buffer buffer; + ItemPointerData itemPointerData; + char nulls[ Natts_pg_operator ]; + char replaces[ Natts_pg_operator ]; + Datum values[ Natts_pg_operator ]; + + static ScanKeyData opKey[1] = { + { 0, ObjectIdAttributeNumber, ObjectIdEqualRegProcedure }, + }; + + fmgr_info(ObjectIdEqualRegProcedure, + &opKey[0].sk_func, &opKey[0].sk_nargs); + + for (i = 0; i < Natts_pg_operator; ++i) { + values[i] = (Datum)NULL; + replaces[i] = ' '; + nulls[i] = ' '; + } + + pg_operator_desc = heap_openr(OperatorRelationName); + + /* check and update the commutator, if necessary */ + opKey[0].sk_argument = ObjectIdGetDatum(commId); + + pg_operator_scan = heap_beginscan(pg_operator_desc, + 0, + SelfTimeQual, + 1, + opKey); + + tup = heap_getnext(pg_operator_scan, 0, &buffer); + + /* if the commutator and negator are the same operator, do one update */ + if (commId == negId) { + if (HeapTupleIsValid(tup)) { + OperatorTupleForm t; + + t = (OperatorTupleForm) GETSTRUCT(tup); + if (!OidIsValid(t->oprcom) + || !OidIsValid(t->oprnegate)) { + + if (!OidIsValid(t->oprnegate)) { + values[Anum_pg_operator_oprnegate - 1] = + ObjectIdGetDatum(baseId); + replaces[ Anum_pg_operator_oprnegate - 1 ] = 'r'; + } + + if (!OidIsValid(t->oprcom)) { + values[Anum_pg_operator_oprcom - 1] = + ObjectIdGetDatum(baseId); + replaces[ Anum_pg_operator_oprcom - 1 ] = 'r'; + } + + tup = heap_modifytuple(tup, + buffer, + pg_operator_desc, + values, + nulls, + replaces); + + ItemPointerCopy(&tup->t_ctid, &itemPointerData); + + setheapoverride(true); + (void) heap_replace(pg_operator_desc, &itemPointerData, tup); + setheapoverride(false); + + } + } + heap_endscan(pg_operator_scan); + + heap_close(pg_operator_desc); + + /* release the buffer properly */ + if (BufferIsValid(buffer)) + ReleaseBuffer(buffer); + + return; + } + + /* if commutator and negator are different, do two updates */ + if (HeapTupleIsValid(tup) && + !(OidIsValid(((OperatorTupleForm) GETSTRUCT(tup))->oprcom))) { + values[ Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(baseId); + replaces[ Anum_pg_operator_oprcom - 1] = 'r'; + tup = heap_modifytuple(tup, + buffer, + pg_operator_desc, + values, + nulls, + replaces); + + ItemPointerCopy(&tup->t_ctid, &itemPointerData); + setheapoverride(true); + (void) heap_replace(pg_operator_desc, &itemPointerData, tup); + setheapoverride(false); + + values[ Anum_pg_operator_oprcom - 1 ] = (Datum)NULL; + replaces[ Anum_pg_operator_oprcom - 1 ] = ' '; + + /* release the buffer properly */ + if (BufferIsValid(buffer)) + ReleaseBuffer(buffer); + + } + + /* check and update the negator, if necessary */ + opKey[0].sk_argument = ObjectIdGetDatum(negId); + + pg_operator_scan = heap_beginscan(pg_operator_desc, + 0, + SelfTimeQual, + 1, + opKey); + + tup = heap_getnext(pg_operator_scan, 0, &buffer); + if (HeapTupleIsValid(tup) && + !(OidIsValid(((OperatorTupleForm) GETSTRUCT(tup))->oprnegate))) { + values[Anum_pg_operator_oprnegate-1] = ObjectIdGetDatum(baseId); + replaces[ Anum_pg_operator_oprnegate - 1 ] = 'r'; + tup = heap_modifytuple(tup, + buffer, + pg_operator_desc, + values, + nulls, + replaces); + + ItemPointerCopy(&tup->t_ctid, &itemPointerData); + + setheapoverride(true); + (void) heap_replace(pg_operator_desc, &itemPointerData, tup); + setheapoverride(false); + } + + /* release the buffer properly */ + if (BufferIsValid(buffer)) + ReleaseBuffer(buffer); + + heap_endscan(pg_operator_scan); + + heap_close(pg_operator_desc); +} + + +/* ---------------------------------------------------------------- + * OperatorCreate + * + * Algorithm: + * + * Since the commutator, negator, leftsortoperator, and rightsortoperator + * can be defined implicitly through OperatorCreate, must check before + * the main operator is added to see if they already exist. If they + * do not already exist, OperatorDef makes a "shell" for each undefined + * one, and then OperatorCreate must call OperatorDef again to fill in + * each shell. All this is necessary in order to get the right ObjectId's + * filled into the right fields. + * + * The "definedOk" flag indicates that OperatorDef can be called on + * the operator even though it already has an entry in the PG_OPERATOR + * relation. This allows shells to be filled in. The user cannot + * forward declare operators, this is strictly an internal capability. + * + * When the shells are filled in by subsequent calls to OperatorDef, + * all the fields are the same as the definition of the original operator + * except that the target operator name and the original operatorName + * are switched. In the case of commutator and negator, special flags + * are set to indicate their status, telling the executor(?) that + * the operands are to be switched, or the outcome of the procedure + * negated. + * + * ************************* NOTE NOTE NOTE ****************************** + * + * If the execution of this utility is interrupted, the pg_operator + * catalog may be left in an inconsistent state. Similarly, if + * something is removed from the pg_operator, pg_type, or pg_procedure + * catalog while this is executing, the results may be inconsistent. + * ---------------------------------------------------------------- + * + * "X" indicates an optional argument (i.e. one that can be NULL) + * operatorName; -- operator name + * leftTypeName; -- X left type name + * rightTypeName; -- X right type name + * procedureName; -- procedure for operator + * precedence; -- operator precedence + * isLeftAssociative; -- operator is left associative + * commutatorName; -- X commutator operator name + * negatorName; -- X negator operator name + * restrictionName; -- X restriction sel. procedure + * joinName; -- X join sel. procedure name + * canHash; -- operator hashes + * leftSortName; -- X left sort operator + * rightSortName; -- X right sort operator + * + */ +void +OperatorCreate(char *operatorName, + char *leftTypeName, + char *rightTypeName, + char *procedureName, + uint16 precedence, + bool isLeftAssociative, + char *commutatorName, + char *negatorName, + char *restrictionName, + char *joinName, + bool canHash, + char *leftSortName, + char *rightSortName) +{ + Oid commObjectId, negObjectId; + Oid leftSortObjectId, rightSortObjectId; + int definedOK; + + if (!leftTypeName && !rightTypeName) + elog(WARN, "OperatorCreate : at least one of leftarg or rightarg must be defined"); + + /* ---------------- + * get the oid's of the operator's associated operators, if possible. + * ---------------- + */ + if (commutatorName) + commObjectId = OperatorGet(commutatorName, /* commute type order */ + rightTypeName, + leftTypeName); + + if (negatorName) + negObjectId = OperatorGet(negatorName, + leftTypeName, + rightTypeName); + + if (leftSortName) + leftSortObjectId = OperatorGet(leftSortName, + leftTypeName, + rightTypeName); + + if (rightSortName) + rightSortObjectId = OperatorGet(rightSortName, + rightTypeName, + leftTypeName); + + /* ---------------- + * Use OperatorDef() to define the specified operator and + * also create shells for the operator's associated operators + * if they don't already exist. + * + * This operator should not be defined yet. + * ---------------- + */ + definedOK = 0; + + OperatorDef(operatorName, + definedOK, + leftTypeName, + rightTypeName, + procedureName, + precedence, + isLeftAssociative, + commutatorName, + negatorName, + restrictionName, + joinName, + canHash, + leftSortName, + rightSortName); + + /* ---------------- + * Now fill in information in the operator's associated + * operators. + * + * These operators should be defined or have shells defined. + * ---------------- + */ + definedOK = 1; + + if (!OidIsValid(commObjectId) && commutatorName) + OperatorDef(commutatorName, + definedOK, + leftTypeName, /* should eventually */ + rightTypeName, /* commute order */ + procedureName, + precedence, + isLeftAssociative, + operatorName, /* commutator */ + negatorName, + restrictionName, + joinName, + canHash, + rightSortName, + leftSortName); + + if (negatorName && !OidIsValid(negObjectId)) + OperatorDef(negatorName, + definedOK, + leftTypeName, + rightTypeName, + procedureName, + precedence, + isLeftAssociative, + commutatorName, + operatorName, /* negator */ + restrictionName, + joinName, + canHash, + leftSortName, + rightSortName); + + if (leftSortName && !OidIsValid(leftSortObjectId)) + OperatorDef(leftSortName, + definedOK, + leftTypeName, + rightTypeName, + procedureName, + precedence, + isLeftAssociative, + commutatorName, + negatorName, + restrictionName, + joinName, + canHash, + operatorName, /* left sort */ + rightSortName); + + if (rightSortName && !OidIsValid(rightSortObjectId)) + OperatorDef(rightSortName, + definedOK, + leftTypeName, + rightTypeName, + procedureName, + precedence, + isLeftAssociative, + commutatorName, + negatorName, + restrictionName, + joinName, + canHash, + leftSortName, + operatorName); /* right sort */ +} diff --git a/src/backend/catalog/pg_operator.h b/src/backend/catalog/pg_operator.h new file mode 100644 index 00000000000..9f9533b2ffc --- /dev/null +++ b/src/backend/catalog/pg_operator.h @@ -0,0 +1,480 @@ +/*------------------------------------------------------------------------- + * + * pg_operator.h-- + * definition of the system "operator" relation (pg_operator) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_operator.h,v 1.1.1.1 1996/07/09 06:21:17 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + * XXX do NOT break up DATA() statements into multiple lines! + * the scripts are not as smart as you might think... + * + *------------------------------------------------------------------------- + */ +#ifndef PG_OPERATOR_H +#define PG_OPERATOR_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_operator definition. cpp turns this into + * typedef struct FormData_pg_operator + * ---------------- + */ +CATALOG(pg_operator) { + NameData oprname; + Oid oprowner; + int2 oprprec; + char oprkind; + bool oprisleft; + bool oprcanhash; + Oid oprleft; + Oid oprright; + Oid oprresult; + Oid oprcom; + Oid oprnegate; + Oid oprlsortop; + Oid oprrsortop; + regproc oprcode; + regproc oprrest; + regproc oprjoin; +} FormData_pg_operator; + +/* ---------------- + * Form_pg_operator corresponds to a pointer to a tuple with + * the format of pg_operator relation. + * ---------------- + */ +typedef FormData_pg_operator *OperatorTupleForm; + +/* ---------------- + * compiler constants for pg_operator + * ---------------- + */ + +#define Natts_pg_operator 16 +#define Anum_pg_operator_oprname 1 +#define Anum_pg_operator_oprowner 2 +#define Anum_pg_operator_oprprec 3 +#define Anum_pg_operator_oprkind 4 +#define Anum_pg_operator_oprisleft 5 +#define Anum_pg_operator_oprcanhash 6 +#define Anum_pg_operator_oprleft 7 +#define Anum_pg_operator_oprright 8 +#define Anum_pg_operator_oprresult 9 +#define Anum_pg_operator_oprcom 10 +#define Anum_pg_operator_oprnegate 11 +#define Anum_pg_operator_oprlsortop 12 +#define Anum_pg_operator_oprrsortop 13 +#define Anum_pg_operator_oprcode 14 +#define Anum_pg_operator_oprrest 15 +#define Anum_pg_operator_oprjoin 16 + +/* ---------------- + * initial contents of pg_operator + * ---------------- + */ + +DATA(insert OID = 85 ( "<>" PGUID 0 b t f 16 16 16 85 91 0 0 boolne neqsel neqjoinsel )); +DATA(insert OID = 91 ( "=" PGUID 0 b t t 16 16 16 91 85 0 0 booleq eqsel eqjoinsel )); +#define BooleanEqualOperator 91 + +DATA(insert OID = 92 ( "=" PGUID 0 b t t 18 18 16 92 630 631 631 chareq eqsel eqjoinsel )); +DATA(insert OID = 93 ( "=" PGUID 0 b t t 19 19 16 93 643 660 660 nameeq eqsel eqjoinsel )); +DATA(insert OID = 94 ( "=" PGUID 0 b t t 21 21 16 94 519 95 95 int2eq eqsel eqjoinsel )); +DATA(insert OID = 95 ( "<" PGUID 0 b t f 21 21 16 520 524 0 0 int2lt intltsel intltjoinsel )); +DATA(insert OID = 96 ( "=" PGUID 0 b t t 23 23 16 96 518 97 97 int4eq eqsel eqjoinsel )); +DATA(insert OID = 97 ( "<" PGUID 0 b t f 23 23 16 521 525 0 0 int4lt intltsel intltjoinsel )); +DATA(insert OID = 98 ( "=" PGUID 0 b t t 25 25 16 98 531 664 664 texteq eqsel eqjoinsel )); +DATA(insert OID = 99 ( "=" PGUID 0 b t t 20 20 16 99 644 645 645 char16eq eqsel eqjoinsel )); +DATA(insert OID = 329 ( "=" PGUID 0 b t t 1000 1000 16 329 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 349 ( "=" PGUID 0 b t t 1001 1001 16 349 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 374 ( "=" PGUID 0 b t t 1002 1002 16 374 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 375 ( "=" PGUID 0 b t t 1003 1003 16 375 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 376 ( "=" PGUID 0 b t t 1004 1004 16 376 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 377 ( "=" PGUID 0 b t t 1005 1005 16 377 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 378 ( "=" PGUID 0 b t t 1006 1006 16 378 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 379 ( "=" PGUID 0 b t t 1007 1007 16 379 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 380 ( "=" PGUID 0 b t t 1008 1008 16 380 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 381 ( "=" PGUID 0 b t t 1009 1009 16 381 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 382 ( "=" PGUID 0 b t t 1028 1028 16 382 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 383 ( "=" PGUID 0 b t t 1010 1010 16 383 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 384 ( "=" PGUID 0 b t t 1011 1011 16 384 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 385 ( "=" PGUID 0 b t t 1012 1012 16 385 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 386 ( "=" PGUID 0 b t t 1013 1013 16 386 0 0 0 array_eq eqsel eqjoinsel )); +/* +DATA(insert OID = 387 ( "=" PGUID 0 b t t 1014 1014 16 387 0 0 0 array_eq eqsel eqjoinsel )); +*/ +DATA(insert OID = 388 ( "=" PGUID 0 b t t 1015 1015 16 388 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 389 ( "=" PGUID 0 b t t 1016 1016 16 389 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 390 ( "=" PGUID 0 b t t 1017 1017 16 390 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 391 ( "=" PGUID 0 b t t 1018 1018 16 391 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 392 ( "=" PGUID 0 b t t 1019 1019 16 392 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 393 ( "=" PGUID 0 b t t 1020 1020 16 393 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 394 ( "=" PGUID 0 b t t 1021 1021 16 394 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 395 ( "=" PGUID 0 b t t 1022 1022 16 395 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 396 ( "=" PGUID 0 b t t 1023 1023 16 396 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 397 ( "=" PGUID 0 b t t 1024 1024 16 397 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 398 ( "=" PGUID 0 b t t 1025 1025 16 398 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 399 ( "=" PGUID 0 b t t 1026 1026 16 399 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 400 ( "=" PGUID 0 b t t 1027 1027 16 400 0 0 0 array_eq eqsel eqjoinsel )); +DATA(insert OID = 401 ( "=" PGUID 0 b t t 1034 1034 16 401 0 0 0 array_eq eqsel eqjoinsel )); + +DATA(insert OID = 412 ( "=" PGUID 0 b t t 409 409 16 412 415 418 418 char2eq eqsel eqjoinsel )); +DATA(insert OID = 413 ( "=" PGUID 0 b t t 410 410 16 413 416 419 419 char4eq eqsel eqjoinsel )); +DATA(insert OID = 414 ( "=" PGUID 0 b t t 411 411 16 414 417 420 420 char8eq eqsel eqjoinsel )); + +DATA(insert OID = 415 ( "<>" PGUID 0 b t f 409 409 16 415 412 0 0 char2ne neqsel neqjoinsel )); +DATA(insert OID = 416 ( "<>" PGUID 0 b t f 410 410 16 416 413 0 0 char4ne neqsel neqjoinsel )); +DATA(insert OID = 417 ( "<>" PGUID 0 b t f 411 411 16 417 414 0 0 char8ne neqsel neqjoinsel )); +DATA(insert OID = 418 ( "<" PGUID 0 b t f 409 409 16 460 463 0 0 char2lt intltsel intltjoinsel )); +DATA(insert OID = 419 ( "<" PGUID 0 b t f 410 410 16 461 464 0 0 char4lt intltsel intltjoinsel )); +DATA(insert OID = 420 ( "<" PGUID 0 b t f 411 411 16 462 465 0 0 char8lt intltsel intltjoinsel )); + +DATA(insert OID = 457 ( "<=" PGUID 0 b t f 409 409 16 463 460 0 0 char2le intltsel intltjoinsel )); +DATA(insert OID = 458 ( "<=" PGUID 0 b t f 410 410 16 464 461 0 0 char4le intltsel intltjoinsel )); +DATA(insert OID = 459 ( "<=" PGUID 0 b t f 411 411 16 465 462 0 0 char8le intltsel intltjoinsel )); +DATA(insert OID = 460 ( ">" PGUID 0 b t f 409 409 16 418 457 0 0 char2gt intltsel intltjoinsel )); +DATA(insert OID = 461 ( ">" PGUID 0 b t f 410 410 16 419 458 0 0 char4gt intltsel intltjoinsel )); +DATA(insert OID = 462 ( ">" PGUID 0 b t f 411 411 16 420 459 0 0 char8gt intltsel intltjoinsel )); +DATA(insert OID = 463 ( ">=" PGUID 0 b t f 409 409 16 457 418 0 0 char2ge intltsel intltjoinsel )); +DATA(insert OID = 464 ( ">=" PGUID 0 b t f 410 410 16 458 418 0 0 char4ge intltsel intltjoinsel )); +DATA(insert OID = 465 ( ">=" PGUID 0 b t f 411 411 16 459 420 0 0 char8ge intltsel intltjoinsel )); + +DATA(insert OID = 485 ( "<<" PGUID 0 b t f 604 604 16 0 0 0 0 poly_left intltsel intltjoinsel )); +DATA(insert OID = 486 ( "&<" PGUID 0 b t f 604 604 16 0 0 0 0 poly_overleft intltsel intltjoinsel )); +DATA(insert OID = 487 ( "&>" PGUID 0 b t f 604 604 16 0 0 0 0 poly_overright intltsel intltjoinsel )); +DATA(insert OID = 488 ( ">>" PGUID 0 b t f 604 604 16 0 0 0 0 poly_right intltsel intltjoinsel )); +DATA(insert OID = 489 ( "@" PGUID 0 b t f 604 604 16 0 0 0 0 poly_contained intltsel intltjoinsel )); +DATA(insert OID = 490 ( "~" PGUID 0 b t f 604 604 16 0 0 0 0 poly_contain intltsel intltjoinsel )); +DATA(insert OID = 491 ( "~=" PGUID 0 b t f 604 604 16 0 0 0 0 poly_same intltsel intltjoinsel )); +DATA(insert OID = 492 ( "&&" PGUID 0 b t f 604 604 16 0 0 0 0 poly_overlap intltsel intltjoinsel )); +DATA(insert OID = 493 ( "<<" PGUID 0 b t f 603 603 16 0 0 0 0 box_left intltsel intltjoinsel )); +DATA(insert OID = 494 ( "&<" PGUID 0 b t f 603 603 16 0 0 0 0 box_overleft intltsel intltjoinsel )); +DATA(insert OID = 495 ( "&>" PGUID 0 b t f 603 603 16 0 0 0 0 box_overright intltsel intltjoinsel )); +DATA(insert OID = 496 ( ">>" PGUID 0 b t f 603 603 16 0 0 0 0 box_right intltsel intltjoinsel )); +DATA(insert OID = 497 ( "@" PGUID 0 b t f 603 603 16 0 0 0 0 box_contained intltsel intltjoinsel )); +DATA(insert OID = 498 ( "~" PGUID 0 b t f 603 603 16 0 0 0 0 box_contain intltsel intltjoinsel )); +DATA(insert OID = 499 ( "~=" PGUID 0 b t f 603 603 16 0 0 0 0 box_same intltsel intltjoinsel )); +DATA(insert OID = 500 ( "&&" PGUID 0 b t f 603 603 16 0 0 0 0 box_overlap intltsel intltjoinsel )); +DATA(insert OID = 501 ( ">=" PGUID 0 b t f 603 603 16 0 0 0 0 box_ge areasel areajoinsel )); +DATA(insert OID = 502 ( ">" PGUID 0 b t f 603 603 16 0 0 0 0 box_gt areasel areajoinsel )); +DATA(insert OID = 503 ( "=" PGUID 0 b t t 603 603 16 0 0 0 0 box_eq areasel areajoinsel )); +DATA(insert OID = 504 ( "<" PGUID 0 b t f 603 603 16 0 0 0 0 box_lt areasel areajoinsel )); +DATA(insert OID = 505 ( "<=" PGUID 0 b t f 603 603 16 0 0 0 0 box_le areasel areajoinsel )); +DATA(insert OID = 506 ( "!^" PGUID 0 b t f 600 600 16 0 0 0 0 point_above intltsel intltjoinsel )); +DATA(insert OID = 507 ( "!<" PGUID 0 b t f 600 600 16 0 0 0 0 point_left intltsel intltjoinsel )); +DATA(insert OID = 508 ( "!>" PGUID 0 b t f 600 600 16 0 0 0 0 point_right intltsel intltjoinsel )); +DATA(insert OID = 509 ( "!|" PGUID 0 b t f 600 600 16 0 0 0 0 point_below intltsel intltjoinsel )); +DATA(insert OID = 510 ( "=|=" PGUID 0 b t f 600 600 16 0 0 0 0 point_eq intltsel intltjoinsel )); +DATA(insert OID = 511 ( "===>" PGUID 0 b t f 600 603 16 0 0 0 0 on_pb intltsel intltjoinsel )); +DATA(insert OID = 512 ( "===`" PGUID 0 b t f 600 602 16 0 0 0 0 on_ppath intltsel intltjoinsel )); +DATA(insert OID = 513 ( "@@" PGUID 0 l t f 0 603 600 0 0 0 0 box_center intltsel intltjoinsel )); +DATA(insert OID = 514 ( "*" PGUID 0 b t f 23 23 23 514 0 0 0 int4mul intltsel intltjoinsel )); +DATA(insert OID = 515 ( "!" PGUID 0 r t f 23 0 23 0 0 0 0 int4fac intltsel intltjoinsel )); +DATA(insert OID = 516 ( "!!" PGUID 0 l t f 0 23 23 0 0 0 0 int4fac intltsel intltjoinsel )); +DATA(insert OID = 517 ( "<===>" PGUID 0 b t f 600 600 23 0 0 0 0 pointdist intltsel intltjoinsel )); +DATA(insert OID = 518 ( "<>" PGUID 0 b t f 23 23 16 518 96 0 0 int4ne neqsel neqjoinsel )); +DATA(insert OID = 519 ( "<>" PGUID 0 b t f 21 21 16 519 94 0 0 int2ne neqsel neqjoinsel )); +DATA(insert OID = 520 ( ">" PGUID 0 b t f 21 21 16 95 0 0 0 int2gt intgtsel intgtjoinsel )); +DATA(insert OID = 521 ( ">" PGUID 0 b t f 23 23 16 97 0 0 0 int4gt intgtsel intgtjoinsel )); +DATA(insert OID = 522 ( "<=" PGUID 0 b t f 21 21 16 524 520 0 0 int2le intltsel intltjoinsel )); +DATA(insert OID = 523 ( "<=" PGUID 0 b t f 23 23 16 525 521 0 0 int4le intltsel intltjoinsel )); +DATA(insert OID = 524 ( ">=" PGUID 0 b t f 21 21 16 522 95 0 0 int2ge intgtsel intgtjoinsel )); +DATA(insert OID = 525 ( ">=" PGUID 0 b t f 23 23 16 523 97 0 0 int4ge intgtsel intgtjoinsel )); +DATA(insert OID = 526 ( "*" PGUID 0 b t f 21 21 21 526 0 0 0 int2mul intltsel intltjoinsel )); +DATA(insert OID = 527 ( "/" PGUID 0 b t f 21 21 21 0 0 0 0 int2div intltsel intltjoinsel )); +DATA(insert OID = 528 ( "/" PGUID 0 b t f 23 23 23 0 0 0 0 int4div intltsel intltjoinsel )); +DATA(insert OID = 529 ( "%" PGUID 0 b t f 21 21 21 6 0 0 0 int2mod intltsel intltjoinsel )); +DATA(insert OID = 530 ( "%" PGUID 0 b t f 23 23 23 6 0 0 0 int4mod intltsel intltjoinsel )); +DATA(insert OID = 531 ( "<>" PGUID 0 b t f 25 25 16 531 98 0 0 textne neqsel neqjoinsel )); +DATA(insert OID = 532 ( "=" PGUID 0 b t t 21 23 16 533 538 95 97 int24eq eqsel eqjoinsel )); +DATA(insert OID = 533 ( "=" PGUID 0 b t t 23 21 16 532 539 97 95 int42eq eqsel eqjoinsel )); +DATA(insert OID = 534 ( "<" PGUID 0 b t f 21 23 16 537 542 0 0 int24lt intltsel intltjoinsel )); +DATA(insert OID = 535 ( "<" PGUID 0 b t f 23 21 16 536 543 0 0 int42lt intltsel intltjoinsel )); +DATA(insert OID = 536 ( ">" PGUID 0 b t f 21 23 16 535 540 0 0 int24gt intgtsel intgtjoinsel )); +DATA(insert OID = 537 ( ">" PGUID 0 b t f 23 21 16 534 541 0 0 int42gt intgtsel intgtjoinsel )); +DATA(insert OID = 538 ( "<>" PGUID 0 b t f 21 23 16 539 532 0 0 int24ne neqsel neqjoinsel )); +DATA(insert OID = 539 ( "<>" PGUID 0 b t f 23 21 16 538 533 0 0 int42ne neqsel neqjoinsel )); +DATA(insert OID = 540 ( "<=" PGUID 0 b t f 21 23 16 543 536 0 0 int24le intltsel intltjoinsel )); +DATA(insert OID = 541 ( "<=" PGUID 0 b t f 23 21 16 542 537 0 0 int42le intltsel intltjoinsel )); +DATA(insert OID = 542 ( ">=" PGUID 0 b t f 21 23 16 541 534 0 0 int24ge intgtsel intgtjoinsel )); +DATA(insert OID = 543 ( ">=" PGUID 0 b t f 23 21 16 540 535 0 0 int42ge intgtsel intgtjoinsel )); +DATA(insert OID = 544 ( "*" PGUID 0 b t f 21 23 23 545 0 0 0 int24mul intltsel intltjoinsel )); +DATA(insert OID = 545 ( "*" PGUID 0 b t f 23 21 23 544 0 0 0 int42mul intltsel intltjoinsel )); +DATA(insert OID = 546 ( "/" PGUID 0 b t f 21 23 23 0 0 0 0 int24div intltsel intltjoinsel )); +DATA(insert OID = 547 ( "/" PGUID 0 b t f 23 21 23 0 0 0 0 int42div intltsel intltjoinsel )); +DATA(insert OID = 548 ( "%" PGUID 0 b t f 21 23 23 6 0 0 0 int24mod intltsel intltjoinsel )); +DATA(insert OID = 549 ( "%" PGUID 0 b t f 23 21 23 6 0 0 0 int42mod intltsel intltjoinsel )); +DATA(insert OID = 550 ( "+" PGUID 0 b t f 21 21 21 550 0 0 0 int2pl intltsel intltjoinsel )); +DATA(insert OID = 551 ( "+" PGUID 0 b t f 23 23 23 551 0 0 0 int4pl intltsel intltjoinsel )); +DATA(insert OID = 552 ( "+" PGUID 0 b t f 21 23 23 553 0 0 0 int24pl intltsel intltjoinsel )); +DATA(insert OID = 553 ( "+" PGUID 0 b t f 23 21 23 552 0 0 0 int42pl intltsel intltjoinsel )); +DATA(insert OID = 554 ( "-" PGUID 0 b t f 21 21 21 0 0 0 0 int2mi intltsel intltjoinsel )); +DATA(insert OID = 555 ( "-" PGUID 0 b t f 23 23 23 0 0 0 0 int4mi intltsel intltjoinsel )); +DATA(insert OID = 556 ( "-" PGUID 0 b t f 21 23 23 0 0 0 0 int24mi intltsel intltjoinsel )); +DATA(insert OID = 557 ( "-" PGUID 0 b t f 23 21 23 0 0 0 0 int42mi intltsel intltjoinsel )); +DATA(insert OID = 558 ( "-" PGUID 0 l t f 0 23 23 0 0 0 0 int4um intltsel intltjoinsel )); +DATA(insert OID = 559 ( "-" PGUID 0 l t f 0 21 21 0 0 0 0 int2um intltsel intltjoinsel )); +DATA(insert OID = 560 ( "=" PGUID 0 b t t 702 702 16 560 561 562 562 abstimeeq eqsel eqjoinsel )); +DATA(insert OID = 561 ( "<>" PGUID 0 b t f 702 702 16 561 560 0 0 abstimene neqsel neqjoinsel )); +DATA(insert OID = 562 ( "<" PGUID 0 b t f 702 702 16 563 565 0 0 abstimelt intltsel intltjoinsel )); +DATA(insert OID = 563 ( ">" PGUID 0 b t f 702 702 16 562 564 0 0 abstimegt intltsel intltjoinsel )); +DATA(insert OID = 564 ( "<=" PGUID 0 b t f 702 702 16 565 563 0 0 abstimele intltsel intltjoinsel )); +DATA(insert OID = 565 ( ">=" PGUID 0 b t f 702 702 16 564 562 0 0 abstimege intltsel intltjoinsel )); +DATA(insert OID = 566 ( "=" PGUID 0 b t t 703 703 16 566 567 568 568 reltimeeq - - )); +DATA(insert OID = 567 ( "<>" PGUID 0 b t f 703 703 16 567 566 0 0 reltimene - - )); +DATA(insert OID = 568 ( "<" PGUID 0 b t f 703 703 16 569 571 0 0 reltimelt - - )); +DATA(insert OID = 569 ( ">" PGUID 0 b t f 703 703 16 568 570 0 0 reltimegt - - )); +DATA(insert OID = 570 ( "<=" PGUID 0 b t f 703 703 16 571 569 0 0 reltimele - - )); +DATA(insert OID = 571 ( ">=" PGUID 0 b t f 703 703 16 570 568 0 0 reltimege - - )); +DATA(insert OID = 572 ( "=" PGUID 0 b t t 704 704 16 572 0 0 0 intervaleq - - )); +DATA(insert OID = 573 ( "<<" PGUID 0 b t f 704 704 16 0 0 0 0 intervalct - - )); +DATA(insert OID = 574 ( "&&" PGUID 0 b t f 704 704 16 0 0 0 0 intervalov - - )); +DATA(insert OID = 575 ( "#=" PGUID 0 b t f 704 703 16 0 576 0 568 intervalleneq - - )); +DATA(insert OID = 576 ( "#<>" PGUID 0 b t f 704 703 16 0 575 0 568 intervallenne - - )); +DATA(insert OID = 577 ( "#<" PGUID 0 b t f 704 703 16 0 580 0 568 intervallenlt - - )); +DATA(insert OID = 578 ( "#>" PGUID 0 b t f 704 703 16 0 579 0 568 intervallengt - - )); +DATA(insert OID = 579 ( "#<=" PGUID 0 b t f 704 703 16 0 578 0 568 intervallenle - - )); +DATA(insert OID = 580 ( "#>=" PGUID 0 b t f 704 703 16 0 577 0 568 intervallenge - - )); +DATA(insert OID = 581 ( "+" PGUID 0 b t f 702 703 702 581 0 0 0 timepl - - )); +DATA(insert OID = 582 ( "-" PGUID 0 b t f 702 703 702 0 0 0 0 timemi - - )); +DATA(insert OID = 583 ( "<?>" PGUID 0 b t f 702 704 16 0 0 562 0 ininterval - - )); +DATA(insert OID = 584 ( "-" PGUID 0 l t f 0 700 700 0 0 0 0 float4um - - )); +DATA(insert OID = 585 ( "-" PGUID 0 l t f 0 701 701 0 0 0 0 float8um - - )); +DATA(insert OID = 586 ( "+" PGUID 0 b t f 700 700 700 586 0 0 0 float4pl - - )); +DATA(insert OID = 587 ( "-" PGUID 0 b t f 700 700 700 0 0 0 0 float4mi - - )); +DATA(insert OID = 588 ( "/" PGUID 0 b t f 700 700 700 0 0 0 0 float4div - - )); +DATA(insert OID = 589 ( "*" PGUID 0 b t f 700 700 700 589 0 0 0 float4mul - - )); +DATA(insert OID = 590 ( "@" PGUID 0 l t f 0 700 700 0 0 0 0 float4abs - - )); +DATA(insert OID = 591 ( "+" PGUID 0 b t f 701 701 701 591 0 0 0 float8pl - - )); +DATA(insert OID = 592 ( "-" PGUID 0 b t f 701 701 701 0 0 0 0 float8mi - - )); +DATA(insert OID = 593 ( "/" PGUID 0 b t f 701 701 701 0 0 0 0 float8div - - )); +DATA(insert OID = 594 ( "*" PGUID 0 b t f 701 701 701 594 0 0 0 float8mul - - )); +DATA(insert OID = 595 ( "@" PGUID 0 l t f 0 701 701 0 0 0 0 float8abs - - )); +DATA(insert OID = 596 ( "|/" PGUID 0 l t f 0 701 701 0 0 0 0 dsqrt - - )); +DATA(insert OID = 597 ( "||/" PGUID 0 l t f 0 701 701 0 0 0 0 dcbrt - - )); +DATA(insert OID = 598 ( "%" PGUID 0 l t f 0 701 701 0 0 0 0 dtrunc - - )); +DATA(insert OID = 599 ( "%" PGUID 0 r t f 701 0 701 0 0 0 0 dround - - )); +DATA(insert OID = 601 ( ":" PGUID 0 l t f 0 701 701 0 0 0 0 dexp - - )); +DATA(insert OID = 602 ( ";" PGUID 0 l t f 0 701 701 0 0 0 0 dlog1 - - )); +DATA(insert OID = 603 ( "|" PGUID 0 l t f 0 704 702 0 0 0 0 intervalstart - - )); +DATA(insert OID = 606 ( "<#>" PGUID 0 b t f 702 702 704 0 0 0 0 mktinterval - - )); +DATA(insert OID = 607 ( "=" PGUID 0 b t t 26 26 16 607 608 97 97 oideq eqsel eqjoinsel )); +#define OIDEqualOperator 607 /* XXX planner/prep/semanopt.c crock */ +DATA(insert OID = 608 ( "<>" PGUID 0 b t f 26 26 16 608 607 0 0 oidne neqsel neqjoinsel )); +DATA(insert OID = 609 ( "<" PGUID 0 b t f 26 26 16 610 612 0 0 int4lt intltsel intltjoinsel )); +DATA(insert OID = 610 ( ">" PGUID 0 b t f 26 26 16 609 611 0 0 int4gt intgtsel intgtjoinsel )); +DATA(insert OID = 611 ( "<=" PGUID 0 b t f 26 26 16 612 610 0 0 int4le intltsel intltjoinsel )); +DATA(insert OID = 612 ( ">=" PGUID 0 b t f 26 26 16 611 609 0 0 int4ge intgtsel intgtjoinsel )); +DATA(insert OID = 620 ( "=" PGUID 0 b t t 700 700 16 620 621 622 622 float4eq eqsel eqjoinsel )); +DATA(insert OID = 621 ( "<>" PGUID 0 b t f 700 700 16 621 620 0 0 float4ne neqsel neqjoinsel )); +DATA(insert OID = 622 ( "<" PGUID 0 b t f 700 700 16 623 625 0 0 float4lt intltsel intltjoinsel )); +DATA(insert OID = 623 ( ">" PGUID 0 b t f 700 700 16 622 624 0 0 float4gt intgtsel intgtjoinsel )); +DATA(insert OID = 624 ( "<=" PGUID 0 b t f 700 700 16 625 623 0 0 float4le intltsel intltjoinsel )); +DATA(insert OID = 625 ( ">=" PGUID 0 b t f 700 700 16 624 622 0 0 float4ge intgtsel intgtjoinsel )); +DATA(insert OID = 626 ( "!!=" PGUID 0 b t f 23 19 16 0 0 0 0 int4notin "-" "-")); +DATA(insert OID = 627 ( "!!=" PGUID 0 b t f 26 19 16 0 0 0 0 oidnotin "-" "-")); +#define OIDNotInOperator 627 /* XXX planner/prep/semanopt.c crock */ +DATA(insert OID = 630 ( "<>" PGUID 0 b t f 18 18 16 630 92 0 0 charne neqsel neqjoinsel )); + +DATA(insert OID = 631 ( "<" PGUID 0 b t f 18 18 16 633 634 0 0 charlt intltsel intltjoinsel )); +DATA(insert OID = 632 ( "<=" PGUID 0 b t f 18 18 16 634 633 0 0 charle intltsel intltjoinsel )); +DATA(insert OID = 633 ( ">" PGUID 0 b t f 18 18 16 631 632 0 0 chargt intltsel intltjoinsel )); +DATA(insert OID = 634 ( ">=" PGUID 0 b t f 18 18 16 632 631 0 0 charge intltsel intltjoinsel )); + +DATA(insert OID = 635 ( "+" PGUID 0 b t f 18 18 18 0 0 0 0 charpl eqsel eqjoinsel )); +DATA(insert OID = 636 ( "-" PGUID 0 b t f 18 18 18 0 0 0 0 charmi eqsel eqjoinsel )); +DATA(insert OID = 637 ( "*" PGUID 0 b t f 18 18 18 0 0 0 0 charmul eqsel eqjoinsel )); +DATA(insert OID = 638 ( "/" PGUID 0 b t f 18 18 18 0 0 0 0 chardiv eqsel eqjoinsel )); + +DATA(insert OID = 639 ( "~" PGUID 0 b t f 19 25 16 0 640 0 0 nameregexeq eqsel eqjoinsel )); +DATA(insert OID = 640 ( "!~" PGUID 0 b t f 19 25 16 0 639 0 0 nameregexne neqsel neqjoinsel )); +DATA(insert OID = 641 ( "~" PGUID 0 b t f 25 25 16 0 642 0 0 textregexeq eqsel eqjoinsel )); +DATA(insert OID = 642 ( "!~" PGUID 0 b t f 25 25 16 0 641 0 0 textregexne eqsel eqjoinsel )); +DATA(insert OID = 643 ( "<>" PGUID 0 b t f 19 19 16 643 93 0 0 namene neqsel neqjoinsel )); +DATA(insert OID = 644 ( "<>" PGUID 0 b t f 20 20 16 644 99 0 0 char16ne neqsel neqjoinsel )); +DATA(insert OID = 645 ( "<" PGUID 0 b t f 20 20 16 647 648 0 0 char16lt intltsel intltjoinsel )); +DATA(insert OID = 646 ( "<=" PGUID 0 b t f 20 20 16 648 647 0 0 char16le intltsel intltjoinsel )); +DATA(insert OID = 647 ( ">" PGUID 0 b t f 20 20 16 645 646 0 0 char16gt intltsel intltjoinsel )); +DATA(insert OID = 648 ( ">=" PGUID 0 b t f 20 20 16 646 645 0 0 char16ge intltsel intltjoinsel )); +DATA(insert OID = 649 ( "~" PGUID 0 b t f 20 25 16 0 650 0 0 char16regexeq intltsel intltjoinsel )); +DATA(insert OID = 650 ( "!~" PGUID 0 b t f 20 25 16 650 0 0 0 char16regexne intltsel intltjoinsel )); +DATA(insert OID = 651 ( "~~" PGUID 0 b t f 20 25 16 0 651 0 0 char16like eqsel eqjoinsel )); +DATA(insert OID = 652 ( "!~~" PGUID 0 b t f 20 25 16 651 0 0 0 char16nlike neqsel neqjoinsel )); + +DATA(insert OID = 660 ( "<" PGUID 0 b t f 19 19 16 662 663 0 0 namelt intltsel intltjoinsel )); +DATA(insert OID = 661 ( "<=" PGUID 0 b t f 19 19 16 663 662 0 0 namele intltsel intltjoinsel )); +DATA(insert OID = 662 ( ">" PGUID 0 b t f 19 19 16 660 661 0 0 namegt intltsel intltjoinsel )); +DATA(insert OID = 663 ( ">=" PGUID 0 b t f 19 19 16 661 660 0 0 namege intltsel intltjoinsel )); +DATA(insert OID = 664 ( "<" PGUID 0 b t f 25 25 16 666 667 0 0 text_lt intltsel intltjoinsel )); +DATA(insert OID = 665 ( "<=" PGUID 0 b t f 25 25 16 667 666 0 0 text_le intltsel intltjoinsel )); +DATA(insert OID = 666 ( ">" PGUID 0 b t f 25 25 16 664 665 0 0 text_gt intltsel intltjoinsel )); +DATA(insert OID = 667 ( ">=" PGUID 0 b t f 25 25 16 665 664 0 0 text_ge intltsel intltjoinsel )); + +DATA(insert OID = 670 ( "=" PGUID 0 b t f 701 701 16 670 671 0 0 float8eq eqsel eqjoinsel )); +DATA(insert OID = 671 ( "<>" PGUID 0 b t f 701 701 16 671 670 0 0 float8ne neqsel neqjoinsel )); +DATA(insert OID = 672 ( "<" PGUID 0 b t f 701 701 16 674 675 0 0 float8lt intltsel intltjoinsel )); +DATA(insert OID = 673 ( "<=" PGUID 0 b t f 701 701 16 675 674 0 0 float8le intltsel intltjoinsel )); +DATA(insert OID = 674 ( ">" PGUID 0 b t f 701 701 16 672 673 0 0 float8gt intltsel intltjoinsel )); +DATA(insert OID = 675 ( ">=" PGUID 0 b t f 701 701 16 673 672 0 0 float8ge intltsel intltjoinsel )); + +DATA(insert OID = 676 ( "<" PGUID 0 b t f 911 911 16 680 679 0 0 oidnamelt intltsel intltjoinsel )); +DATA(insert OID = 677 ( "<=" PGUID 0 b t f 911 911 16 679 680 0 0 oidnamele intltsel intltjoinsel )); +DATA(insert OID = 678 ( "=" PGUID 0 b t f 911 911 16 678 681 0 0 oidnameeq intltsel intltjoinsel )); +DATA(insert OID = 679 ( ">=" PGUID 0 b t f 911 911 16 677 676 0 0 oidnamege intltsel intltjoinsel )); +DATA(insert OID = 680 ( ">" PGUID 0 b t f 911 911 16 676 677 0 0 oidnamegt intltsel intltjoinsel )); +DATA(insert OID = 681 ( "<>" PGUID 0 b t f 911 911 16 681 678 0 0 oidnamene intltsel intltjoinsel )); + +DATA(insert OID = 697 ( "~" PGUID 0 b t f 411 25 16 0 698 0 0 char8regexeq eqsel eqjoinsel )); +DATA(insert OID = 698 ( "!~" PGUID 0 b t f 411 25 16 0 697 0 0 char8regexne neqsel neqjoinsel )); + +DATA(insert OID = 830 ( "<" PGUID 0 b t f 810 810 16 834 833 0 0 oidint2lt intltsel intltjoinsel )); +DATA(insert OID = 831 ( "<=" PGUID 0 b t f 810 810 16 833 834 0 0 oidint2le intltsel intltjoinsel )); +DATA(insert OID = 832 ( "=" PGUID 0 b t f 810 810 16 832 835 0 0 oidint2eq intltsel intltjoinsel )); +DATA(insert OID = 833 ( ">=" PGUID 0 b t f 810 810 16 831 830 0 0 oidint2ge intltsel intltjoinsel )); +DATA(insert OID = 834 ( ">" PGUID 0 b t f 810 810 16 830 831 0 0 oidint2gt intltsel intltjoinsel )); +DATA(insert OID = 835 ( "<>" PGUID 0 b t f 810 810 16 835 832 0 0 oidint2ne intltsel intltjoinsel )); + +DATA(insert OID = 839 ( "~" PGUID 0 b t f 409 25 16 0 841 0 0 char2regexeq eqsel eqjoinsel )); +DATA(insert OID = 841 ( "!~" PGUID 0 b t f 409 25 16 0 839 0 0 char2regexne neqsel neqjoinsel )); +DATA(insert OID = 840 ( "~" PGUID 0 b t f 410 25 16 0 842 0 0 char4regexeq eqsel eqjoinsel )); +DATA(insert OID = 842 ( "!~" PGUID 0 b t f 410 25 16 0 840 0 0 char4regexne neqsel neqjoinsel )); + +DATA(insert OID = 930 ( "<" PGUID 0 b t f 910 910 16 934 933 0 0 oidint4lt intltsel intltjoinsel )); +DATA(insert OID = 931 ( "<=" PGUID 0 b t f 910 910 16 933 934 0 0 oidint4le intltsel intltjoinsel )); +DATA(insert OID = 932 ( "=" PGUID 0 b t f 910 910 16 932 935 0 0 oidint4eq intltsel intltjoinsel )); +DATA(insert OID = 933 ( ">=" PGUID 0 b t f 910 910 16 931 930 0 0 oidint4ge intltsel intltjoinsel )); +DATA(insert OID = 934 ( ">" PGUID 0 b t f 910 910 16 930 931 0 0 oidint4gt intltsel intltjoinsel )); +DATA(insert OID = 935 ( "<>" PGUID 0 b t f 910 910 16 935 932 0 0 oidint4ne intltsel intltjoinsel )); + +DATA(insert OID = 965 ( "^" PGUID 0 b t f 701 701 701 0 0 0 0 dpow - - )); +DATA(insert OID = 966 ( "+" PGUID 0 b t f 1034 1033 1034 0 0 0 0 aclinsert intltsel intltjoinsel )); +DATA(insert OID = 967 ( "-" PGUID 0 b t f 1034 1033 1034 0 0 0 0 aclremove intltsel intltjoinsel )); +DATA(insert OID = 968 ( "~" PGUID 0 b t f 1034 1033 16 0 0 0 0 aclcontains intltsel intltjoinsel )); + +DATA(insert OID = 1054 ( "=" PGUID 0 b t t 1042 1042 16 1054 1057 1058 1058 bpchareq eqsel eqjoinsel )); +DATA(insert OID = 1055 ( "~" PGUID 0 b t f 1042 25 16 0 1056 0 0 textregexeq eqsel eqjoinsel )); +DATA(insert OID = 1056 ( "!~" PGUID 0 b t f 1042 25 16 0 1055 0 0 textregexne neqsel neqjoinsel )); +DATA(insert OID = 1057 ( "<>" PGUID 0 b t f 1042 1042 16 1057 1054 0 0 bpcharne neqsel neqjoinsel )); +DATA(insert OID = 1058 ( "<" PGUID 0 b t f 1042 1042 16 1060 1061 0 0 bpcharlt intltsel intltjoinsel )); +DATA(insert OID = 1059 ( "<=" PGUID 0 b t f 1042 1042 16 1061 1060 0 0 bpcharle intltsel intltjoinsel )); +DATA(insert OID = 1060 ( ">" PGUID 0 b t f 1042 1042 16 1058 1059 0 0 bpchargt intltsel intltjoinsel )); +DATA(insert OID = 1061 ( ">=" PGUID 0 b t f 1042 1042 16 1059 1058 0 0 bpcharge intltsel intltjoinsel )); + +DATA(insert OID = 1062 ( "=" PGUID 0 b t t 1043 1043 16 1062 1065 1066 1066 varchareq eqsel eqjoinsel )); +DATA(insert OID = 1063 ( "~" PGUID 0 b t f 1043 25 16 0 1064 0 0 textregexeq eqsel eqjoinsel )); +DATA(insert OID = 1064 ( "!~" PGUID 0 b t f 1043 25 16 0 1063 0 0 textregexne neqsel neqjoinsel )); +DATA(insert OID = 1065 ( "<>" PGUID 0 b t f 1043 1043 16 1065 1062 0 0 varcharne neqsel neqjoinsel )); +DATA(insert OID = 1066 ( "<" PGUID 0 b t f 1043 1043 16 1068 1069 0 0 varcharlt intltsel intltjoinsel )); +DATA(insert OID = 1067 ( "<=" PGUID 0 b t f 1043 1043 16 1069 1068 0 0 varcharle intltsel intltjoinsel )); +DATA(insert OID = 1068 ( ">" PGUID 0 b t f 1043 1043 16 1066 1067 0 0 varchargt intltsel intltjoinsel )); +DATA(insert OID = 1069 ( ">=" PGUID 0 b t f 1043 1043 16 1067 1066 0 0 varcharge intltsel intltjoinsel )); + +DATA(insert OID = 1093 ( "=" PGUID 0 b t t 1082 1082 16 1093 1094 1095 1095 date_eq eqsel eqjoinsel )); +DATA(insert OID = 1094 ( "<>" PGUID 0 b t f 1082 1082 16 1094 1093 0 0 date_ne neqsel neqjoinsel )); +DATA(insert OID = 1095 ( "<" PGUID 0 b t f 1082 1082 16 1097 1098 0 0 date_lt intltsel intltjoinsel )); +DATA(insert OID = 1096 ( "<=" PGUID 0 b t f 1082 1082 16 1098 1097 0 0 date_le intltsel intltjoinsel )); +DATA(insert OID = 1097 ( ">" PGUID 0 b t f 1082 1082 16 1095 1096 0 0 date_gt intltsel intltjoinsel )); +DATA(insert OID = 1098 ( ">=" PGUID 0 b t f 1082 1082 16 1096 1065 0 0 date_ge intltsel intltjoinsel )); + +DATA(insert OID = 1108 ( "=" PGUID 0 b t t 1083 1083 16 1108 1109 1110 1110 time_eq eqsel eqjoinsel )); +DATA(insert OID = 1109 ( "<>" PGUID 0 b t f 1083 1083 16 1109 1108 0 0 time_ne neqsel neqjoinsel )); +DATA(insert OID = 1110 ( "<" PGUID 0 b t f 1083 1083 16 1112 1113 0 0 time_lt intltsel intltjoinsel )); +DATA(insert OID = 1111 ( "<=" PGUID 0 b t f 1083 1083 16 1113 1112 0 0 time_le intltsel intltjoinsel )); +DATA(insert OID = 1112 ( ">" PGUID 0 b t f 1083 1083 16 1110 1111 0 0 time_gt intltsel intltjoinsel )); +DATA(insert OID = 1113 ( ">=" PGUID 0 b t f 1083 1083 16 1111 1065 0 0 time_ge intltsel intltjoinsel )); + +/* float48 operators */ +DATA(insert OID = 1116 ( "+" PGUID 0 b t f 700 701 701 1116 0 0 0 float48pl - - )); +DATA(insert OID = 1117 ( "-" PGUID 0 b t f 700 701 701 0 0 0 0 float48mi - - )); +DATA(insert OID = 1118 ( "/" PGUID 0 b t f 700 701 701 0 0 0 0 float48div - - )); +DATA(insert OID = 1119 ( "*" PGUID 0 b t f 700 701 701 1119 0 0 0 float48mul - - )); +DATA(insert OID = 1120 ( "=" PGUID 0 b t t 700 701 16 1120 1121 1122 1122 float48eq eqsel eqjoinsel )); +DATA(insert OID = 1121 ( "<>" PGUID 0 b t f 700 701 16 1121 1120 0 0 float48ne neqsel neqjoinsel )); +DATA(insert OID = 1122 ( "<" PGUID 0 b t f 700 701 16 1123 1125 0 0 float48lt intltsel intltjoinsel )); +DATA(insert OID = 1123 ( ">" PGUID 0 b t f 700 701 16 1122 1124 0 0 float48gt intgtsel intgtjoinsel )); +DATA(insert OID = 1124 ( "<=" PGUID 0 b t f 700 701 16 1125 1123 0 0 float48le intltsel intltjoinsel )); +DATA(insert OID = 1125 ( ">=" PGUID 0 b t f 700 701 16 1124 1122 0 0 float48ge intgtsel intgtjoinsel )); + +/* float84 operators */ +DATA(insert OID = 1126 ( "+" PGUID 0 b t f 701 700 701 1126 0 0 0 float84pl - - )); +DATA(insert OID = 1127 ( "-" PGUID 0 b t f 701 700 701 0 0 0 0 float84mi - - )); +DATA(insert OID = 1128 ( "/" PGUID 0 b t f 701 700 701 0 0 0 0 float84div - - )); +DATA(insert OID = 1129 ( "*" PGUID 0 b t f 701 700 701 1129 0 0 0 float84mul - - )); +DATA(insert OID = 1130 ( "=" PGUID 0 b t t 701 700 16 1130 1131 1132 1132 float84eq eqsel eqjoinsel )); +DATA(insert OID = 1131 ( "<>" PGUID 0 b t f 701 700 16 1131 1130 0 0 float84ne neqsel neqjoinsel )); +DATA(insert OID = 1132 ( "<" PGUID 0 b t f 701 700 16 1133 1135 0 0 float84lt intltsel intltjoinsel )); +DATA(insert OID = 1133 ( ">" PGUID 0 b t f 701 700 16 1132 1134 0 0 float84gt intgtsel intgtjoinsel )); +DATA(insert OID = 1134 ( "<=" PGUID 0 b t f 701 700 16 1135 1133 0 0 float84le intltsel intltjoinsel )); +DATA(insert OID = 1135 ( ">=" PGUID 0 b t f 701 700 16 1134 1132 0 0 float84ge intgtsel intgtjoinsel )); + +/* int4 and oid equality */ +DATA(insert OID = 1136 ( "=" PGUID 0 b t t 23 26 16 1137 0 0 0 int4eqoid eqsel eqjoinsel )); +DATA(insert OID = 1137 ( "=" PGUID 0 b t t 26 23 16 1136 0 0 0 oideqint4 eqsel eqjoinsel )); + +/* LIKE hacks by Keith Parks. */ +DATA(insert OID = 1201 ( "~~" PGUID 0 b t f 409 25 16 0 1202 0 0 char2like eqsel eqjoinsel )); +DATA(insert OID = 1202 ( "!~~" PGUID 0 b t f 409 25 16 0 1201 0 0 char2nlike neqsel neqjoinsel )); +DATA(insert OID = 1203 ( "~~" PGUID 0 b t f 410 25 16 0 1204 0 0 char4like eqsel eqjoinsel )); +DATA(insert OID = 1204 ( "!~~" PGUID 0 b t f 410 25 16 0 1203 0 0 char4nlike neqsel neqjoinsel )); +DATA(insert OID = 1205 ( "~~" PGUID 0 b t f 411 25 16 0 1206 0 0 char8like eqsel eqjoinsel )); +DATA(insert OID = 1206 ( "!~~" PGUID 0 b t f 411 25 16 0 1205 0 0 char8nlike neqsel neqjoinsel )); +DATA(insert OID = 1207 ( "~~" PGUID 0 b t f 19 25 16 0 1208 0 0 namelike eqsel eqjoinsel )); +DATA(insert OID = 1208 ( "!~~" PGUID 0 b t f 19 25 16 0 1207 0 0 namenlike neqsel neqjoinsel )); +DATA(insert OID = 1209 ( "~~" PGUID 0 b t f 25 25 16 0 1210 0 0 textlike eqsel eqjoinsel )); +DATA(insert OID = 1210 ( "!~~" PGUID 0 b t f 25 25 16 0 1209 0 0 textnlike neqsel neqjoinsel )); +DATA(insert OID = 1211 ( "~~" PGUID 0 b t f 1042 25 16 0 1212 0 0 textlike eqsel eqjoinsel )); +DATA(insert OID = 1212 ( "!~~" PGUID 0 b t f 1042 25 16 0 1211 0 0 textnlike neqsel neqjoinsel )); +DATA(insert OID = 1213 ( "~~" PGUID 0 b t f 1043 25 16 0 1214 0 0 textlike eqsel eqjoinsel )); +DATA(insert OID = 1214 ( "!~~" PGUID 0 b t f 1043 25 16 0 1213 0 0 textnlike neqsel neqjoinsel )); +DATA(insert OID = 1215 ( "~~" PGUID 0 b t f 20 25 16 0 1216 0 0 char16like eqsel eqjoinsel )); +DATA(insert OID = 1216 ( "!~~" PGUID 0 b t f 20 25 16 0 1215 0 0 char16nlike neqsel neqjoinsel )); + +/* case-insensitive LIKE hacks */ +DATA(insert OID = 1220 ( "~*" PGUID 0 b t f 409 25 16 0 1221 0 0 char2icregexeq eqsel eqjoinsel )); +DATA(insert OID = 1221 ( "!~*" PGUID 0 b t f 409 25 16 0 1220 0 0 char2icregexne neqsel neqjoinsel )); +DATA(insert OID = 1222 ( "~*" PGUID 0 b t f 410 25 16 0 1223 0 0 char4icregexeq eqsel eqjoinsel )); +DATA(insert OID = 1223 ( "!~*" PGUID 0 b t f 410 25 16 0 1222 0 0 char4icregexne neqsel neqjoinsel )); +DATA(insert OID = 1224 ( "~*" PGUID 0 b t f 411 25 16 0 1225 0 0 char8icregexeq eqsel eqjoinsel )); +DATA(insert OID = 1225 ( "!~*" PGUID 0 b t f 411 25 16 0 1224 0 0 char8icregexne neqsel neqjoinsel )); +DATA(insert OID = 1226 ( "~*" PGUID 0 b t f 19 25 16 0 1227 0 0 nameicregexeq eqsel eqjoinsel )); +DATA(insert OID = 1227 ( "!~*" PGUID 0 b t f 19 25 16 0 1226 0 0 nameicregexne neqsel neqjoinsel )); +DATA(insert OID = 1228 ( "~*" PGUID 0 b t f 25 25 16 0 1229 0 0 texticregexeq eqsel eqjoinsel )); +DATA(insert OID = 1229 ( "!~*" PGUID 0 b t f 25 25 16 0 1228 0 0 texticregexne eqsel eqjoinsel )); +DATA(insert OID = 1230 ( "~*" PGUID 0 b t f 20 25 16 0 1231 0 0 char16icregexeq eqsel eqjoinsel )); +DATA(insert OID = 1231 ( "!~*" PGUID 0 b t f 20 25 16 0 1230 0 0 char16icregexne neqsel neqjoinsel )); + + + +/* + * function prototypes + */ +extern void OperatorCreate(char *operatorName, + char *leftTypeName, + char *rightTypeName, + char *procedureName, + uint16 precedence, + bool isLeftAssociative, + char *commutatorName, + char *negatorName, + char *restrictionName, + char *joinName, + bool canHash, + char *leftSortName, + char *rightSortName); + +#endif /* PG_OPERATOR_H */ diff --git a/src/backend/catalog/pg_parg.h b/src/backend/catalog/pg_parg.h new file mode 100644 index 00000000000..aa088278455 --- /dev/null +++ b/src/backend/catalog/pg_parg.h @@ -0,0 +1,116 @@ +/*------------------------------------------------------------------------- + * + * pg_parg.h-- + * definition of the system "parg" relation (pg_parg) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_parg.h,v 1.1.1.1 1996/07/09 06:21:17 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PARG_H +#define PG_PARG_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_parg definition. cpp turns this into + * typedef struct FormData_pg_parg + * ---------------- + */ +CATALOG(pg_parg) { + Oid parproid; + int2 parnum; + char parbound; + Oid partype; +} FormData_pg_parg; + +/* ---------------- + * Form_pg_parg corresponds to a pointer to a tuple with + * the format of pg_parg relation. + * ---------------- + */ +typedef FormData_pg_parg *Form_pg_parg; + +/* ---------------- + * compiler constants for pg_parg + * ---------------- + */ +#define Natts_pg_parg 4 +#define Anum_pg_parg_parproid 1 +#define Anum_pg_parg_parnum 2 +#define Anum_pg_parg_parbound 3 +#define Anum_pg_parg_partype 4 + +/* ---------------- + * initial contents of pg_parg + * ---------------- + */ + +DATA(insert OID = 0 ( 28 1 - 23 )); +DATA(insert OID = 0 ( 29 1 - 16 )); +DATA(insert OID = 0 ( 30 1 - 23 )); +DATA(insert OID = 0 ( 31 1 - 17 )); +DATA(insert OID = 0 ( 32 1 - 23 )); +DATA(insert OID = 0 ( 33 1 - 18 )); +DATA(insert OID = 0 ( 34 1 - 23 )); +DATA(insert OID = 0 ( 35 1 - 19 )); +DATA(insert OID = 0 ( 36 1 - 23 )); +DATA(insert OID = 0 ( 37 1 - 20 )); +DATA(insert OID = 0 ( 38 1 - 23 )); +DATA(insert OID = 0 ( 39 1 - 21 )); +DATA(insert OID = 0 ( 40 1 - 23 )); +DATA(insert OID = 0 ( 41 1 - 22 )); +DATA(insert OID = 0 ( 42 1 - 23 )); +DATA(insert OID = 0 ( 43 1 - 23 )); +DATA(insert OID = 0 ( 44 1 - 23 )); +DATA(insert OID = 0 ( 45 1 - 24 )); +DATA(insert OID = 0 ( 46 1 - 23 )); +DATA(insert OID = 0 ( 47 1 - 25 )); +DATA(insert OID = 0 ( 50 1 - 23 )); +DATA(insert OID = 0 ( 50 2 - 23 )); +DATA(insert OID = 0 ( 50 3 - 23 )); +DATA(insert OID = 0 ( 51 1 - 23 )); +DATA(insert OID = 0 ( 52 1 - 23 )); +DATA(insert OID = 0 ( 52 2 - 23 )); +DATA(insert OID = 0 ( 52 3 - 23 )); +DATA(insert OID = 0 ( 52 4 - 23 )); +DATA(insert OID = 0 ( 53 1 - 23 )); +DATA(insert OID = 0 ( 54 1 - 23 )); +DATA(insert OID = 0 ( 54 2 - 23 )); +DATA(insert OID = 0 ( 55 1 - 23 )); +DATA(insert OID = 0 ( 55 2 - 23 )); +DATA(insert OID = 0 ( 56 1 - 23 )); +DATA(insert OID = 0 ( 56 2 - 23 )); +DATA(insert OID = 0 ( 57 1 - 23 )); +DATA(insert OID = 0 ( 57 2 - 23 )); +DATA(insert OID = 0 ( 57 3 - 23 )); +DATA(insert OID = 0 ( 60 1 - 16 )); +DATA(insert OID = 0 ( 60 2 - 16 )); +DATA(insert OID = 0 ( 61 1 - 18 )); +DATA(insert OID = 0 ( 61 2 - 18 )); +DATA(insert OID = 0 ( 63 1 - 21 )); +DATA(insert OID = 0 ( 63 2 - 21 )); +DATA(insert OID = 0 ( 64 1 - 21 )); +DATA(insert OID = 0 ( 64 2 - 21 )); +DATA(insert OID = 0 ( 65 1 - 23 )); +DATA(insert OID = 0 ( 65 2 - 23 )); +DATA(insert OID = 0 ( 66 1 - 23 )); +DATA(insert OID = 0 ( 66 2 - 23 )); +DATA(insert OID = 0 ( 67 1 - 25 )); +DATA(insert OID = 0 ( 67 2 - 25 )); + +#endif /* PG_PARG_H */ diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c new file mode 100644 index 00000000000..d8273efcce8 --- /dev/null +++ b/src/backend/catalog/pg_proc.c @@ -0,0 +1,265 @@ +/*------------------------------------------------------------------------- + * + * pg_proc.c-- + * routines to support manipulation of the pg_proc relation + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.1.1.1 1996/07/09 06:21:17 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <string.h> +#include "postgres.h" + +#include "access/heapam.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "utils/rel.h" +#include "fmgr.h" +#include "utils/elog.h" +#include "utils/builtins.h" +#include "utils/sets.h" + +#include "nodes/pg_list.h" + +#include "catalog/catname.h" +#include "utils/syscache.h" +#include "catalog/pg_proc.h" +#include "catalog/indexing.h" +#include "tcop/dest.h" +#include "parser/parse_query.h" +#include "tcop/tcopprot.h" +#include "catalog/pg_type.h" +#include "parser/catalog_utils.h" +#include "utils/lsyscache.h" +#include "optimizer/internal.h" +#include "optimizer/planner.h" + +/* ---------------------------------------------------------------- + * ProcedureDefine + * ---------------------------------------------------------------- + */ +Oid +ProcedureCreate(char *procedureName, + bool returnsSet, + char *returnTypeName, + char *languageName, + char *prosrc, + char *probin, + bool canCache, + bool trusted, + int32 byte_pct, + int32 perbyte_cpu, + int32 percall_cpu, + int32 outin_ratio, + List *argList, + CommandDest dest) +{ + register i; + Relation rdesc; + HeapTuple tup; + bool defined; + uint16 parameterCount; + char nulls[ Natts_pg_proc ]; + Datum values[ Natts_pg_proc ]; + Oid languageObjectId; + Oid typeObjectId; + List *x; + QueryTreeList *querytree_list; + List *plan_list; + Oid typev[8]; + Oid relid; + Oid toid; + text *prosrctext; + TupleDesc tupDesc; + + /* ---------------- + * sanity checks + * ---------------- + */ + Assert(PointerIsValid(prosrc)); + Assert(PointerIsValid(probin)); + + parameterCount = 0; + memset(typev, 0, 8 * sizeof(Oid)); + foreach (x, argList) { + Value *t = lfirst(x); + + if (parameterCount == 8) + elog(WARN, "Procedures cannot take more than 8 arguments"); + + if (strcmp(strVal(t), "opaque") == 0) { + if (strcmp(languageName, "sql") == 0) { + elog(WARN, "ProcedureDefine: sql functions cannot take type \"opaque\""); + } + else + toid = 0; + } else { + toid = TypeGet(strVal(t), &defined); + + if (!OidIsValid(toid)) { + elog(WARN, "ProcedureCreate: arg type '%s' is not defined", + strVal(t)); + } + + if (!defined) { + elog(NOTICE, "ProcedureCreate: arg type '%s' is only a shell", + strVal(t)); + } + } + + typev[parameterCount++] = toid; + } + + tup = SearchSysCacheTuple(PRONAME, + PointerGetDatum(procedureName), + UInt16GetDatum(parameterCount), + PointerGetDatum(typev), + 0); + + if (HeapTupleIsValid(tup)) + elog(WARN, "ProcedureCreate: procedure %s already exists with same arguments", + procedureName); + + if (!strcmp(languageName, "sql")) { + /* If this call is defining a set, check if the set is already + * defined by looking to see whether this call's function text + * matches a function already in pg_proc. If so just return the + * OID of the existing set. + */ + if (!strcmp(procedureName, GENERICSETNAME)) { + prosrctext = textin(prosrc); + tup = SearchSysCacheTuple(PROSRC, + PointerGetDatum(prosrctext), + 0,0,0); + if (HeapTupleIsValid(tup)) + return tup->t_oid; + } + } + + tup = SearchSysCacheTuple(LANNAME, + PointerGetDatum(languageName), + 0,0,0); + + if (!HeapTupleIsValid(tup)) + elog(WARN, "ProcedureCreate: no such language %s", + languageName); + + languageObjectId = tup->t_oid; + + if (strcmp(returnTypeName, "opaque") == 0) { + if (strcmp(languageName, "sql") == 0) { + elog(WARN, "ProcedureCreate: sql functions cannot return type \"opaque\""); + } + else + typeObjectId = 0; + } + + else { + typeObjectId = TypeGet(returnTypeName, &defined); + + if (!OidIsValid(typeObjectId)) { + elog(NOTICE, "ProcedureCreate: type '%s' is not yet defined", + returnTypeName); +#if 0 + elog(NOTICE, "ProcedureCreate: creating a shell for type '%s'", + returnTypeName); +#endif + typeObjectId = TypeShellMake(returnTypeName); + if (!OidIsValid(typeObjectId)) { + elog(WARN, "ProcedureCreate: could not create type '%s'", + returnTypeName); + } + } + + else if (!defined) { + elog(NOTICE, "ProcedureCreate: return type '%s' is only a shell", + returnTypeName); + } + } + + /* don't allow functions of complex types that have the same name as + existing attributes of the type */ + if (parameterCount == 1 && + (toid = TypeGet(strVal(lfirst(argList)), &defined)) && + defined && + (relid = typeid_get_relid(toid)) != 0 && + get_attnum(relid, procedureName) != InvalidAttrNumber) + elog(WARN, "method %s already an attribute of type %s", + procedureName, strVal(lfirst(argList))); + + + /* + * If this is a postquel procedure, we parse it here in order to + * be sure that it contains no syntax errors. We should store + * the plan in an Inversion file for use later, but for now, we + * just store the procedure's text in the prosrc attribute. + */ + + if (strcmp(languageName, "sql") == 0) { + plan_list = pg_plan(prosrc, typev, parameterCount, + &querytree_list, dest); + + /* typecheck return value */ + pg_checkretval(typeObjectId, querytree_list); + } + + for (i = 0; i < Natts_pg_proc; ++i) { + nulls[i] = ' '; + values[i] = (Datum)NULL; + } + + i = 0; + values[i++] = PointerGetDatum(procedureName); + values[i++] = Int32GetDatum(GetUserId()); + values[i++] = ObjectIdGetDatum(languageObjectId); + + /* XXX isinherited is always false for now */ + + values[i++] = Int8GetDatum((bool) 0); + + /* XXX istrusted is always false for now */ + + values[i++] = Int8GetDatum(trusted); + values[i++] = Int8GetDatum(canCache); + values[i++] = UInt16GetDatum(parameterCount); + values[i++] = Int8GetDatum(returnsSet); + values[i++] = ObjectIdGetDatum(typeObjectId); + + values[i++] = (Datum) typev; + /* + * The following assignments of constants are made. The real values + * will have to be extracted from the arglist someday soon. + */ + values[i++] = Int32GetDatum(byte_pct); /* probyte_pct */ + values[i++] = Int32GetDatum(perbyte_cpu); /* properbyte_cpu */ + values[i++] = Int32GetDatum(percall_cpu); /* propercall_cpu */ + values[i++] = Int32GetDatum(outin_ratio); /* prooutin_ratio */ + + values[i++] = (Datum)fmgr(TextInRegProcedure, prosrc); /* prosrc */ + values[i++] = (Datum)fmgr(TextInRegProcedure, probin); /* probin */ + + rdesc = heap_openr(ProcedureRelationName); + + tupDesc = rdesc->rd_att; + tup = heap_formtuple(tupDesc, + values, + nulls); + + heap_insert(rdesc, tup); + + if (RelationGetRelationTupleForm(rdesc)->relhasindex) + { + Relation idescs[Num_pg_proc_indices]; + + CatalogOpenIndices(Num_pg_proc_indices, Name_pg_proc_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_proc_indices, rdesc, tup); + CatalogCloseIndices(Num_pg_proc_indices, idescs); + } + heap_close(rdesc); + return tup->t_oid; +} + diff --git a/src/backend/catalog/pg_proc.h b/src/backend/catalog/pg_proc.h new file mode 100644 index 00000000000..f2828394192 --- /dev/null +++ b/src/backend/catalog/pg_proc.h @@ -0,0 +1,769 @@ +/*------------------------------------------------------------------------- + * + * pg_proc.h-- + * definition of the system "procedure" relation (pg_proc) + * along with the relation's initial contents. + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_proc.h,v 1.1.1.1 1996/07/09 06:21:18 scrappy Exp $ + * + * NOTES + * The script catalog/genbki.sh reads this file and generates .bki + * information from the DATA() statements. utils/Gen_fmgrtab.sh + * generates fmgr.h and fmgrtab.c the same way. + * + * XXX do NOT break up DATA() statements into multiple lines! + * the scripts are not as smart as you might think... + * XXX (eg. #if 0 #endif won't do what you think) + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PROC_H +#define PG_PROC_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" +#include "nodes/pg_list.h" +#include "tcop/dest.h" + +/* ---------------- + * pg_proc definition. cpp turns this into + * typedef struct FormData_pg_proc + * ---------------- + */ +CATALOG(pg_proc) BOOTSTRAP { + NameData proname; + Oid proowner; + Oid prolang; + bool proisinh; + bool proistrusted; + bool proiscachable; + int2 pronargs; + bool proretset; + Oid prorettype; + oid8 proargtypes; + int4 probyte_pct; + int4 properbyte_cpu; + int4 propercall_cpu; + int4 prooutin_ratio; + text prosrc; /* VARIABLE LENGTH FIELD */ + bytea probin; /* VARIABLE LENGTH FIELD */ +} FormData_pg_proc; + +/* ---------------- + * Form_pg_proc corresponds to a pointer to a tuple with + * the format of pg_proc relation. + * ---------------- + */ +typedef FormData_pg_proc *Form_pg_proc; + +/* ---------------- + * compiler constants for pg_proc + * ---------------- + */ +#define Natts_pg_proc 16 +#define Anum_pg_proc_proname 1 +#define Anum_pg_proc_proowner 2 +#define Anum_pg_proc_prolang 3 +#define Anum_pg_proc_proisinh 4 +#define Anum_pg_proc_proistrusted 5 +#define Anum_pg_proc_proiscachable 6 +#define Anum_pg_proc_pronargs 7 +#define Anum_pg_proc_proretset 8 +#define Anum_pg_proc_prorettype 9 +#define Anum_pg_proc_proargtypes 10 +#define Anum_pg_proc_probyte_pct 11 +#define Anum_pg_proc_properbyte_cpu 12 +#define Anum_pg_proc_propercall_cpu 13 +#define Anum_pg_proc_prooutin_ratio 14 +#define Anum_pg_proc_prosrc 15 +#define Anum_pg_proc_probin 16 + +/* ---------------- + * initial contents of pg_proc + * ---------------- + */ + +/* keep the following ordered by OID so that later changes can be made easier*/ + +/* OIDS 1 - 99 */ +DATA(insert OID = 28 ( boolin PGUID 11 f t f 1 f 16 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 29 ( boolout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 30 ( byteain PGUID 11 f t f 1 f 17 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 31 ( byteaout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 32 ( charin PGUID 11 f t f 1 f 18 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 33 ( charout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 34 ( namein PGUID 11 f t f 1 f 19 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 35 ( nameout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 36 ( char16in PGUID 11 f t f 1 f 19 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 37 ( char16out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 38 ( int2in PGUID 11 f t f 1 f 21 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 39 ( int2out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 40 ( int28in PGUID 11 f t f 1 f 22 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 41 ( int28out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 42 ( int4in PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 43 ( int4out PGUID 11 f t f 1 f 19 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 44 ( regprocin PGUID 11 f t f 1 f 24 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 45 ( regprocout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 46 ( textin PGUID 11 f t f 1 f 25 "0" 100 0 0 100 foo bar )); +#define TextInRegProcedure 46 + +DATA(insert OID = 47 ( textout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 48 ( tidin PGUID 11 f t f 1 f 27 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 49 ( tidout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 50 ( xidin PGUID 11 f t f 1 f 28 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 51 ( xidout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 52 ( cidin PGUID 11 f t f 1 f 29 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 53 ( cidout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 54 ( oid8in PGUID 11 f t f 1 f 30 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 55 ( oid8out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 60 ( booleq PGUID 11 f t f 2 f 16 "16 16" 100 0 0 100 foo bar )); +DATA(insert OID = 61 ( chareq PGUID 11 f t f 2 f 16 "18 18" 100 0 0 100 foo bar )); +#define CharacterEqualRegProcedure 61 + +DATA(insert OID = 62 ( nameeq PGUID 11 f t f 2 f 16 "19 19" 100 0 0 100 foo bar )); +#define NameEqualRegProcedure 62 + +DATA(insert OID = 63 ( int2eq PGUID 11 f t f 2 f 16 "21 21" 100 0 0 100 foo bar )); +#define Integer16EqualRegProcedure 63 + +DATA(insert OID = 64 ( int2lt PGUID 11 f t f 2 f 16 "21 21" 100 0 0 100 foo bar )); +DATA(insert OID = 65 ( int4eq PGUID 11 f t f 2 f 16 "23 23" 100 0 0 100 foo bar )); +#define Integer32EqualRegProcedure 65 + +DATA(insert OID = 66 ( int4lt PGUID 11 f t f 2 f 16 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 67 ( texteq PGUID 11 f t f 2 f 16 "25 25" 100 0 0 0 foo bar )); +#define TextEqualRegProcedure 67 + +DATA(insert OID = 68 ( xideq PGUID 11 f t f 2 f 16 "28 28" 100 0 0 100 foo bar )); +DATA(insert OID = 69 ( cideq PGUID 11 f t f 2 f 16 "29 29" 100 0 0 100 foo bar )); +DATA(insert OID = 70 ( charne PGUID 11 f t f 2 f 16 "18 18" 100 0 0 100 foo bar )); +DATA(insert OID = 71 ( charlt PGUID 11 f t f 2 f 16 "18 18" 100 0 0 100 foo bar )); +DATA(insert OID = 72 ( charle PGUID 11 f t f 2 f 16 "18 18" 100 0 0 100 foo bar )); +DATA(insert OID = 73 ( chargt PGUID 11 f t f 2 f 16 "18 18" 100 0 0 100 foo bar )); +DATA(insert OID = 74 ( charge PGUID 11 f t f 2 f 16 "18 18" 100 0 0 100 foo bar )); +DATA(insert OID = 75 ( charpl PGUID 11 f t f 2 f 18 "18 18" 100 0 0 100 foo bar )); +DATA(insert OID = 76 ( charmi PGUID 11 f t f 2 f 18 "18 18" 100 0 0 100 foo bar )); +DATA(insert OID = 77 ( charmul PGUID 11 f t f 2 f 18 "18 18" 100 0 0 100 foo bar )); +DATA(insert OID = 78 ( chardiv PGUID 11 f t f 2 f 18 "18 18" 100 0 0 100 foo bar )); + +DATA(insert OID = 79 ( nameregexeq PGUID 11 f t f 2 f 16 "19 25" 100 0 0 100 foo bar )); +DATA(insert OID = 80 ( nameregexne PGUID 11 f t f 2 f 16 "19 25" 100 0 0 100 foo bar )); +DATA(insert OID = 81 ( textregexeq PGUID 11 f t f 2 f 16 "25 25" 100 0 1 0 foo bar )); +DATA(insert OID = 82 ( textregexne PGUID 11 f t f 2 f 16 "25 25" 100 0 1 0 foo bar )); +DATA(insert OID = 83 ( textcat PGUID 11 f t f 2 f 25 "25 25" 100 0 1 0 foo bar )); +DATA(insert OID = 84 ( boolne PGUID 11 f t f 2 f 16 "16 16" 100 0 0 100 foo bar )); + +DATA(insert OID = 97 ( rtsel PGUID 11 f t f 7 f 701 "26 26 21 0 23 23 26" 100 0 0 100 foo bar )); +DATA(insert OID = 98 ( rtnpage PGUID 11 f t f 7 f 701 "26 26 21 0 23 23 26" 100 0 0 100 foo bar )); +DATA(insert OID = 99 ( btreesel PGUID 11 f t f 7 f 701 "26 26 21 0 23 23 26" 100 0 0 100 foo bar )); + +/* OIDS 100 - 199 */ + +DATA(insert OID = 100 ( btreenpage PGUID 11 f t f 7 f 701 "26 26 21 0 23 23 26" 100 0 0 100 foo bar )); +DATA(insert OID = 101 ( eqsel PGUID 11 f t f 5 f 701 "26 26 21 0 23" 100 0 0 100 foo bar )); +#define EqualSelectivityProcedure 101 + +DATA(insert OID = 102 ( neqsel PGUID 11 f t f 5 f 701 "26 26 21 0 23" 100 0 0 100 foo bar )); +DATA(insert OID = 103 ( intltsel PGUID 11 f t f 5 f 701 "26 26 21 0 23" 100 0 0 100 foo bar )); +DATA(insert OID = 104 ( intgtsel PGUID 11 f t f 5 f 701 "26 26 21 0 23" 100 0 0 100 foo bar )); +DATA(insert OID = 105 ( eqjoinsel PGUID 11 f t f 5 f 701 "26 26 21 26 21" 100 0 0 100 foo bar )); +DATA(insert OID = 106 ( neqjoinsel PGUID 11 f t f 5 f 701 "26 26 21 26 21" 100 0 0 100 foo bar )); +DATA(insert OID = 107 ( intltjoinsel PGUID 11 f t f 5 f 701 "26 26 21 26 21" 100 0 0 100 foo bar )); +DATA(insert OID = 108 ( intgtjoinsel PGUID 11 f t f 5 f 701 "26 26 21 26 21" 100 0 0 100 foo bar )); + + + +DATA(insert OID = 117 ( point_in PGUID 11 f t f 1 f 600 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 118 ( point_out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 119 ( lseg_in PGUID 11 f t f 1 f 601 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 120 ( lseg_out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 121 ( path_in PGUID 11 f t f 1 f 602 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 122 ( path_out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 123 ( box_in PGUID 11 f t f 1 f 603 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 124 ( box_out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 125 ( box_overlap PGUID 11 f t f 2 f 16 "603 603" 100 1 0 100 foo bar )); +DATA(insert OID = 126 ( box_ge PGUID 11 f t f 2 f 16 "603 603" 100 1 0 100 foo bar )); +DATA(insert OID = 127 ( box_gt PGUID 11 f t f 2 f 16 "603 603" 100 1 0 100 foo bar )); +DATA(insert OID = 128 ( box_eq PGUID 11 f t f 2 f 16 "603 603" 100 1 0 100 foo bar )); +DATA(insert OID = 129 ( box_lt PGUID 11 f t f 2 f 16 "603 603" 100 1 0 100 foo bar )); +DATA(insert OID = 130 ( box_le PGUID 11 f t f 2 f 16 "603 603" 100 1 0 100 foo bar )); +DATA(insert OID = 131 ( point_above PGUID 11 f t f 2 f 16 "600 600" 100 0 0 100 foo bar )); +DATA(insert OID = 132 ( point_left PGUID 11 f t f 2 f 16 "600 600" 100 0 0 100 foo bar )); +DATA(insert OID = 133 ( point_right PGUID 11 f t f 2 f 16 "600 600" 100 0 0 100 foo bar )); +DATA(insert OID = 134 ( point_below PGUID 11 f t f 2 f 16 "600 600" 100 0 0 100 foo bar )); +DATA(insert OID = 135 ( point_eq PGUID 11 f t f 2 f 16 "600 600" 100 0 0 100 foo bar )); +DATA(insert OID = 136 ( on_pb PGUID 11 f t f 2 f 16 "600 603" 100 0 0 100 foo bar )); +DATA(insert OID = 137 ( on_ppath PGUID 11 f t f 2 f 16 "600 602" 100 0 1 0 foo bar )); +DATA(insert OID = 138 ( box_center PGUID 11 f t f 1 f 600 "603" 100 1 0 100 foo bar )); +DATA(insert OID = 139 ( areasel PGUID 11 f t f 5 f 701 "26 26 21 0 23" 100 0 0 100 foo bar )); +DATA(insert OID = 140 ( areajoinsel PGUID 11 f t f 5 f 701 "26 26 21 0 23" 100 0 0 100 foo bar )); +DATA(insert OID = 141 ( int4mul PGUID 11 f t f 2 f 23 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 142 ( int4fac PGUID 11 f t f 1 f 23 "23" 100 0 0 100 foo bar )); +DATA(insert OID = 143 ( pointdist PGUID 11 f t f 2 f 23 "600 600" 100 0 0 100 foo bar )); +DATA(insert OID = 144 ( int4ne PGUID 11 f t f 2 f 16 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 145 ( int2ne PGUID 11 f t f 2 f 16 "21 21" 100 0 0 100 foo bar )); +DATA(insert OID = 146 ( int2gt PGUID 11 f t f 2 f 16 "21 21" 100 0 0 100 foo bar )); +DATA(insert OID = 147 ( int4gt PGUID 11 f t f 2 f 16 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 148 ( int2le PGUID 11 f t f 2 f 16 "21 21" 100 0 0 100 foo bar )); +DATA(insert OID = 149 ( int4le PGUID 11 f t f 2 f 16 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 150 ( int4ge PGUID 11 f t f 2 f 16 "23 23" 100 0 0 100 foo bar )); +#define INT4GE_PROC_OID 150 +DATA(insert OID = 151 ( int2ge PGUID 11 f t f 2 f 16 "21 21" 100 0 0 100 foo bar )); +DATA(insert OID = 152 ( int2mul PGUID 11 f t f 2 f 21 "21 21" 100 0 0 100 foo bar )); +DATA(insert OID = 153 ( int2div PGUID 11 f t f 2 f 21 "21 21" 100 0 0 100 foo bar )); +DATA(insert OID = 154 ( int4div PGUID 11 f t f 2 f 23 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 155 ( int2mod PGUID 11 f t f 2 f 21 "21 21" 100 0 0 100 foo bar )); +DATA(insert OID = 156 ( int4mod PGUID 11 f t f 2 f 23 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 157 ( textne PGUID 11 f t f 2 f 16 "25 25" 100 0 0 0 foo bar )); +DATA(insert OID = 158 ( int24eq PGUID 11 f t f 2 f 23 "21 23" 100 0 0 100 foo bar )); +DATA(insert OID = 159 ( int42eq PGUID 11 f t f 2 f 23 "23 21" 100 0 0 100 foo bar )); +DATA(insert OID = 160 ( int24lt PGUID 11 f t f 2 f 23 "21 23" 100 0 0 100 foo bar )); +DATA(insert OID = 161 ( int42lt PGUID 11 f t f 2 f 23 "23 21" 100 0 0 100 foo bar )); +DATA(insert OID = 162 ( int24gt PGUID 11 f t f 2 f 23 "21 23" 100 0 0 100 foo bar )); +DATA(insert OID = 163 ( int42gt PGUID 11 f t f 2 f 23 "23 21" 100 0 0 100 foo bar )); +DATA(insert OID = 164 ( int24ne PGUID 11 f t f 2 f 23 "21 23" 100 0 0 100 foo bar )); +DATA(insert OID = 165 ( int42ne PGUID 11 f t f 2 f 23 "23 21" 100 0 0 100 foo bar )); +DATA(insert OID = 166 ( int24le PGUID 11 f t f 2 f 23 "21 23" 100 0 0 100 foo bar )); +DATA(insert OID = 167 ( int42le PGUID 11 f t f 2 f 23 "23 21" 100 0 0 100 foo bar )); +DATA(insert OID = 168 ( int24ge PGUID 11 f t f 2 f 23 "21 23" 100 0 0 100 foo bar )); +DATA(insert OID = 169 ( int42ge PGUID 11 f t f 2 f 23 "23 21" 100 0 0 100 foo bar )); +DATA(insert OID = 170 ( int24mul PGUID 11 f t f 2 f 23 "21 23" 100 0 0 100 foo bar )); +DATA(insert OID = 171 ( int42mul PGUID 11 f t f 2 f 23 "23 21" 100 0 0 100 foo bar )); +DATA(insert OID = 172 ( int24div PGUID 11 f t f 2 f 23 "21 23" 100 0 0 100 foo bar )); +DATA(insert OID = 173 ( int42div PGUID 11 f t f 2 f 23 "23 21" 100 0 0 100 foo bar )); +DATA(insert OID = 174 ( int24mod PGUID 11 f t f 2 f 23 "21 23" 100 0 0 100 foo bar )); +DATA(insert OID = 175 ( int42mod PGUID 11 f t f 2 f 23 "23 21" 100 0 0 100 foo bar )); +DATA(insert OID = 176 ( int2pl PGUID 11 f t f 2 f 21 "21 21" 100 0 0 100 foo bar )); +DATA(insert OID = 177 ( int4pl PGUID 11 f t f 2 f 23 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 178 ( int24pl PGUID 11 f t f 2 f 23 "21 23" 100 0 0 100 foo bar )); +DATA(insert OID = 179 ( int42pl PGUID 11 f t f 2 f 23 "23 21" 100 0 0 100 foo bar )); +DATA(insert OID = 180 ( int2mi PGUID 11 f t f 2 f 21 "21 21" 100 0 0 100 foo bar )); +DATA(insert OID = 181 ( int4mi PGUID 11 f t f 2 f 23 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 182 ( int24mi PGUID 11 f t f 2 f 23 "21 23" 100 0 0 100 foo bar )); +DATA(insert OID = 183 ( int42mi PGUID 11 f t f 2 f 23 "23 21" 100 0 0 100 foo bar )); +DATA(insert OID = 184 ( oideq PGUID 11 f t f 2 f 16 "26 26" 100 0 0 100 foo bar )); +#define ObjectIdEqualRegProcedure 184 + +DATA(insert OID = 185 ( oidne PGUID 11 f t f 2 f 16 "26 26" 100 0 0 100 foo bar )); +DATA(insert OID = 186 ( box_same PGUID 11 f t f 2 f 16 "603 603" 100 0 0 100 foo bar )); +DATA(insert OID = 187 ( box_contain PGUID 11 f t f 2 f 16 "603 603" 100 0 0 100 foo bar )); +DATA(insert OID = 188 ( box_left PGUID 11 f t f 2 f 16 "603 603" 100 0 0 100 foo bar )); +DATA(insert OID = 189 ( box_overleft PGUID 11 f t f 2 f 16 "603 603" 100 0 0 100 foo bar )); +DATA(insert OID = 190 ( box_overright PGUID 11 f t f 2 f 16 "603 603" 100 0 0 100 foo bar )); +DATA(insert OID = 191 ( box_right PGUID 11 f t f 2 f 16 "603 603" 100 0 0 100 foo bar )); +DATA(insert OID = 192 ( box_contained PGUID 11 f t f 2 f 16 "603 603" 100 0 0 100 foo bar )); +DATA(insert OID = 193 ( rt_box_union PGUID 11 f t f 2 f 603 "603 603" 100 0 0 100 foo bar )); +DATA(insert OID = 194 ( rt_box_inter PGUID 11 f t f 2 f 603 "603 603" 100 0 0 100 foo bar )); +DATA(insert OID = 195 ( rt_box_size PGUID 11 f t f 2 f 700 "603 700" 100 0 0 100 foo bar )); +DATA(insert OID = 196 ( rt_bigbox_size PGUID 11 f t f 2 f 700 "603 700" 100 0 0 100 foo bar )); +DATA(insert OID = 197 ( rt_poly_union PGUID 11 f t f 2 f 604 "604 604" 100 0 0 100 foo bar )); +DATA(insert OID = 198 ( rt_poly_inter PGUID 11 f t f 2 f 604 "604 604" 100 0 0 100 foo bar )); +DATA(insert OID = 199 ( rt_poly_size PGUID 11 f t f 2 f 23 "604 23" 100 0 0 100 foo bar )); + +/* OIDS 200 - 299 */ + +DATA(insert OID = 200 ( float4in PGUID 11 f t f 1 f 700 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 201 ( float4out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 202 ( float4mul PGUID 11 f t f 2 f 700 "700 700" 100 0 0 100 foo bar )); +DATA(insert OID = 203 ( float4div PGUID 11 f t f 2 f 700 "700 700" 100 0 0 100 foo bar )); +DATA(insert OID = 204 ( float4pl PGUID 11 f t f 2 f 700 "700 700" 100 0 0 100 foo bar )); +DATA(insert OID = 205 ( float4mi PGUID 11 f t f 2 f 700 "700 700" 100 0 0 100 foo bar )); +DATA(insert OID = 206 ( float4um PGUID 11 f t f 1 f 700 "700" 100 0 0 100 foo bar )); +DATA(insert OID = 207 ( float4abs PGUID 11 f t f 1 f 700 "700 700" 100 0 0 100 foo bar )); +DATA(insert OID = 208 ( float4inc PGUID 11 f t f 1 f 700 "700" 100 0 0 100 foo bar )); +DATA(insert OID = 209 ( float4larger PGUID 11 f t f 2 f 700 "700 700" 100 0 0 100 foo bar )); +DATA(insert OID = 211 ( float4smaller PGUID 11 f t f 2 f 700 "700 700" 100 0 0 100 foo bar )); + +DATA(insert OID = 212 ( int4um PGUID 11 f t f 1 f 23 "23" 100 0 0 100 foo bar )); +DATA(insert OID = 213 ( int2um PGUID 11 f t f 1 f 21 "21" 100 0 0 100 foo bar )); + +DATA(insert OID = 214 ( float8in PGUID 11 f t f 1 f 701 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 215 ( float8out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 216 ( float8mul PGUID 11 f t f 2 f 701 "701 701" 100 0 0 100 foo bar )); +DATA(insert OID = 217 ( float8div PGUID 11 f t f 2 f 701 "701 701" 100 0 0 100 foo bar )); +DATA(insert OID = 218 ( float8pl PGUID 11 f t f 2 f 701 "701 701" 100 0 0 100 foo bar )); +DATA(insert OID = 219 ( float8mi PGUID 11 f t f 2 f 701 "701 701" 100 0 0 100 foo bar )); +DATA(insert OID = 220 ( float8um PGUID 11 f t f 1 f 701 "701" 100 0 0 100 foo bar )); +DATA(insert OID = 221 ( float8abs PGUID 11 f t f 1 f 701 "701" 100 0 0 100 foo bar )); +DATA(insert OID = 222 ( float8inc PGUID 11 f t f 1 f 701 "701" 100 0 0 100 foo bar )); +DATA(insert OID = 223 ( float8larger PGUID 11 f t f 2 f 701 "701 701" 100 0 0 100 foo bar )); +DATA(insert OID = 224 ( float8smaller PGUID 11 f t f 2 f 701 "701 701" 100 0 0 100 foo bar )); +DATA(insert OID = 228 ( dround PGUID 11 f t f 1 f 701 "701" 100 0 0 100 foo bar )); +DATA(insert OID = 229 ( dtrunc PGUID 11 f t f 1 f 701 "701" 100 0 0 100 foo bar )); +DATA(insert OID = 230 ( dsqrt PGUID 11 f t f 1 f 701 "701" 100 0 0 100 foo bar )); +DATA(insert OID = 231 ( dcbrt PGUID 11 f t f 1 f 701 "701" 100 0 0 100 foo bar )); +DATA(insert OID = 232 ( dpow PGUID 11 f t f 2 f 701 "701" 100 0 0 100 foo bar )); +DATA(insert OID = 233 ( dexp PGUID 11 f t f 1 f 701 "701" 100 0 0 100 foo bar )); +DATA(insert OID = 234 ( dlog1 PGUID 11 f t f 1 f 701 "701" 100 0 0 100 foo bar )); + +DATA(insert OID = 240 ( nabstimein PGUID 11 f t f 1 f 702 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 241 ( nabstimeout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 242 ( reltimein PGUID 11 f t f 1 f 703 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 243 ( reltimeout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 244 ( timepl PGUID 11 f t f 2 f 702 "702 703" 100 0 0 100 foo bar )); +DATA(insert OID = 245 ( timemi PGUID 11 f t f 2 f 702 "702 703" 100 0 0 100 foo bar )); +DATA(insert OID = 246 ( tintervalin PGUID 11 f t f 1 f 704 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 247 ( tintervalout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 248 ( ininterval PGUID 11 f t f 2 f 16 "702 704" 100 0 0 100 foo bar )); +DATA(insert OID = 249 ( intervalrel PGUID 11 f t f 1 f 703 "704" 100 0 0 100 foo bar )); +DATA(insert OID = 250 ( timenow PGUID 11 f t f 0 f 702 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 251 ( abstimeeq PGUID 11 f t f 2 f 16 "702 702" 100 0 0 100 foo bar )); +DATA(insert OID = 252 ( abstimene PGUID 11 f t f 2 f 16 "702 702" 100 0 0 100 foo bar )); +DATA(insert OID = 253 ( abstimelt PGUID 11 f t f 2 f 16 "702 702" 100 0 0 100 foo bar )); +DATA(insert OID = 254 ( abstimegt PGUID 11 f t f 2 f 16 "702 702" 100 0 0 100 foo bar )); +DATA(insert OID = 255 ( abstimele PGUID 11 f t f 2 f 16 "702 702" 100 0 0 100 foo bar )); +DATA(insert OID = 256 ( abstimege PGUID 11 f t f 2 f 16 "702 702" 100 0 0 100 foo bar )); +DATA(insert OID = 257 ( reltimeeq PGUID 11 f t f 2 f 16 "703 703" 100 0 0 100 foo bar )); +DATA(insert OID = 258 ( reltimene PGUID 11 f t f 2 f 16 "703 703" 100 0 0 100 foo bar )); +DATA(insert OID = 259 ( reltimelt PGUID 11 f t f 2 f 16 "703 703" 100 0 0 100 foo bar )); +DATA(insert OID = 260 ( reltimegt PGUID 11 f t f 2 f 16 "703 703" 100 0 0 100 foo bar )); +DATA(insert OID = 261 ( reltimele PGUID 11 f t f 2 f 16 "703 703" 100 0 0 100 foo bar )); +DATA(insert OID = 262 ( reltimege PGUID 11 f t f 2 f 16 "703 703" 100 0 0 100 foo bar )); +DATA(insert OID = 263 ( intervaleq PGUID 11 f t f 2 f 16 "704 704" 100 0 0 100 foo bar )); +DATA(insert OID = 264 ( intervalct PGUID 11 f t f 2 f 16 "704 704" 100 0 0 100 foo bar )); +DATA(insert OID = 265 ( intervalov PGUID 11 f t f 2 f 16 "704 704" 100 0 0 100 foo bar )); +DATA(insert OID = 266 ( intervalleneq PGUID 11 f t f 2 f 16 "704 703" 100 0 0 100 foo bar )); +DATA(insert OID = 267 ( intervallenne PGUID 11 f t f 2 f 16 "704 703" 100 0 0 100 foo bar )); +DATA(insert OID = 268 ( intervallenlt PGUID 11 f t f 2 f 16 "704 703" 100 0 0 100 foo bar )); +DATA(insert OID = 269 ( intervallengt PGUID 11 f t f 2 f 16 "704 703" 100 0 0 100 foo bar )); +DATA(insert OID = 270 ( intervallenle PGUID 11 f t f 2 f 16 "704 703" 100 0 0 100 foo bar )); +DATA(insert OID = 271 ( intervallenge PGUID 11 f t f 2 f 16 "704 703" 100 0 0 100 foo bar )); +DATA(insert OID = 272 ( intervalstart PGUID 11 f t f 1 f 702 "704" 100 0 0 100 foo bar )); +DATA(insert OID = 273 ( intervalend PGUID 11 f t f 1 f 702 "704" 100 0 0 100 foo bar )); +DATA(insert OID = 274 ( timeofday PGUID 11 f t f 0 f 25 "0" 100 0 0 100 foo bar )); + +DATA(insert OID = 276 ( int2fac PGUID 11 f t f 1 f 21 "21" 100 0 0 100 foo bar )); +DATA(insert OID = 279 ( float48mul PGUID 11 f t f 2 f 701 "700 701" 100 0 0 100 foo bar )); +DATA(insert OID = 280 ( float48div PGUID 11 f t f 2 f 701 "700 701" 100 0 0 100 foo bar )); +DATA(insert OID = 281 ( float48pl PGUID 11 f t f 2 f 701 "700 701" 100 0 0 100 foo bar )); +DATA(insert OID = 282 ( float48mi PGUID 11 f t f 2 f 701 "700 701" 100 0 0 100 foo bar )); +DATA(insert OID = 283 ( float84mul PGUID 11 f t f 2 f 701 "701 700" 100 0 0 100 foo bar )); +DATA(insert OID = 284 ( float84div PGUID 11 f t f 2 f 701 "701 700" 100 0 0 100 foo bar )); +DATA(insert OID = 285 ( float84pl PGUID 11 f t f 2 f 701 "701 700" 100 0 0 100 foo bar )); +DATA(insert OID = 286 ( float84mi PGUID 11 f t f 2 f 701 "701 700" 100 0 0 100 foo bar )); + +DATA(insert OID = 287 ( float4eq PGUID 11 f t f 2 f 16 "700 700" 100 0 0 100 foo bar )); +DATA(insert OID = 288 ( float4ne PGUID 11 f t f 2 f 16 "700 700" 100 0 0 100 foo bar )); +DATA(insert OID = 289 ( float4lt PGUID 11 f t f 2 f 16 "700 700" 100 0 0 100 foo bar )); +DATA(insert OID = 290 ( float4le PGUID 11 f t f 2 f 16 "700 700" 100 0 0 100 foo bar )); +DATA(insert OID = 291 ( float4gt PGUID 11 f t f 2 f 16 "700 700" 100 0 0 100 foo bar )); +DATA(insert OID = 292 ( float4ge PGUID 11 f t f 2 f 16 "700 700" 100 0 0 100 foo bar )); + +DATA(insert OID = 293 ( float8eq PGUID 11 f t f 2 f 16 "701 701" 100 0 0 100 foo bar )); +DATA(insert OID = 294 ( float8ne PGUID 11 f t f 2 f 16 "701 701" 100 0 0 100 foo bar )); +DATA(insert OID = 295 ( float8lt PGUID 11 f t f 2 f 16 "701 701" 100 0 0 100 foo bar )); +DATA(insert OID = 296 ( float8le PGUID 11 f t f 2 f 16 "701 701" 100 0 0 100 foo bar )); +DATA(insert OID = 297 ( float8gt PGUID 11 f t f 2 f 16 "701 701" 100 0 0 100 foo bar )); +DATA(insert OID = 298 ( float8ge PGUID 11 f t f 2 f 16 "701 701" 100 0 0 100 foo bar )); + +DATA(insert OID = 299 ( float48eq PGUID 11 f t f 2 f 16 "700 701" 100 0 0 100 foo bar )); + +/* OIDS 300 - 399 */ + +DATA(insert OID = 300 ( float48ne PGUID 11 f t f 2 f 16 "700 701" 100 0 0 100 foo bar )); +DATA(insert OID = 301 ( float48lt PGUID 11 f t f 2 f 16 "700 701" 100 0 0 100 foo bar )); +DATA(insert OID = 302 ( float48le PGUID 11 f t f 2 f 16 "700 701" 100 0 0 100 foo bar )); +DATA(insert OID = 303 ( float48gt PGUID 11 f t f 2 f 16 "700 701" 100 0 0 100 foo bar )); +DATA(insert OID = 304 ( float48ge PGUID 11 f t f 2 f 16 "700 701" 100 0 0 100 foo bar )); +DATA(insert OID = 305 ( float84eq PGUID 11 f t f 2 f 16 "701 700" 100 0 0 100 foo bar )); +DATA(insert OID = 306 ( float84ne PGUID 11 f t f 2 f 16 "701 700" 100 0 0 100 foo bar )); +DATA(insert OID = 307 ( float84lt PGUID 11 f t f 2 f 16 "701 700" 100 0 0 100 foo bar )); +DATA(insert OID = 308 ( float84le PGUID 11 f t f 2 f 16 "701 700" 100 0 0 100 foo bar )); +DATA(insert OID = 309 ( float84gt PGUID 11 f t f 2 f 16 "701 700" 100 0 0 100 foo bar )); +DATA(insert OID = 310 ( float84ge PGUID 11 f t f 2 f 16 "701 700" 100 0 0 100 foo bar )); + +DATA(insert OID = 311 ( ftod PGUID 11 f t f 2 f 701 "700" 100 0 0 100 foo bar )); +DATA(insert OID = 312 ( dtof PGUID 11 f t f 2 f 700 "701" 100 0 0 100 foo bar )); +DATA(insert OID = 313 ( i2toi4 PGUID 11 f t f 2 f 23 "21" 100 0 0 100 foo bar )); +DATA(insert OID = 314 ( i4toi2 PGUID 11 f t f 2 f 21 "23" 100 0 0 100 foo bar )); +DATA(insert OID = 315 ( keyfirsteq PGUID 11 f t f 2 f 16 "0 21" 100 0 0 100 foo bar )); + +DATA(insert OID = 320 ( rtinsert PGUID 11 f t f 2 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 321 ( rtdelete PGUID 11 f t f 2 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 322 ( rtgettuple PGUID 11 f t f 2 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 323 ( rtbuild PGUID 11 f t f 9 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 324 ( rtbeginscan PGUID 11 f t f 4 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 325 ( rtendscan PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 326 ( rtmarkpos PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 327 ( rtrestrpos PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 328 ( rtrescan PGUID 11 f t f 3 f 23 "0" 100 0 0 100 foo bar )); + +DATA(insert OID = 330 ( btgettuple PGUID 11 f t f 2 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 331 ( btinsert PGUID 11 f t f 2 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 332 ( btdelete PGUID 11 f t f 2 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 333 ( btbeginscan PGUID 11 f t f 4 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 334 ( btrescan PGUID 11 f t f 3 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 335 ( btendscan PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 336 ( btmarkpos PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 337 ( btrestrpos PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 338 ( btbuild PGUID 11 f t f 9 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 339 ( poly_same PGUID 11 f t f 2 f 16 "604 604" 100 0 1 0 foo bar )); +DATA(insert OID = 340 ( poly_contain PGUID 11 f t f 2 f 16 "604 604" 100 0 1 0 foo bar )); +DATA(insert OID = 341 ( poly_left PGUID 11 f t f 2 f 16 "604 604" 100 0 1 0 foo bar )); +DATA(insert OID = 342 ( poly_overleft PGUID 11 f t f 2 f 16 "604 604" 100 0 1 0 foo bar )); +DATA(insert OID = 343 ( poly_overright PGUID 11 f t f 2 f 16 "604 604" 100 0 1 0 foo bar )); +DATA(insert OID = 344 ( poly_right PGUID 11 f t f 2 f 16 "604 604" 100 0 1 0 foo bar )); +DATA(insert OID = 345 ( poly_contained PGUID 11 f t f 2 f 16 "604 604" 100 0 1 0 foo bar )); +DATA(insert OID = 346 ( poly_overlap PGUID 11 f t f 2 f 16 "604 604" 100 0 1 0 foo bar )); +DATA(insert OID = 347 ( poly_in PGUID 11 f t f 1 f 604 "0" 100 0 1 0 foo bar )); +DATA(insert OID = 348 ( poly_out PGUID 11 f t f 1 f 23 "0" 100 0 1 0 foo bar )); + +DATA(insert OID = 350 ( btint2cmp PGUID 11 f t f 2 f 23 "21 21" 100 0 0 100 foo bar )); +DATA(insert OID = 351 ( btint4cmp PGUID 11 f t f 2 f 23 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 352 ( btint42cmp PGUID 11 f t f 2 f 23 "23 21" 100 0 0 100 foo bar )); +DATA(insert OID = 353 ( btint24cmp PGUID 11 f t f 2 f 23 "21 23" 100 0 0 100 foo bar )); +DATA(insert OID = 354 ( btfloat4cmp PGUID 11 f t f 2 f 23 "700 700" 100 0 0 100 foo bar )); +DATA(insert OID = 355 ( btfloat8cmp PGUID 11 f t f 2 f 23 "701 701" 100 0 0 100 foo bar )); +DATA(insert OID = 356 ( btoidcmp PGUID 11 f t f 2 f 23 "26 26" 100 0 0 100 foo bar )); +DATA(insert OID = 357 ( btabstimecmp PGUID 11 f t f 2 f 23 "702 702" 100 0 0 100 foo bar )); +DATA(insert OID = 358 ( btcharcmp PGUID 11 f t f 2 f 23 "18 18" 100 0 0 100 foo bar )); +DATA(insert OID = 359 ( btnamecmp PGUID 11 f t f 2 f 23 "19 19" 100 0 0 100 foo bar )); +DATA(insert OID = 360 ( bttextcmp PGUID 11 f t f 2 f 23 "25 25" 100 0 0 100 foo bar )); + +DATA(insert OID = 361 ( lseg_distance PGUID 11 f t f 2 f 701 "601 601" 100 0 0 100 foo bar )); +DATA(insert OID = 362 ( lseg_interpt PGUID 11 f t f 2 f 600 "601 601" 100 0 0 100 foo bar )); +DATA(insert OID = 363 ( dist_ps PGUID 11 f t f 2 f 701 "600 601" 100 0 0 100 foo bar )); +DATA(insert OID = 364 ( dist_pb PGUID 11 f t f 2 f 701 "600 603" 100 0 0 100 foo bar )); +DATA(insert OID = 365 ( dist_sb PGUID 11 f t f 2 f 701 "601 603" 100 0 0 100 foo bar )); +DATA(insert OID = 366 ( close_ps PGUID 11 f t f 2 f 600 "600 601" 100 0 0 100 foo bar )); +DATA(insert OID = 367 ( close_pb PGUID 11 f t f 2 f 600 "600 603" 100 0 0 100 foo bar )); +DATA(insert OID = 368 ( close_sb PGUID 11 f t f 2 f 600 "601 603" 100 0 0 100 foo bar )); +DATA(insert OID = 369 ( on_ps PGUID 11 f t f 2 f 16 "600 601" 100 0 0 100 foo bar )); +DATA(insert OID = 370 ( path_distance PGUID 11 f t f 2 f 701 "602 602" 100 0 1 0 foo bar )); +DATA(insert OID = 371 ( dist_ppth PGUID 11 f t f 2 f 701 "600 602" 100 0 1 0 foo bar )); +DATA(insert OID = 372 ( on_sb PGUID 11 f t f 2 f 16 "601 603" 100 0 0 100 foo bar )); +DATA(insert OID = 373 ( inter_sb PGUID 11 f t f 2 f 16 "601 603" 100 0 0 100 foo bar )); +DATA(insert OID = 374 ( btchar16cmp PGUID 11 f t f 2 f 23 "19 19" 100 0 0 100 foo bar )); + +/* OIDS 400 - 499 */ + +DATA(insert OID = 438 ( hashsel PGUID 11 f t t 7 f 701 "26 26 21 0 23 23 26" 100 0 0 100 foo bar )); +DATA(insert OID = 439 ( hashnpage PGUID 11 f t t 7 f 701 "26 26 21 0 23 23 26" 100 0 0 100 foo bar )); + +DATA(insert OID = 440 ( hashgettuple PGUID 11 f t f 2 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 441 ( hashinsert PGUID 11 f t f 2 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 442 ( hashdelete PGUID 11 f t f 2 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 443 ( hashbeginscan PGUID 11 f t f 4 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 444 ( hashrescan PGUID 11 f t f 3 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 445 ( hashendscan PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 446 ( hashmarkpos PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 447 ( hashrestrpos PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 448 ( hashbuild PGUID 11 f t f 9 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 449 ( hashint2 PGUID 11 f t f 2 f 23 "21 21" 100 0 0 100 foo bar )); +DATA(insert OID = 450 ( hashint4 PGUID 11 f t f 2 f 23 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 451 ( hashfloat4 PGUID 11 f t f 2 f 23 "700 700" 100 0 0 100 foo bar )); +DATA(insert OID = 452 ( hashfloat8 PGUID 11 f t f 2 f 23 "701 701" 100 0 0 100 foo bar )); +DATA(insert OID = 453 ( hashoid PGUID 11 f t f 2 f 23 "26 26" 100 0 0 100 foo bar )); +DATA(insert OID = 454 ( hashchar PGUID 11 f t f 2 f 23 "18 18" 100 0 0 100 foo bar )); +DATA(insert OID = 455 ( hashname PGUID 11 f t f 2 f 23 "19 19" 100 0 0 100 foo bar )); +DATA(insert OID = 456 ( hashtext PGUID 11 f t f 2 f 23 "25 25" 100 0 0 100 foo bar )); +DATA(insert OID = 466 ( char2in PGUID 11 f t f 1 f 409 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 467 ( char4in PGUID 11 f t f 1 f 410 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 468 ( char8in PGUID 11 f t f 1 f 411 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 469 ( char2out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 470 ( char4out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 471 ( char8out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 472 ( char2eq PGUID 11 f t f 2 f 16 "409 409" 100 0 0 100 foo bar )); +DATA(insert OID = 473 ( char4eq PGUID 11 f t f 2 f 16 "410 410" 100 0 0 100 foo bar )); +DATA(insert OID = 474 ( char8eq PGUID 11 f t f 2 f 16 "411 411" 100 0 0 100 foo bar )); +DATA(insert OID = 475 ( char2lt PGUID 11 f t f 2 f 16 "409 409" 100 0 0 100 foo bar )); +DATA(insert OID = 476 ( char4lt PGUID 11 f t f 2 f 16 "410 410" 100 0 0 100 foo bar )); +DATA(insert OID = 477 ( char8lt PGUID 11 f t f 2 f 16 "411 411" 100 0 0 100 foo bar )); +DATA(insert OID = 478 ( char2le PGUID 11 f t f 2 f 16 "409 409" 100 0 0 100 foo bar )); +DATA(insert OID = 479 ( char4le PGUID 11 f t f 2 f 16 "410 410" 100 0 0 100 foo bar )); +DATA(insert OID = 480 ( char8le PGUID 11 f t f 2 f 16 "411 411" 100 0 0 100 foo bar )); +DATA(insert OID = 481 ( char2gt PGUID 11 f t f 2 f 16 "409 409" 100 0 0 100 foo bar )); +DATA(insert OID = 482 ( char4gt PGUID 11 f t f 2 f 16 "410 410" 100 0 0 100 foo bar )); +DATA(insert OID = 483 ( char8gt PGUID 11 f t f 2 f 16 "411 411" 100 0 0 100 foo bar )); +DATA(insert OID = 484 ( char2ge PGUID 11 f t f 2 f 16 "409 409" 100 0 0 100 foo bar )); +DATA(insert OID = 490 ( char16eq PGUID 11 f t f 2 f 16 "19 19" 100 0 0 100 foo bar )); +#define Character16EqualRegProcedure 490 +DATA(insert OID = 492 ( char16lt PGUID 11 f t f 2 f 16 "19 19" 100 0 0 100 foo bar )); +DATA(insert OID = 493 ( char16le PGUID 11 f t f 2 f 16 "19 19" 100 0 0 100 foo bar )); +DATA(insert OID = 494 ( char16gt PGUID 11 f t f 2 f 16 "19 19" 100 0 0 100 foo bar )); +DATA(insert OID = 495 ( char16ge PGUID 11 f t f 2 f 16 "19 19" 100 0 0 100 foo bar )); +DATA(insert OID = 496 ( char16ne PGUID 11 f t f 2 f 16 "19 19" 100 0 0 100 foo bar )); + +DATA(insert OID = 499 ( hashchar16 PGUID 11 f t f 2 f 23 "19 19" 100 0 0 100 foo bar )); + +/* OIDS 500 - 599 */ + +/* OIDS 600 - 699 */ + +DATA(insert OID = 650 ( int4notin PGUID 11 f t f 2 f 16 "21 0" 100 0 0 100 foo bar )); +DATA(insert OID = 651 ( oidnotin PGUID 11 f t f 2 f 16 "26 0" 100 0 0 100 foo bar )); +DATA(insert OID = 652 ( int44in PGUID 11 f t f 1 f 22 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 653 ( int44out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 655 ( namelt PGUID 11 f t f 2 f 16 "19 19" 100 0 0 100 foo bar )); +DATA(insert OID = 656 ( namele PGUID 11 f t f 2 f 16 "19 19" 100 0 0 100 foo bar )); +DATA(insert OID = 657 ( namegt PGUID 11 f t f 2 f 16 "19 19" 100 0 0 100 foo bar )); +DATA(insert OID = 658 ( namege PGUID 11 f t f 2 f 16 "19 19" 100 0 0 100 foo bar )); +DATA(insert OID = 659 ( namene PGUID 11 f t f 2 f 16 "19 19" 100 0 0 100 foo bar )); +DATA(insert OID = 682 ( mktinterval PGUID 11 f t f 2 f 704 "702 702" 100 0 0 100 foo bar )); +DATA(insert OID = 683 ( oid8eq PGUID 11 f t f 2 f 16 "30 30" 100 0 0 100 foo bar )); +DATA(insert OID = 684 ( char4ge PGUID 11 f t f 2 f 16 "410 410" 100 0 0 100 foo bar )); +DATA(insert OID = 685 ( char8ge PGUID 11 f t f 2 f 16 "411 411" 100 0 0 100 foo bar )); +DATA(insert OID = 686 ( char2ne PGUID 11 f t f 2 f 16 "409 409" 100 0 0 100 foo bar )); +DATA(insert OID = 687 ( char4ne PGUID 11 f t f 2 f 16 "410 410" 100 0 0 100 foo bar )); +DATA(insert OID = 688 ( char8ne PGUID 11 f t f 2 f 16 "411 411" 100 0 0 100 foo bar )); +DATA(insert OID = 689 ( btchar2cmp PGUID 11 f t f 2 f 23 "409 409" 100 0 0 100 foo bar )); +DATA(insert OID = 690 ( btchar4cmp PGUID 11 f t f 2 f 23 "410 410" 100 0 0 100 foo bar )); +DATA(insert OID = 691 ( btchar8cmp PGUID 11 f t f 2 f 23 "411 411" 100 0 0 100 foo bar )); +DATA(insert OID = 692 ( hashchar2 PGUID 11 f t f 2 f 23 "409 409" 100 0 0 100 foo bar )); +DATA(insert OID = 693 ( hashchar4 PGUID 11 f t f 2 f 23 "410 410" 100 0 0 100 foo bar )); +DATA(insert OID = 694 ( hashchar8 PGUID 11 f t f 2 f 23 "411 411" 100 0 0 100 foo bar )); +DATA(insert OID = 695 ( char8regexeq PGUID 11 f t f 2 f 16 "411 25" 100 0 0 100 foo bar )); +DATA(insert OID = 696 ( char8regexne PGUID 11 f t f 2 f 16 "411 25" 100 0 0 100 foo bar )); +DATA(insert OID = 699 ( char2regexeq PGUID 11 f t f 2 f 16 "409 25" 100 0 0 100 foo bar )); + +/* OIDS 700 - 799 */ +DATA(insert OID = 700 ( char16regexeq PGUID 11 f t f 2 f 16 "19 25" 100 0 0 100 foo bar )); +DATA(insert OID = 701 ( char16regexne PGUID 11 f t f 2 f 16 "19 25" 100 0 0 100 foo bar )); + +DATA(insert OID = 710 ( GetPgUserName PGUID 11 f t f 0 f 19 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 711 ( userfntest PGUID 11 f t f 1 f 23 "23" 100 0 0 100 foo bar )); +DATA(insert OID = 713 ( oidrand PGUID 11 f t f 2 f 16 "26 23" 100 0 0 100 foo bar )); +DATA(insert OID = 715 ( oidsrand PGUID 11 f t f 1 f 16 "23" 100 0 0 100 foo bar )); +DATA(insert OID = 716 ( oideqint4 PGUID 11 f t f 2 f 16 "26 23" 100 0 0 100 foo bar )); +DATA(insert OID = 717 ( int4eqoid PGUID 11 f t f 2 f 16 "23 26" 100 0 0 100 foo bar )); + + +DATA(insert OID = 720 ( byteaGetSize PGUID 11 f t f 1 f 23 "17" 100 0 0 100 foo bar )); +DATA(insert OID = 721 ( byteaGetByte PGUID 11 f t f 2 f 23 "17 23" 100 0 0 100 foo bar )); +DATA(insert OID = 722 ( byteaSetByte PGUID 11 f t f 3 f 17 "17 23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 723 ( byteaGetBit PGUID 11 f t f 2 f 23 "17 23" 100 0 0 100 foo bar )); +DATA(insert OID = 724 ( byteaSetBit PGUID 11 f t f 3 f 17 "17 23 23" 100 0 0 100 foo bar )); + +DATA(insert OID = 730 ( pqtest PGUID 11 f t f 1 f 23 "25" 100 0 0 100 foo bar )); + +DATA(insert OID = 740 ( text_lt PGUID 11 f t f 2 f 16 "25 25" 100 0 0 0 foo bar )); +DATA(insert OID = 741 ( text_le PGUID 11 f t f 2 f 16 "25 25" 100 0 0 0 foo bar )); +DATA(insert OID = 742 ( text_gt PGUID 11 f t f 2 f 16 "25 25" 100 0 0 0 foo bar )); +DATA(insert OID = 743 ( text_ge PGUID 11 f t f 2 f 16 "25 25" 100 0 0 0 foo bar )); + +DATA(insert OID = 744 ( array_eq PGUID 11 f t f 2 f 16 "0 0" 100 0 0 100 foo bar)); +DATA(insert OID = 745 ( array_assgn PGUID 11 f t f 8 f 23 "0 23 0 0 0 23 23 0" 100 0 0 100 foo bar)); +DATA(insert OID = 746 ( array_clip PGUID 11 f t f 7 f 23 "0 23 0 0 23 23 0" 100 0 0 100 foo bar)); +DATA(insert OID = 747 ( array_dims PGUID 11 f t f 1 f 25 "0" 100 0 0 100 foo bar)); +DATA(insert OID = 748 ( array_set PGUID 11 f t f 8 f 23 "0 23 0 0 23 23 23 0" 100 0 0 100 foo bar)); +DATA(insert OID = 749 ( array_ref PGUID 11 f t f 7 f 23 "0 23 0 23 23 23 0" 100 0 0 100 foo bar)); +DATA(insert OID = 750 ( array_in PGUID 11 f t f 2 f 23 "0 0" 100 0 0 100 foo bar )); +DATA(insert OID = 751 ( array_out PGUID 11 f t f 2 f 23 "0 0" 100 0 0 100 foo bar )); + +DATA(insert OID = 752 ( filename_in PGUID 11 f t f 2 f 605 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 753 ( filename_out PGUID 11 f t f 2 f 19 "0" 100 0 0 100 foo bar )); + +DATA(insert OID = 760 ( smgrin PGUID 11 f t f 1 f 210 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 761 ( smgrout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 762 ( smgreq PGUID 11 f t f 2 f 16 "210 210" 100 0 0 100 foo bar )); +DATA(insert OID = 763 ( smgrne PGUID 11 f t f 2 f 16 "210 210" 100 0 0 100 foo bar )); + +DATA(insert OID = 764 ( lo_import PGUID 11 f t f 1 f 26 "25" 100 0 0 100 foo bar )); +DATA(insert OID = 765 ( lo_export PGUID 11 f t f 2 f 23 "26 25" 100 0 0 100 foo bar )); + +DATA(insert OID = 766 ( int4inc PGUID 11 f t f 1 f 23 "23" 100 0 0 100 foo bar )); +DATA(insert OID = 767 ( int2inc PGUID 11 f t f 1 f 21 "21" 100 0 0 100 foo bar )); +DATA(insert OID = 768 ( int4larger PGUID 11 f t f 2 f 23 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 769 ( int4smaller PGUID 11 f t f 2 f 23 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 770 ( int2larger PGUID 11 f t f 2 f 23 "21 21" 100 0 0 100 foo bar )); +DATA(insert OID = 771 ( int2smaller PGUID 11 f t f 2 f 23 "21 21" 100 0 0 100 foo bar )); + +/* OIDS 800 - 899 */ +DATA(insert OID = 820 ( oidint2in PGUID 11 f t f 1 f 810 "0" 100 0 0 100 foo bar)); +DATA(insert OID = 821 ( oidint2out PGUID 11 f t f 1 f 19 "0" 100 0 0 100 foo bar)); +DATA(insert OID = 822 ( oidint2lt PGUID 11 f t f 2 f 16 "810 810" 100 0 0 100 foo bar)); +DATA(insert OID = 823 ( oidint2le PGUID 11 f t f 2 f 16 "810 810" 100 0 0 100 foo bar)); +DATA(insert OID = 824 ( oidint2eq PGUID 11 f t f 2 f 16 "810 810" 100 0 0 100 foo bar)); + +#define OidInt2EqRegProcedure 824 + +DATA(insert OID = 825 ( oidint2ge PGUID 11 f t f 2 f 16 "810 810" 100 0 0 100 foo bar)); +DATA(insert OID = 826 ( oidint2gt PGUID 11 f t f 2 f 16 "810 810" 100 0 0 100 foo bar)); +DATA(insert OID = 827 ( oidint2ne PGUID 11 f t f 2 f 16 "810 810" 100 0 0 100 foo bar)); +DATA(insert OID = 828 ( oidint2cmp PGUID 11 f t f 2 f 21 "810 810" 100 0 0 100 foo bar)); +DATA(insert OID = 829 ( mkoidint2 PGUID 11 f t f 2 f 810 "26 21" 100 0 0 100 foo bar)); + +DATA(insert OID = 837 ( char2regexne PGUID 11 f t f 2 f 16 "409 25" 100 0 0 100 foo bar )); +DATA(insert OID = 836 ( char4regexeq PGUID 11 f t f 2 f 16 "410 25" 100 0 0 100 foo bar )); +DATA(insert OID = 838 ( char4regexne PGUID 11 f t f 2 f 16 "410 25" 100 0 0 100 foo bar )); + +DATA(insert OID = 850 ( textlike PGUID 11 f t f 2 f 16 "25 25" 100 0 1 0 foo bar )); +DATA(insert OID = 851 ( textnlike PGUID 11 f t f 2 f 16 "25 25" 100 0 1 0 foo bar )); +DATA(insert OID = 852 ( char2like PGUID 11 f t f 2 f 16 "409 25" 100 0 0 100 foo bar )); +DATA(insert OID = 853 ( char2nlike PGUID 11 f t f 2 f 16 "409 25" 100 0 0 100 foo bar )); +DATA(insert OID = 854 ( char4like PGUID 11 f t f 2 f 16 "410 25" 100 0 0 100 foo bar )); +DATA(insert OID = 855 ( char4nlike PGUID 11 f t f 2 f 16 "410 25" 100 0 0 100 foo bar )); +DATA(insert OID = 856 ( char8like PGUID 11 f t f 2 f 16 "411 25" 100 0 0 100 foo bar )); +DATA(insert OID = 857 ( char8nlike PGUID 11 f t f 2 f 16 "411 25" 100 0 0 100 foo bar )); +DATA(insert OID = 858 ( namelike PGUID 11 f t f 2 f 16 "19 25" 100 0 0 100 foo bar )); +DATA(insert OID = 859 ( namenlike PGUID 11 f t f 2 f 16 "19 25" 100 0 0 100 foo bar )); +DATA(insert OID = 860 ( char16like PGUID 11 f t f 2 f 16 "20 25" 100 0 0 100 foo bar )); +DATA(insert OID = 861 ( char16nlike PGUID 11 f t f 2 f 16 "20 25" 100 0 0 100 foo bar )); + +/* OIDS 900 - 999 */ + +DATA(insert OID = 920 ( oidint4in PGUID 11 f t f 1 f 910 "0" 100 0 0 100 foo bar)); +DATA(insert OID = 921 ( oidint4out PGUID 11 f t f 1 f 19 "0" 100 0 0 100 foo bar)); +DATA(insert OID = 922 ( oidint4lt PGUID 11 f t f 2 f 16 "910 910" 100 0 0 100 foo bar)); +DATA(insert OID = 923 ( oidint4le PGUID 11 f t f 2 f 16 "910 910" 100 0 0 100 foo bar)); +DATA(insert OID = 924 ( oidint4eq PGUID 11 f t f 2 f 16 "910 910" 100 0 0 100 foo bar)); + +#define OidInt4EqRegProcedure 924 + +DATA(insert OID = 925 ( oidint4ge PGUID 11 f t f 2 f 16 "910 910" 100 0 0 100 foo bar)); +DATA(insert OID = 926 ( oidint4gt PGUID 11 f t f 2 f 16 "910 910" 100 0 0 100 foo bar)); +DATA(insert OID = 927 ( oidint4ne PGUID 11 f t f 2 f 16 "910 910" 100 0 0 100 foo bar)); +DATA(insert OID = 928 ( oidint4cmp PGUID 11 f t f 2 f 23 "910 910" 100 0 0 100 foo bar)); +DATA(insert OID = 929 ( mkoidint4 PGUID 11 f t f 2 f 910 "26 23" 100 0 0 100 foo bar)); + +DATA(insert OID = 940 ( oidnamein PGUID 11 f t f 1 f 911 "0" 100 0 0 100 foo bar)); +DATA(insert OID = 941 ( oidnameout PGUID 11 f t f 1 f 19 "0" 100 0 0 100 foo bar)); +DATA(insert OID = 942 ( oidnamelt PGUID 11 f t f 2 f 16 "911 911" 100 0 0 100 foo bar)); +DATA(insert OID = 943 ( oidnamele PGUID 11 f t f 2 f 16 "911 911" 100 0 0 100 foo bar)); +DATA(insert OID = 944 ( oidnameeq PGUID 11 f t f 2 f 16 "911 911" 100 0 0 100 foo bar)); + +#define OidNameEqRegProcedure 944 + +DATA(insert OID = 945 ( oidnamege PGUID 11 f t f 2 f 16 "911 911" 100 0 0 100 foo bar)); +DATA(insert OID = 946 ( oidnamegt PGUID 11 f t f 2 f 16 "911 911" 100 0 0 100 foo bar)); +DATA(insert OID = 947 ( oidnamene PGUID 11 f t f 2 f 16 "911 911" 100 0 0 100 foo bar)); +DATA(insert OID = 948 ( oidnamecmp PGUID 11 f t f 2 f 23 "911 911" 100 0 0 100 foo bar)); +DATA(insert OID = 949 ( mkoidname PGUID 11 f t f 2 f 911 "26 19" 100 0 0 100 foo bar)); + +DATA(insert OID = 952 ( lo_open PGUID 11 f t f 2 f 23 "26 23" 100 0 0 100 foo bar )); +DATA(insert OID = 953 ( lo_close PGUID 11 f t f 1 f 23 "23" 100 0 0 100 foo bar )); +DATA(insert OID = 954 ( LOread PGUID 11 f t f 2 f 17 "23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 955 ( LOwrite PGUID 11 f t f 2 f 23 "23 17" 100 0 0 100 foo bar )); +DATA(insert OID = 956 ( lo_lseek PGUID 11 f t f 3 f 23 "23 23 23" 100 0 0 100 foo bar )); +DATA(insert OID = 957 ( lo_creat PGUID 11 f t f 1 f 26 "23" 100 0 0 100 foo bar )); +DATA(insert OID = 958 ( lo_tell PGUID 11 f t f 1 f 23 "23" 100 0 0 100 foo bar )); +DATA(insert OID = 964 ( lo_unlink PGUID 11 f t f 1 f 23 "23" 100 0 0 100 foo bar )); + +DATA(insert OID = 972 ( RegprocToOid PGUID 11 f t f 1 f 26 "24" 100 0 0 100 foo bar )); + +DATA(insert OID = 973 ( path_inter PGUID 11 f t f 2 f 16 "602 602" 100 0 10 100 foo bar )); +DATA(insert OID = 974 ( box_copy PGUID 11 f t f 1 f 603 "603" 100 0 0 100 foo bar )); +DATA(insert OID = 975 ( box_area PGUID 11 f t f 1 f 701 "603" 100 0 0 100 foo bar )); +DATA(insert OID = 976 ( box_length PGUID 11 f t f 1 f 701 "603" 100 0 0 100 foo bar )); +DATA(insert OID = 977 ( box_height PGUID 11 f t f 1 f 701 "603" 100 0 0 100 foo bar )); +DATA(insert OID = 978 ( box_distance PGUID 11 f t f 2 f 701 "603 603" 100 0 0 100 foo bar )); +DATA(insert OID = 980 ( box_intersect PGUID 11 f t f 2 f 603 "603 603" 100 0 0 100 foo bar )); +DATA(insert OID = 981 ( box_diagonal PGUID 11 f t f 1 f 601 "603" 100 0 0 100 foo bar )); +DATA(insert OID = 982 ( path_n_lt PGUID 11 f t f 2 f 16 "602 602" 100 0 0 100 foo bar )); +DATA(insert OID = 983 ( path_n_gt PGUID 11 f t f 2 f 16 "602 602" 100 0 0 100 foo bar )); +DATA(insert OID = 984 ( path_n_eq PGUID 11 f t f 2 f 16 "602 602" 100 0 0 100 foo bar )); +DATA(insert OID = 985 ( path_n_le PGUID 11 f t f 2 f 16 "602 602" 100 0 0 100 foo bar )); +DATA(insert OID = 986 ( path_n_ge PGUID 11 f t f 2 f 16 "602 602" 100 0 0 100 foo bar )); +DATA(insert OID = 987 ( path_length PGUID 11 f t f 1 f 701 "602" 100 0 1 0 foo bar )); +DATA(insert OID = 988 ( point_copy PGUID 11 f t f 1 f 600 "600" 100 0 0 100 foo bar )); +DATA(insert OID = 989 ( point_vert PGUID 11 f t f 2 f 16 "600 600" 100 0 0 100 foo bar )); +DATA(insert OID = 990 ( point_horiz PGUID 11 f t f 2 f 16 "600 600" 100 0 0 100 foo bar )); +DATA(insert OID = 991 ( point_distance PGUID 11 f t f 2 f 701 "600 600" 100 0 0 100 foo bar )); +DATA(insert OID = 992 ( point_slope PGUID 11 f t f 2 f 701 "600 600" 100 0 0 100 foo bar )); +DATA(insert OID = 993 ( lseg_construct PGUID 11 f t f 2 f 601 "600 600" 100 0 0 100 foo bar )); +DATA(insert OID = 994 ( lseg_intersect PGUID 11 f t f 2 f 16 "601 601" 100 0 0 100 foo bar )); +DATA(insert OID = 995 ( lseg_parallel PGUID 11 f t f 2 f 16 "601 601" 100 0 0 100 foo bar )); +DATA(insert OID = 996 ( lseg_perp PGUID 11 f t f 2 f 16 "601 601" 100 0 0 100 foo bar )); +DATA(insert OID = 997 ( lseg_vertical PGUID 11 f t f 1 f 16 "601" 100 0 0 100 foo bar )); +DATA(insert OID = 998 ( lseg_horizontal PGUID 11 f t f 1 f 16 "601" 100 0 0 100 foo bar )); +DATA(insert OID = 999 ( lseg_eq PGUID 11 f t f 2 f 16 "601 601" 100 0 0 100 foo bar )); + +/* OIDS 1000 - 1999 */ + +DATA(insert OID = 1029 ( NullValue PGUID 11 f t f 1 f 16 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 1030 ( NonNullValue PGUID 11 f t f 1 f 16 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 1031 ( aclitemin PGUID 11 f t f 1 f 1033 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 1032 ( aclitemout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 1035 ( aclinsert PGUID 11 f t f 2 f 1034 "1034 1033" 100 0 0 100 foo bar )); +DATA(insert OID = 1036 ( aclremove PGUID 11 f t f 2 f 1034 "1034 1033" 100 0 0 100 foo bar )); +DATA(insert OID = 1037 ( aclcontains PGUID 11 f t f 2 f 16 "1034 1033" 100 0 0 100 foo bar )); +DATA(insert OID = 1038 ( seteval PGUID 11 f t f 1 f 23 "26" 100 0 0 100 foo bar )); +#define SetEvalRegProcedure 1038 + +DATA(insert OID = 1044 ( bpcharin PGUID 11 f t f 3 f 1042 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 1045 ( bpcharout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 1046 ( varcharin PGUID 11 f t f 3 f 1043 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 1047 ( varcharout PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 1048 ( bpchareq PGUID 11 f t f 2 f 16 "1042 1042" 100 0 0 100 foo bar )); +DATA(insert OID = 1049 ( bpcharlt PGUID 11 f t f 2 f 16 "1042 1042" 100 0 0 100 foo bar )); +DATA(insert OID = 1050 ( bpcharle PGUID 11 f t f 2 f 16 "1042 1042" 100 0 0 100 foo bar )); +DATA(insert OID = 1051 ( bpchargt PGUID 11 f t f 2 f 16 "1042 1042" 100 0 0 100 foo bar )); +DATA(insert OID = 1052 ( bpcharge PGUID 11 f t f 2 f 16 "1042 1042" 100 0 0 100 foo bar )); +DATA(insert OID = 1053 ( bpcharne PGUID 11 f t f 2 f 16 "1042 1042" 100 0 0 100 foo bar )); +DATA(insert OID = 1070 ( varchareq PGUID 11 f t f 2 f 16 "1043 1043" 100 0 0 100 foo bar )); +DATA(insert OID = 1071 ( varcharlt PGUID 11 f t f 2 f 16 "1043 1043" 100 0 0 100 foo bar )); +DATA(insert OID = 1072 ( varcharle PGUID 11 f t f 2 f 16 "1043 1043" 100 0 0 100 foo bar )); +DATA(insert OID = 1073 ( varchargt PGUID 11 f t f 2 f 16 "1043 1043" 100 0 0 100 foo bar )); +DATA(insert OID = 1074 ( varcharge PGUID 11 f t f 2 f 16 "1043 1043" 100 0 0 100 foo bar )); +DATA(insert OID = 1075 ( varcharne PGUID 11 f t f 2 f 16 "1043 1043" 100 0 0 100 foo bar )); +DATA(insert OID = 1078 ( bpcharcmp PGUID 11 f t f 2 f 23 "1042 1042" 100 0 0 100 foo bar )); +DATA(insert OID = 1079 ( varcharcmp PGUID 11 f t f 2 f 23 "1043 1043" 100 0 0 100 foo bar )); +DATA(insert OID = 1080 ( hashbpchar PGUID 11 f t f 1 f 23 "1042" 100 0 0 100 foo bar )); +DATA(insert OID = 1081 ( hashvarchar PGUID 11 f t f 1 f 23 "1043" 100 0 0 100 foo bar )); + +DATA(insert OID = 1084 ( date_in PGUID 11 f t f 1 f 1082 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 1085 ( date_out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 1086 ( date_eq PGUID 11 f t f 2 f 16 "1082 1082" 100 0 0 100 foo bar )); +DATA(insert OID = 1087 ( date_lt PGUID 11 f t f 2 f 16 "1082 1082" 100 0 0 100 foo bar )); +DATA(insert OID = 1088 ( date_le PGUID 11 f t f 2 f 16 "1082 1082" 100 0 0 100 foo bar )); +DATA(insert OID = 1089 ( date_gt PGUID 11 f t f 2 f 16 "1082 1082" 100 0 0 100 foo bar )); +DATA(insert OID = 1090 ( date_ge PGUID 11 f t f 2 f 16 "1082 1082" 100 0 0 100 foo bar )); +DATA(insert OID = 1091 ( date_ne PGUID 11 f t f 2 f 16 "1082 1082" 100 0 0 100 foo bar )); +DATA(insert OID = 1092 ( date_cmp PGUID 11 f t f 2 f 23 "1082 1082" 100 0 0 100 foo bar )); + +DATA(insert OID = 1099 ( time_in PGUID 11 f t f 1 f 1083 "0" 100 0 0 100 foo bar )); + +/* OIDS 1100 - 1199 */ +DATA(insert OID = 1100 ( time_out PGUID 11 f t f 1 f 23 "0" 100 0 0 100 foo bar )); +DATA(insert OID = 1101 ( time_eq PGUID 11 f t f 2 f 16 "1083 1083" 100 0 0 100 foo bar )); +DATA(insert OID = 1102 ( time_lt PGUID 11 f t f 2 f 16 "1083 1083" 100 0 0 100 foo bar )); +DATA(insert OID = 1103 ( time_le PGUID 11 f t f 2 f 16 "1083 1083" 100 0 0 100 foo bar )); +DATA(insert OID = 1104 ( time_gt PGUID 11 f t f 2 f 16 "1083 1083" 100 0 0 100 foo bar )); +DATA(insert OID = 1105 ( time_ge PGUID 11 f t f 2 f 16 "1083 1083" 100 0 0 100 foo bar )); +DATA(insert OID = 1106 ( time_ne PGUID 11 f t f 2 f 16 "1083 1083" 100 0 0 100 foo bar )); +DATA(insert OID = 1107 ( time_cmp PGUID 11 f t f 2 f 23 "1083 1083" 100 0 0 100 foo bar )); +DATA(insert OID = 1200 ( int42reltime PGUID 11 f t f 1 f 703 "21" 100 0 0 100 foo bar )); + +DATA(insert OID = 1230 ( char2icregexeq PGUID 11 f t f 2 f 16 "409 25" 100 0 0 100 foo bar )); +DATA(insert OID = 1231 ( char2icregexne PGUID 11 f t f 2 f 16 "409 25" 100 0 0 100 foo bar )); +DATA(insert OID = 1232 ( char4icregexeq PGUID 11 f t f 2 f 16 "410 25" 100 0 0 100 foo bar )); +DATA(insert OID = 1233 ( char4icregexne PGUID 11 f t f 2 f 16 "410 25" 100 0 0 100 foo bar )); +DATA(insert OID = 1234 ( char8icregexeq PGUID 11 f t f 2 f 16 "411 25" 100 0 0 100 foo bar )); +DATA(insert OID = 1235 ( char8icregexne PGUID 11 f t f 2 f 16 "411 25" 100 0 0 100 foo bar )); +DATA(insert OID = 1236 ( char16icregexeq PGUID 11 f t f 2 f 16 "20 25" 100 0 0 100 foo bar )); +DATA(insert OID = 1237 ( char16icregexne PGUID 11 f t f 2 f 16 "20 25" 100 0 0 100 foo bar )); +DATA(insert OID = 1238 ( texticregexeq PGUID 11 f t f 2 f 16 "25 25" 100 0 1 0 foo bar )); +DATA(insert OID = 1239 ( texticregexne PGUID 11 f t f 2 f 16 "25 25" 100 0 1 0 foo bar )); +DATA(insert OID = 1240 ( nameicregexeq PGUID 11 f t f 2 f 16 "19 25" 100 0 0 100 foo bar )); +DATA(insert OID = 1241 ( nameicregexne PGUID 11 f t f 2 f 16 "19 25" 100 0 0 100 foo bar )); + + +#include "nodes/pg_list.h" + +/* + * prototypes for functions pg_proc.c + */ +extern Oid ProcedureCreate(char* procedureName, + bool returnsSet, + char *returnTypeName, + char *languageName, + char *prosrc, + char *probin, + bool canCache, + bool trusted, + int32 byte_pct, + int32 perbyte_cpu, + int32 percall_cpu, + int32 outin_ratio, + List *argList, + CommandDest dest); + + +#endif /* PG_PROC_H */ diff --git a/src/backend/catalog/pg_rewrite.h b/src/backend/catalog/pg_rewrite.h new file mode 100644 index 00000000000..9f200746274 --- /dev/null +++ b/src/backend/catalog/pg_rewrite.h @@ -0,0 +1,64 @@ +/*------------------------------------------------------------------------- + * + * pg_rewrite.h-- + * definition of the system "rewrite-rule" relation (pg_rewrite) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_rewrite.h,v 1.1.1.1 1996/07/09 06:21:18 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_REWRITE_H +#define PG_REWRITE_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_rewrite definition. cpp turns this into + * typedef struct FormData_pg_rewrite + * ---------------- + */ +CATALOG(pg_rewrite) { + NameData rulename; + char ev_type; + Oid ev_class; + int2 ev_attr; + bool is_instead; + text ev_qual; /* VARLENA */ + text action; /* VARLENA */ +} FormData_pg_rewrite; + +/* ---------------- + * Form_pg_rewrite corresponds to a pointer to a tuple with + * the format of pg_rewrite relation. + * ---------------- + */ +typedef FormData_pg_rewrite *Form_pg_rewrite; + +/* ---------------- + * compiler constants for pg_rewrite + * ---------------- + */ +#define Natts_pg_rewrite 7 +#define Anum_pg_rewrite_rulename 1 +#define Anum_pg_rewrite_ev_type 2 +#define Anum_pg_rewrite_ev_class 3 +#define Anum_pg_rewrite_ev_attr 4 +#define Anum_pg_rewrite_is_instead 5 +#define Anum_pg_rewrite_ev_qual 6 +#define Anum_pg_rewrite_action 7 + +#endif /* PG_REWRITE_H */ diff --git a/src/backend/catalog/pg_server.h b/src/backend/catalog/pg_server.h new file mode 100644 index 00000000000..63052381959 --- /dev/null +++ b/src/backend/catalog/pg_server.h @@ -0,0 +1,56 @@ +/*------------------------------------------------------------------------- + * + * pg_server.h-- + * definition of the system "server" relation (pg_server) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_server.h,v 1.1.1.1 1996/07/09 06:21:18 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_SERVER_H +#define PG_SERVER_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_server definition. cpp turns this into + * typedef struct FormData_pg_server + * ---------------- + */ +CATALOG(pg_server) BOOTSTRAP { + NameData sername; + int2 serpid; + int2 serport; +} FormData_pg_server; + +/* ---------------- + * Form_pg_server corresponds to a pointer to a tuple with + * the format of pg_server relation. + * ---------------- + */ +typedef FormData_pg_server *Form_pg_server; + +/* ---------------- + * compiler constants for pg_server + * ---------------- + */ +#define Natts_pg_server 3 +#define Anum_pg_server_sername 1 +#define Anum_pg_server_serpid 2 +#define Anum_pg_server_serport 3 + +#endif /* PG_SERVER_H */ diff --git a/src/backend/catalog/pg_statistic.h b/src/backend/catalog/pg_statistic.h new file mode 100644 index 00000000000..d8f0c19dffa --- /dev/null +++ b/src/backend/catalog/pg_statistic.h @@ -0,0 +1,60 @@ +/*------------------------------------------------------------------------- + * + * pg_statistic.h-- + * definition of the system "statistic" relation (pg_statistic) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_statistic.h,v 1.1.1.1 1996/07/09 06:21:18 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_STATISTIC_H +#define PG_STATISTIC_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_statistic definition. cpp turns this into + * typedef struct FormData_pg_statistic + * ---------------- + */ +CATALOG(pg_statistic) { + Oid starelid; + int2 staattnum; + Oid staop; + text stalokey; /* VARIABLE LENGTH FIELD */ + text stahikey; /* VARIABLE LENGTH FIELD */ +} FormData_pg_statistic; + +/* ---------------- + * Form_pg_statistic corresponds to a pointer to a tuple with + * the format of pg_statistic relation. + * ---------------- + */ +typedef FormData_pg_statistic *Form_pg_statistic; + +/* ---------------- + * compiler constants for pg_statistic + * ---------------- + */ +#define Natts_pg_statistic 5 +#define Anum_pg_statistic_starelid 1 +#define Anum_pg_statistic_staattnum 2 +#define Anum_pg_statistic_staop 3 +#define Anum_pg_statistic_stalokey 4 +#define Anum_pg_statistic_stahikey 5 + +#endif /* PG_STATISTIC_H */ diff --git a/src/backend/catalog/pg_time.h b/src/backend/catalog/pg_time.h new file mode 100644 index 00000000000..4990f231ba1 --- /dev/null +++ b/src/backend/catalog/pg_time.h @@ -0,0 +1,41 @@ +/*------------------------------------------------------------------------- + * + * pg_time.h-- + * the system commit-time relation "pg_time" is not a "heap" relation. + * it is automatically created by the transam/ code and the + * information here is all bogus and is just here to make the + * relcache code happy. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_time.h,v 1.1.1.1 1996/07/09 06:21:18 scrappy Exp $ + * + * NOTES + * The structures and macros used by the transam/ code + * to access pg_time should some day go here -cim 6/18/90 + * + *------------------------------------------------------------------------- + */ +#ifndef PG_TIME_H +#define PG_TIME_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +CATALOG(pg_time) BOOTSTRAP { + Oid timefoo; +} FormData_pg_time; + +typedef FormData_pg_time *Form_pg_time; + +#define Natts_pg_time 1 +#define Anum_pg_time_timefoo 1 + + +#endif /* PG_TIME_H */ diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c new file mode 100644 index 00000000000..fe9baa05a44 --- /dev/null +++ b/src/backend/catalog/pg_type.c @@ -0,0 +1,595 @@ +/*------------------------------------------------------------------------- + * + * pg_type.c-- + * routines to support manipulation of the pg_type relation + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/catalog/pg_type.c,v 1.1.1.1 1996/07/09 06:21:18 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <string.h> +#include "postgres.h" + +#include "access/heapam.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "access/tupdesc.h" +#include "utils/builtins.h" +#include "utils/rel.h" +#include "utils/palloc.h" +#include "fmgr.h" +#include "utils/elog.h" +#include "parser/catalog_utils.h" + +#include "catalog/catname.h" +#include "utils/syscache.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "catalog/indexing.h" +#include "storage/lmgr.h" + +/* ---------------------------------------------------------------- + * TypeGetWithOpenRelation + * + * preforms a scan on pg_type for a type tuple with the + * given type name. + * ---------------------------------------------------------------- + * pg_type_desc -- reldesc for pg_type + * typeName -- name of type to be fetched + * defined -- has the type been defined? + */ +static Oid +TypeGetWithOpenRelation(Relation pg_type_desc, + char* typeName, + bool *defined) +{ + HeapScanDesc scan; + HeapTuple tup; + + static ScanKeyData typeKey[1] = { + { 0, Anum_pg_type_typname, NameEqualRegProcedure } + }; + + /* ---------------- + * initialize the scan key and begin a scan of pg_type + * ---------------- + */ + fmgr_info(NameEqualRegProcedure, + &typeKey[0].sk_func, &typeKey[0].sk_nargs); + typeKey[0].sk_argument = PointerGetDatum(typeName); + + scan = heap_beginscan(pg_type_desc, + 0, + SelfTimeQual, + 1, + typeKey); + + /* ---------------- + * get the type tuple, if it exists. + * ---------------- + */ + tup = heap_getnext(scan, 0, (Buffer *) 0); + + /* ---------------- + * if no type tuple exists for the given type name, then + * end the scan and return appropriate information. + * ---------------- + */ + if (! HeapTupleIsValid(tup)) { + heap_endscan(scan); + *defined = false; + return InvalidOid; + } + + /* ---------------- + * here, the type tuple does exist so we pull information from + * the typisdefined field of the tuple and return the tuple's + * oid, which is the oid of the type. + * ---------------- + */ + heap_endscan(scan); + *defined = (bool) ((TypeTupleForm) GETSTRUCT(tup))->typisdefined; + + return + tup->t_oid; +} + +/* ---------------------------------------------------------------- + * TypeGet + * + * Finds the ObjectId of a type, even if uncommitted; "defined" + * is only set if the type has actually been defined, i.e., if + * the type tuple is not a shell. + * + * Note: the meat of this function is now in the function + * TypeGetWithOpenRelation(). -cim 6/15/90 + * + * Also called from util/remove.c + * ---------------------------------------------------------------- + */ +Oid +TypeGet(char* typeName, /* name of type to be fetched */ + bool *defined) /* has the type been defined? */ +{ + Relation pg_type_desc; + Oid typeoid; + + /* ---------------- + * open the pg_type relation + * ---------------- + */ + pg_type_desc = heap_openr(TypeRelationName); + + /* ---------------- + * scan the type relation for the information we want + * ---------------- + */ + typeoid = TypeGetWithOpenRelation(pg_type_desc, + typeName, + defined); + + /* ---------------- + * close the type relation and return the type oid. + * ---------------- + */ + heap_close(pg_type_desc); + + return + typeoid; +} + +/* ---------------------------------------------------------------- + * TypeShellMakeWithOpenRelation + * + * ---------------------------------------------------------------- + */ +Oid +TypeShellMakeWithOpenRelation(Relation pg_type_desc, char *typeName) +{ + register int i; + HeapTuple tup; + Datum values[ Natts_pg_type ]; + char nulls[ Natts_pg_type ]; + Oid typoid; + TupleDesc tupDesc; + + /* ---------------- + * initialize our nulls[] and values[] arrays + * ---------------- + */ + for (i = 0; i < Natts_pg_type; ++i) { + nulls[i] = ' '; + values[i] = (Datum)NULL; /* redundant, but safe */ + } + + /* ---------------- + * initialize values[] with the type name and + * ---------------- + */ + i = 0; + values[i++] = (Datum) typeName; /* 1 */ + values[i++] = (Datum) InvalidOid; /* 2 */ + values[i++] = (Datum) (int16) 0; /* 3 */ + values[i++] = (Datum) (int16) 0; /* 4 */ + values[i++] = (Datum) (bool) 0; /* 5 */ + values[i++] = (Datum) (bool) 0; /* 6 */ + values[i++] = (Datum) (bool) 0; /* 7 */ + values[i++] = (Datum) (bool) 0; /* 8 */ + values[i++] = (Datum) InvalidOid; /* 9 */ + values[i++] = (Datum) InvalidOid; /* 10 */ + values[i++] = (Datum) InvalidOid; /* 11 */ + values[i++] = (Datum) InvalidOid; /* 12 */ + values[i++] = (Datum) InvalidOid; /* 13 */ + values[i++] = (Datum) InvalidOid; /* 14 */ + values[i++] = (Datum) 'i'; /* 15 */ + + /* + * ... and fill typdefault with a bogus value + */ + values[i++] = + (Datum)fmgr(TextInRegProcedure, typeName); /* 15 */ + + /* ---------------- + * create a new type tuple with FormHeapTuple + * ---------------- + */ + tupDesc = pg_type_desc->rd_att; + + tup = heap_formtuple(tupDesc, values, nulls); + + /* ---------------- + * insert the tuple in the relation and get the tuple's oid. + * ---------------- + */ + heap_insert(pg_type_desc, tup); + typoid = tup->t_oid; + + if (RelationGetRelationTupleForm(pg_type_desc)->relhasindex) + { + Relation idescs[Num_pg_type_indices]; + + CatalogOpenIndices(Num_pg_type_indices, Name_pg_type_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_type_indices, pg_type_desc, tup); + CatalogCloseIndices(Num_pg_type_indices, idescs); + } + /* ---------------- + * free the tuple and return the type-oid + * ---------------- + */ + pfree(tup); + + return + typoid; +} + +/* ---------------------------------------------------------------- + * TypeShellMake + * + * This procedure inserts a "shell" tuple into the type + * relation. The type tuple inserted has invalid values + * and in particular, the "typisdefined" field is false. + * + * This is used so that a tuple exists in the catalogs. + * The invalid fields should be fixed up sometime after + * this routine is called, and then the "typeisdefined" + * field is set to true. -cim 6/15/90 + * ---------------------------------------------------------------- + */ +Oid +TypeShellMake(char *typeName) +{ + Relation pg_type_desc; + Oid typoid; + + Assert(PointerIsValid(typeName)); + + /* ---------------- + * open pg_type + * ---------------- + */ + pg_type_desc = heap_openr(TypeRelationName); + + /* ---------------- + * insert the shell tuple + * ---------------- + */ + typoid = TypeShellMakeWithOpenRelation(pg_type_desc, typeName); + + /* ---------------- + * close pg_type and return the tuple's oid. + * ---------------- + */ + heap_close(pg_type_desc); + + return + typoid; +} + +/* ---------------------------------------------------------------- + * TypeCreate + * + * This does all the necessary work needed to define a new type. + * ---------------------------------------------------------------- + */ +Oid +TypeCreate(char *typeName, + Oid relationOid, /* only for 'c'atalog typeTypes */ + int16 internalSize, + int16 externalSize, + char typeType, + char typDelim, + char *inputProcedure, + char *outputProcedure, + char *sendProcedure, + char *receiveProcedure, + char *elementTypeName, + char *defaultTypeValue, /* internal rep */ + bool passedByValue, + char alignment) +{ + register i, j; + Relation pg_type_desc; + HeapScanDesc pg_type_scan; + + Oid typeObjectId; + Oid elementObjectId = InvalidOid; + + HeapTuple tup; + char nulls[Natts_pg_type]; + char replaces[Natts_pg_type]; + Datum values[Natts_pg_type]; + + Buffer buffer; + char *procname; + char *procs[4]; + bool defined; + ItemPointerData itemPointerData; + TupleDesc tupDesc; + + Oid argList[8]; + + + static ScanKeyData typeKey[1] = { + { 0, Anum_pg_type_typname, NameEqualRegProcedure } + }; + + fmgr_info(NameEqualRegProcedure, + &typeKey[0].sk_func, &typeKey[0].sk_nargs); + + /* ---------------- + * check that the type is not already defined. + * ---------------- + */ + typeObjectId = TypeGet(typeName, &defined); + if (OidIsValid(typeObjectId) && defined) { + elog(WARN, "TypeCreate: type %s already defined", typeName); + } + + /* ---------------- + * if this type has an associated elementType, then we check that + * it is defined. + * ---------------- + */ + if (elementTypeName) { + elementObjectId = TypeGet(elementTypeName, &defined); + if (!defined) { + elog(WARN, "TypeCreate: type %s is not defined", elementTypeName); + } + } + + /* ---------------- + * XXX comment me + * ---------------- + */ + if (externalSize == 0) { + externalSize = -1; /* variable length */ + } + + /* ---------------- + * initialize arrays needed by FormHeapTuple + * ---------------- + */ + for (i = 0; i < Natts_pg_type; ++i) { + nulls[i] = ' '; + replaces[i] = 'r'; + values[i] = (Datum)NULL; /* redundant, but nice */ + } + + /* + * XXX + * + * Do this so that user-defined types have size -1 instead of zero if + * they are variable-length - this is so that everything else in the + * backend works. + */ + + if (internalSize == 0) + internalSize = -1; + + /* ---------------- + * initialize the values[] information + * ---------------- + */ + i = 0; + values[i++] = PointerGetDatum(typeName); /* 1 */ + values[i++] = (Datum) GetUserId(); /* 2 */ + values[i++] = (Datum) internalSize; /* 3 */ + values[i++] = (Datum) externalSize; /* 4 */ + values[i++] = (Datum) passedByValue; /* 5 */ + values[i++] = (Datum) typeType; /* 6 */ + values[i++] = (Datum) (bool) 1; /* 7 */ + values[i++] = (Datum) typDelim; /* 8 */ + values[i++] = (Datum) (typeType == 'c' ? relationOid : InvalidOid); /* 9 */ + values[i++] = (Datum) elementObjectId; /* 10 */ + + /* + * arguments to type input and output functions must be 0 + */ + memset(argList, 0, 8 * sizeof(Oid)); + + procs[0] = inputProcedure; + procs[1] = outputProcedure; + procs[2] = (receiveProcedure) ? receiveProcedure : inputProcedure; + procs[3] = (sendProcedure) ? sendProcedure : outputProcedure; + + for (j = 0; j < 4; ++j) { + procname = procs[j]; + + tup = SearchSysCacheTuple(PRONAME, + PointerGetDatum(procname), + Int32GetDatum(1), + PointerGetDatum(argList), + 0); + + if (!HeapTupleIsValid(tup)) { + /* + * it is possible for the input/output procedure + * to take two arguments, where the second argument + * is the element type (eg array_in/array_out) + */ + if (OidIsValid(elementObjectId)) { + tup = SearchSysCacheTuple(PRONAME, + PointerGetDatum(procname), + Int32GetDatum(2), + PointerGetDatum(argList), + 0); + } + if (!HeapTupleIsValid(tup)) { + func_error("TypeCreate", procname, 1, (int*)argList); + } + } + + values[i++] = (Datum)tup->t_oid; /* 11 - 14 */ + } + + /* ---------------- + * set default alignment + * ---------------- + */ + values[i++] = (Datum)alignment; /* 15 */ + + /* ---------------- + * initialize the default value for this type. + * ---------------- + */ + values[i] = (Datum)fmgr(TextInRegProcedure, /* 16 */ + PointerIsValid(defaultTypeValue) + ? defaultTypeValue : "-"); /* XXX default typdefault */ + + /* ---------------- + * open pg_type and begin a scan for the type name. + * ---------------- + */ + pg_type_desc = heap_openr(TypeRelationName); + + /* ----------------- + * Set a write lock initially so as not upgrade a read to a write + * when the heap_insert() or heap_replace() is called. + * ----------------- + */ + RelationSetLockForWrite(pg_type_desc); + + typeKey[0].sk_argument = PointerGetDatum(typeName); + pg_type_scan = heap_beginscan(pg_type_desc, + 0, + SelfTimeQual, + 1, + typeKey); + + /* ---------------- + * define the type either by adding a tuple to the type + * relation, or by updating the fields of the "shell" tuple + * already there. + * ---------------- + */ + tup = heap_getnext(pg_type_scan, 0, &buffer); + if (HeapTupleIsValid(tup)) { + tup = heap_modifytuple(tup, + buffer, + pg_type_desc, + values, + nulls, + replaces); + + /* XXX may not be necessary */ + ItemPointerCopy(&tup->t_ctid, &itemPointerData); + + setheapoverride(true); + (void) heap_replace(pg_type_desc, &itemPointerData, tup); + setheapoverride(false); + + typeObjectId = tup->t_oid; + } else { + tupDesc = pg_type_desc->rd_att; + + tup = heap_formtuple(tupDesc, + values, + nulls); + + heap_insert(pg_type_desc, tup); + + typeObjectId = tup->t_oid; + } + + /* ---------------- + * finish up + * ---------------- + */ + heap_endscan(pg_type_scan); + + if (RelationGetRelationTupleForm(pg_type_desc)->relhasindex) + { + Relation idescs[Num_pg_type_indices]; + + CatalogOpenIndices(Num_pg_type_indices, Name_pg_type_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_type_indices, pg_type_desc, tup); + CatalogCloseIndices(Num_pg_type_indices, idescs); + } + RelationUnsetLockForWrite(pg_type_desc); + heap_close(pg_type_desc); + + + return + typeObjectId; +} + +/* ---------------------------------------------------------------- + * TypeRename + * + * This renames a type + * ---------------------------------------------------------------- + */ +void +TypeRename(char *oldTypeName, char *newTypeName) +{ + Relation pg_type_desc; + Relation idescs[Num_pg_type_indices]; + Oid type_oid; + HeapTuple tup; + bool defined; + ItemPointerData itemPointerData; + + /* check that that the new type is not already defined */ + type_oid = TypeGet(newTypeName, &defined); + if (OidIsValid(type_oid) && defined) { + elog(WARN, "TypeRename: type %s already defined", newTypeName); + } + + /* get the type tuple from the catalog index scan manager */ + pg_type_desc = heap_openr(TypeRelationName); + tup = TypeNameIndexScan(pg_type_desc, oldTypeName); + + /* ---------------- + * change the name of the type + * ---------------- + */ + if (HeapTupleIsValid(tup)) { + + namestrcpy(& (((TypeTupleForm) GETSTRUCT(tup))->typname),newTypeName); + + ItemPointerCopy(&tup->t_ctid, &itemPointerData); + + setheapoverride(true); + heap_replace(pg_type_desc, &itemPointerData, tup); + setheapoverride(false); + + /* update the system catalog indices */ + CatalogOpenIndices(Num_pg_type_indices, Name_pg_type_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_type_indices, pg_type_desc, tup); + CatalogCloseIndices(Num_pg_type_indices, idescs); + + /* all done */ + pfree(tup); + + } else { + elog(WARN, "TypeRename: type %s not defined", oldTypeName); + } + + /* finish up */ + heap_close(pg_type_desc); +} + +/* + * makeArrayTypeName(typeName); + * - given a base type name, make an array of type name out of it + * + * the CALLER is responsible for pfreeing the + */ + +char* +makeArrayTypeName(char* typeName) +{ + char *arr; + + if (!typeName) return NULL; + arr = palloc (strlen(typeName) + 2); + arr[0] = '_'; + strcpy(arr+1, typeName); + + return arr; + +} diff --git a/src/backend/catalog/pg_type.h b/src/backend/catalog/pg_type.h new file mode 100644 index 00000000000..dc3fe94e8aa --- /dev/null +++ b/src/backend/catalog/pg_type.h @@ -0,0 +1,267 @@ +/*------------------------------------------------------------------------- + * + * pg_type.h-- + * definition of the system "type" relation (pg_type) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_type.h,v 1.1.1.1 1996/07/09 06:21:18 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_TYPE_H +#define PG_TYPE_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" +#include "utils/rel.h" /* for Relation */ + +/* ---------------- + * pg_type definition. cpp turns this into + * typedef struct FormData_pg_type + * ---------------- + */ +CATALOG(pg_type) BOOTSTRAP { + NameData typname; + Oid typowner; + int2 typlen; + int2 typprtlen; + bool typbyval; + char typtype; + bool typisdefined; + char typdelim; + Oid typrelid; + Oid typelem; + regproc typinput; + regproc typoutput; + regproc typreceive; + regproc typsend; + char typalign; /* alignment (c=char, s=short, i=int, d=double) */ + text typdefault; /* VARIABLE LENGTH FIELD */ +} TypeTupleFormData; + +/* ---------------- + * Form_pg_type corresponds to a pointer to a tuple with + * the format of pg_type relation. + * ---------------- + */ +typedef TypeTupleFormData *TypeTupleForm; + +/* ---------------- + * compiler constants for pg_type + * ---------------- + */ +#define Natts_pg_type 16 +#define Anum_pg_type_typname 1 +#define Anum_pg_type_typowner 2 +#define Anum_pg_type_typlen 3 +#define Anum_pg_type_typprtlen 4 +#define Anum_pg_type_typbyval 5 +#define Anum_pg_type_typtype 6 +#define Anum_pg_type_typisdefined 7 +#define Anum_pg_type_typdelim 8 +#define Anum_pg_type_typrelid 9 +#define Anum_pg_type_typelem 10 +#define Anum_pg_type_typinput 11 +#define Anum_pg_type_typoutput 12 +#define Anum_pg_type_typreceive 13 +#define Anum_pg_type_typsend 14 +#define Anum_pg_type_typalign 15 +#define Anum_pg_type_typdefault 16 + +/* ---------------- + * initial contents of pg_type + * ---------------- + */ + +/* keep the following ordered by OID so that later changes can be made easier*/ + +/* OIDS 1 - 99 */ +DATA(insert OID = 16 ( bool PGUID 1 1 t b t \054 0 0 boolin boolout boolin boolout c _null_ )); + +#define BOOLOID 16 + +DATA(insert OID = 17 ( bytea PGUID -1 -1 f b t \054 0 18 byteain byteaout byteain byteaout i _null_ )); +DATA(insert OID = 18 ( char PGUID 1 1 t b t \054 0 0 charin charout charin charout c _null_ )); + +DATA(insert OID = 19 ( name PGUID NAMEDATALEN NAMEDATALEN f b t \054 0 18 namein nameout namein nameout d _null_ )); +DATA(insert OID = 20 ( char16 PGUID 16 16 f b t \054 0 18 char16in char16out char16in char16out i _null_ )); +/*DATA(insert OID = 20 ( dt PGUID 4 10 t b t \054 0 0 dtin dtout dtin dtout i _null_ )); */ +DATA(insert OID = 21 ( int2 PGUID 2 5 t b t \054 0 0 int2in int2out int2in int2out s _null_ )); + +#define INT2OID 21 + +DATA(insert OID = 22 ( int28 PGUID 16 50 f b t \054 0 21 int28in int28out int28in int28out i _null_ )); + +/* + * XXX -- the implementation of int28's in postgres is a hack, and will + * go away someday. until that happens, there is a case (in the + * catalog cache management code) where we need to step gingerly + * over piles of int28's on the sidewalk. in order to do so, we + * need the OID of the int28 tuple from pg_type. + */ + +#define INT28OID 22 + + +DATA(insert OID = 23 ( int4 PGUID 4 10 t b t \054 0 0 int4in int4out int4in int4out i _null_ )); + +#define INT4OID 23 + +DATA(insert OID = 24 ( regproc PGUID 4 16 t b t \054 0 0 regprocin regprocout regprocin regprocout i _null_ )); +DATA(insert OID = 25 ( text PGUID -1 -1 f b t \054 0 18 textin textout textin textout i _null_ )); +DATA(insert OID = 26 ( oid PGUID 4 10 t b t \054 0 0 int4in int4out int4in int4out i _null_ )); + +#define OIDOID 26 + +DATA(insert OID = 27 ( tid PGUID 6 19 f b t \054 0 0 tidin tidout tidin tidout i _null_ )); +DATA(insert OID = 28 ( xid PGUID 4 12 t b t \054 0 0 xidin xidout xidin xidout i _null_ )); +DATA(insert OID = 29 ( cid PGUID 2 3 t b t \054 0 0 cidin cidout cidin cidout s _null_ )); +DATA(insert OID = 30 ( oid8 PGUID 32 89 f b t \054 0 26 oid8in oid8out oid8in oid8out i _null_ )); +DATA(insert OID = 32 ( SET PGUID -1 -1 f r t \054 0 -1 textin textout textin textout i _null_ )); + +DATA(insert OID = 71 ( pg_type PGUID 1 1 t b t \054 71 0 foo bar foo bar c _null_)); +DATA(insert OID = 75 ( pg_attribute PGUID 1 1 t b t \054 75 0 foo bar foo bar c _null_)); +DATA(insert OID = 76 ( pg_demon PGUID 1 1 t b t \054 76 0 foo bar foo bar c _null_)); +DATA(insert OID = 80 ( pg_magic PGUID 1 1 t b t \054 80 0 foo bar foo bar c _null_)); +DATA(insert OID = 81 ( pg_proc PGUID 1 1 t b t \054 81 0 foo bar foo bar c _null_)); +DATA(insert OID = 82 ( pg_server PGUID 1 1 t b t \054 82 0 foo bar foo bar c _null_)); +DATA(insert OID = 83 ( pg_class PGUID 1 1 t b t \054 83 0 foo bar foo bar c _null_)); +DATA(insert OID = 86 ( pg_user PGUID 1 1 t b t \054 86 0 foo bar foo bar c _null_)); +DATA(insert OID = 87 ( pg_group PGUID 1 1 t b t \054 87 0 foo bar foo bar c _null_)); +DATA(insert OID = 88 ( pg_database PGUID 1 1 t b t \054 88 0 foo bar foo bar c _null_)); +DATA(insert OID = 89 ( pg_defaults PGUID 1 1 t b t \054 89 0 foo bar foo bar c _null_)); +DATA(insert OID = 90 ( pg_variable PGUID 1 1 t b t \054 90 0 foo bar foo bar c _null_)); +DATA(insert OID = 99 ( pg_log PGUID 1 1 t b t \054 99 0 foo bar foo bar c _null_)); + +/* OIDS 100 - 199 */ + +DATA(insert OID = 100 ( pg_time PGUID 1 1 t b t \054 100 0 foo bar foo bar c _null_)); +DATA(insert OID = 101 ( pg_time PGUID 1 1 t b t \054 101 0 foo bar foo bar c _null_)); + +/* OIDS 200 - 299 */ + +DATA(insert OID = 210 ( smgr PGUID 2 12 t b t \054 0 -1 smgrin smgrout smgrin smgrout s _null_ )); + +/* OIDS 300 - 399 */ + +/* OIDS 400 - 499 */ +DATA(insert OID = 409 ( char2 PGUID 2 2 t b t \054 0 18 char2in char2out char2in char2out s _null_ )); +DATA(insert OID = 410 ( char4 PGUID 4 4 t b t \054 0 18 char4in char4out char4in char4out i _null_ )); +DATA(insert OID = 411 ( char8 PGUID 8 8 f b t \054 0 18 char8in char8out char8in char8out i _null_ )); + +/* OIDS 500 - 599 */ + +/* OIDS 600 - 699 */ +DATA(insert OID = 600 ( point PGUID 16 24 f b t \054 0 701 point_in point_out point_in point_out d _null_ )); +DATA(insert OID = 601 ( lseg PGUID 32 48 f b t \054 0 600 lseg_in lseg_out lseg_in lseg_out d _null_ )); +DATA(insert OID = 602 ( path PGUID -1 -1 f b t \054 0 600 path_in path_out path_in path_out d _null_ )); +DATA(insert OID = 603 ( box PGUID 32 100 f b t \073 0 600 box_in box_out box_in box_out d _null_ )); +DATA(insert OID = 604 ( polygon PGUID -1 -1 f b t \054 0 -1 poly_in poly_out poly_in poly_out d _null_ )); +DATA(insert OID = 605 ( filename PGUID 256 -1 f b t \054 0 18 filename_in filename_out filename_in filename_out i _null_ )); + +/* OIDS 700 - 799 */ + +#define FLOAT4OID 700 + +DATA(insert OID = 700 ( float4 PGUID 4 12 f b t \054 0 0 float4in float4out float4in float4out i _null_ )); + + +#define FLOAT8OID 701 + +DATA(insert OID = 701 ( float8 PGUID 8 24 f b t \054 0 0 float8in float8out float8in float8out d _null_ )); +DATA(insert OID = 702 ( abstime PGUID 4 20 t b t \054 0 0 nabstimein nabstimeout nabstimein nabstimeout i _null_ )); +DATA(insert OID = 703 ( reltime PGUID 4 20 t b t \054 0 0 reltimein reltimeout reltimein reltimeout i _null_ )); +DATA(insert OID = 704 ( tinterval PGUID 12 47 f b t \054 0 0 tintervalin tintervalout tintervalin tintervalout i _null_ )); +DATA(insert OID = 705 ( unknown PGUID -1 -1 f b t \054 0 18 textin textout textin textout i _null_ )); + +#define UNKNOWNOID 705 + +/* OIDS 800 - 899 */ +DATA(insert OID = 810 ( oidint2 PGUID 6 20 f b t \054 0 0 oidint2in oidint2out oidint2in oidint2out i _null_ )); + +/* OIDS 900 - 999 */ +DATA(insert OID = 910 ( oidint4 PGUID 8 20 f b t \054 0 0 oidint4in oidint4out oidint4in oidint4out i _null_ )); +DATA(insert OID = 911 ( oidname PGUID OIDNAMELEN OIDNAMELEN f b t \054 0 0 oidnamein oidnameout oidnamein oidnameout i _null_ )); + +/* OIDS 1000 - 1099 */ +DATA(insert OID = 1000 ( _bool PGUID -1 -1 f b t \054 0 16 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1001 ( _bytea PGUID -1 -1 f b t \054 0 17 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1002 ( _char PGUID -1 -1 f b t \054 0 18 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1003 ( _name PGUID -1 -1 f b t \054 0 19 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1004 ( _char16 PGUID -1 -1 f b t \054 0 19 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1005 ( _int2 PGUID -1 -1 f b t \054 0 21 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1006 ( _int28 PGUID -1 -1 f b t \054 0 22 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1007 ( _int4 PGUID -1 -1 f b t \054 0 23 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1008 ( _regproc PGUID -1 -1 f b t \054 0 24 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1009 ( _text PGUID -1 -1 f b t \054 0 25 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1028 ( _oid PGUID -1 -1 f b t \054 0 26 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1010 ( _tid PGUID -1 -1 f b t \054 0 27 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1011 ( _xid PGUID -1 -1 f b t \054 0 28 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1012 ( _cid PGUID -1 -1 f b t \054 0 29 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1013 ( _oid8 PGUID -1 -1 f b t \054 0 30 array_in array_out array_in array_out i _null_ )); +/*DATA(insert OID = 1014 ( _lock PGUID -1 -1 f b t \054 0 31 array_in array_out array_in array_out i _null_ ));*/ +DATA(insert OID = 1015 ( _stub PGUID -1 -1 f b t \054 0 33 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1016 ( _ref PGUID -1 -1 f b t \054 0 591 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1017 ( _point PGUID -1 -1 f b t \054 0 600 array_in array_out array_in array_out d _null_ )); +DATA(insert OID = 1018 ( _lseg PGUID -1 -1 f b t \054 0 601 array_in array_out array_in array_out d _null_ )); +DATA(insert OID = 1019 ( _path PGUID -1 -1 f b t \054 0 602 array_in array_out array_in array_out d _null_ )); +DATA(insert OID = 1020 ( _box PGUID -1 -1 f b t \073 0 603 array_in array_out array_in array_out d _null_ )); +DATA(insert OID = 1021 ( _float4 PGUID -1 -1 f b t \054 0 700 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1022 ( _float8 PGUID -1 -1 f b t \054 0 701 array_in array_out array_in array_out d _null_ )); +DATA(insert OID = 1023 ( _abstime PGUID -1 -1 f b t \054 0 702 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1024 ( _reltime PGUID -1 -1 f b t \054 0 703 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1025 ( _tinterval PGUID -1 -1 f b t \054 0 704 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1026 ( _filename PGUID -1 -1 f b t \054 0 605 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1027 ( _polygon PGUID -1 -1 f b t \054 0 604 array_in array_out array_in array_out d _null_ )); +/* Note: the size of an aclitem needs to match sizeof(AclItem) in acl.h */ +DATA(insert OID = 1033 ( aclitem PGUID 8 -1 f b t \054 0 0 aclitemin aclitemout aclitemin aclitemout i _null_ )); +DATA(insert OID = 1034 ( _aclitem PGUID -1 -1 f b t \054 0 1033 array_in array_out array_in array_out i _null_ )); + +DATA(insert OID = 1039 ( _char2 PGUID -1 -1 f b t \054 0 409 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1040 ( _char4 PGUID -1 -1 f b t \054 0 410 array_in array_out array_in array_out i _null_ )); +DATA(insert OID = 1041 ( _char8 PGUID -1 -1 f b t \054 0 411 array_in array_out array_in array_out i _null_ )); + +#define BPCHAROID 1042 +DATA(insert OID = 1042 ( bpchar PGUID -1 -1 f b t \054 0 18 bpcharin bpcharout bpcharin bpcharout i _null_ )); +#define VARCHAROID 1043 +DATA(insert OID = 1043 ( varchar PGUID -1 -1 f b t \054 0 18 varcharin varcharout varcharin varcharout i _null_ )); + +DATA(insert OID = 1082 ( date PGUID 4 10 t b t \054 0 0 date_in date_out date_in date_out i _null_ )); +DATA(insert OID = 1083 ( time PGUID 8 16 f b t \054 0 0 time_in time_out time_in time_out i _null_ )); +/* + * prototypes for functions in pg_type.c + */ +extern Oid TypeGet(char *typeName, bool *defined); +extern Oid TypeShellMakeWithOpenRelation(Relation pg_type_desc, + char *typeName); +extern Oid TypeShellMake(char *typeName); +extern Oid TypeCreate(char *typeName, + Oid relationOid, + int16 internalSize, + int16 externalSize, + char typeType, + char typDelim, + char *inputProcedure, + char *outputProcedure, + char *sendProcedure, + char *receiveProcedure, + char *elementTypeName, + char *defaultTypeValue, + bool passedByValue, char alignment); +extern void TypeRename(char *oldTypeName, char *newTypeName); +extern char *makeArrayTypeName(char *typeName); + + +#endif /* PG_TYPE_H */ diff --git a/src/backend/catalog/pg_user.h b/src/backend/catalog/pg_user.h new file mode 100644 index 00000000000..25fd02cc0c7 --- /dev/null +++ b/src/backend/catalog/pg_user.h @@ -0,0 +1,99 @@ +/*------------------------------------------------------------------------- + * + * pg_user.h-- + * definition of the system "user" relation (pg_user) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_user.h,v 1.1.1.1 1996/07/09 06:21:18 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_USER_H +#define PG_USER_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +/* ---------------- + * pg_user definition. cpp turns this into + * typedef struct FormData_pg_user + * ---------------- + */ +CATALOG(pg_user) BOOTSTRAP { + NameData usename; + int4 usesysid; + bool usecreatedb; + bool usetrace; + bool usesuper; + bool usecatupd; +} FormData_pg_user; + +/* ---------------- + * Form_pg_user corresponds to a pointer to a tuple with + * the format of pg_user relation. + * ---------------- + */ +typedef FormData_pg_user *Form_pg_user; + +/* ---------------- + * compiler constants for pg_user + * ---------------- + */ +#define Natts_pg_user 6 +#define Anum_pg_user_usename 1 +#define Anum_pg_user_usesysid 2 +#define Anum_pg_user_usecreatedb 3 +#define Anum_pg_user_usetrace 4 +#define Anum_pg_user_usesuper 5 +#define Anum_pg_user_usecatupd 6 + +/* ---------------- + * initial contents of pg_user + * ---------------- + */ +DATA(insert OID = 0 ( postgres PGUID t t t t )); + +BKI_BEGIN +#ifdef ALLOW_PG_GROUP +BKI_END + +DATA(insert OID = 0 ( mike 799 t t t t )); +DATA(insert OID = 0 ( mao 1806 t t t t )); +DATA(insert OID = 0 ( hellers 1089 t t t t )); +DATA(insert OID = 0 ( joey 5209 t t t t )); +DATA(insert OID = 0 ( jolly 5443 t t t t )); +DATA(insert OID = 0 ( sunita 6559 t t t t )); +DATA(insert OID = 0 ( paxson 3029 t t t t )); +DATA(insert OID = 0 ( marc 2435 t t t t )); +DATA(insert OID = 0 ( jiangwu 6124 t t t t )); +DATA(insert OID = 0 ( aoki 2360 t t t t )); +DATA(insert OID = 0 ( avi 31080 t t t t )); +DATA(insert OID = 0 ( kristin 1123 t t t t )); +DATA(insert OID = 0 ( andrew 5229 t t t t )); +DATA(insert OID = 0 ( nobuko 5493 t t t t )); +DATA(insert OID = 0 ( hartzell 6676 t t t t )); +DATA(insert OID = 0 ( devine 6724 t t t t )); +DATA(insert OID = 0 ( boris 6396 t t t t )); +DATA(insert OID = 0 ( sklower 354 t t t t )); +DATA(insert OID = 0 ( marcel 31113 t t t t )); +DATA(insert OID = 0 ( ginger 3692 t t t t )); +DATA(insert OID = 0 ( woodruff 31026 t t t t )); +DATA(insert OID = 0 ( searcher 8261 t t t t )); + +BKI_BEGIN +#endif /* ALLOW_PG_GROUP */ +BKI_END + +#endif /* PG_USER_H */ diff --git a/src/backend/catalog/pg_variable.h b/src/backend/catalog/pg_variable.h new file mode 100644 index 00000000000..d38a1185740 --- /dev/null +++ b/src/backend/catalog/pg_variable.h @@ -0,0 +1,40 @@ +/*------------------------------------------------------------------------- + * + * pg_variable.h-- + * the system variable relation "pg_variable" is not a "heap" relation. + * it is automatically created by the transam/ code and the + * information here is all bogus and is just here to make the + * relcache code happy. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_variable.h,v 1.1.1.1 1996/07/09 06:21:18 scrappy Exp $ + * + * NOTES + * The structures and macros used by the transam/ code + * to access pg_variable should someday go here -cim 6/18/90 + * + *------------------------------------------------------------------------- + */ +#ifndef PG_VARIABLE_H +#define PG_VARIABLE_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" + +CATALOG(pg_variable) BOOTSTRAP { + Oid varfoo; +} FormData_pg_variable; + +typedef FormData_pg_variable *Form_pg_variable; + +#define Natts_pg_variable 1 +#define Anum_pg_variable_varfoo 1 + +#endif /* PG_VARIABLE_H */ diff --git a/src/backend/catalog/pg_version.h b/src/backend/catalog/pg_version.h new file mode 100644 index 00000000000..fea795bd492 --- /dev/null +++ b/src/backend/catalog/pg_version.h @@ -0,0 +1,58 @@ +/*------------------------------------------------------------------------- + * + * pg_version.h-- + * definition of the system "version" relation (pg_version) + * along with the relation's initial contents. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pg_version.h,v 1.1.1.1 1996/07/09 06:21:18 scrappy Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_VERSION_H +#define PG_VERSION_H + +/* ---------------- + * postgres.h contains the system type definintions and the + * CATALOG(), BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ +#include "postgres.h" +#include "utils/nabstime.h" + +/* ---------------- + * pg_version definition. cpp turns this into + * typedef struct FormData_pg_version + * ---------------- + */ +CATALOG(pg_version) { + Oid verrelid; + Oid verbaseid; + int4 vertime; /* really should be some abstime */ +} FormData_pg_version; + +/* ---------------- + * Form_pg_version corresponds to a pointer to a tuple with + * the format of pg_version relation. + * ---------------- + */ +typedef FormData_pg_version *VersionTupleForm; + +/* ---------------- + * compiler constants for pg_version + * ---------------- + */ +#define Natts_pg_version 3 +#define Anum_pg_version_verrelid 1 +#define Anum_pg_version_verbaseid 2 +#define Anum_pg_version_vertime 3 + + +#endif /* PG_VERSION_H */ diff --git a/src/backend/catalog/unused_oids b/src/backend/catalog/unused_oids new file mode 100644 index 00000000000..9608204f495 --- /dev/null +++ b/src/backend/catalog/unused_oids @@ -0,0 +1,41 @@ +#!/bin/sh +# unused_oids +# +# $Header: /cvsroot/pgsql/src/backend/catalog/Attic/unused_oids,v 1.1.1.1 1996/07/09 06:21:18 scrappy Exp $ +# +# finds blocks of oids that have not already been claimed by +# post_hackers for internal purposes. primarily useful for +# finding valid oids for new internal function oids. the numbers +# printed are inclusive ranges of valid (unused) oids. +# +# before using a large empty block, make sure you aren't about +# to take over what was intended as expansion space for something +# else. also, before using a number, do a "grepsrc" to make sure +# that someone isn't using a literal numeric constant somewhere.. +# +# non-berkeley post_hackers should probably not try to use oids +# less than the highest one that comes with the distributed source. +# +# run this script in src/backend/catalog. +# +egrep '^DATA' pg_*.h | \ + sed -e 's/^.*OID[^=]*=[^0-9]*//' -e 's/[^0-9].*$//' | \ + sort -n | \ + uniq | \ + awk ' +BEGIN { + last = 0; +} +/^[0-9]/ { + if ($1 > last + 1) { + if ($1 > last + 2) { + print last + 1, "-", $1 - 1; + } else { + print last + 1; + } + } + last = $1; +} +END { + print last + 1, "-"; +}' diff --git a/src/backend/commands/Makefile.inc b/src/backend/commands/Makefile.inc new file mode 100644 index 00000000000..d05052dfccd --- /dev/null +++ b/src/backend/commands/Makefile.inc @@ -0,0 +1,25 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for the commands module +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/commands/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:18 scrappy Exp $ +# +#------------------------------------------------------------------------- + +VPATH:=$(VPATH):$(CURDIR)/commands + + +SRCS_COMMANDS= async.c creatinh.c command.c copy.c defind.c define.c \ + purge.c remove.c rename.c vacuum.c version.c view.c cluster.c \ + recipe.c explain.c + +HEADERS+= async.h command.h copy.h creatinh.h defrem.h purge.h \ + rename.h vacuum.h version.h view.h cluster.h \ + recipe.h + + diff --git a/src/backend/commands/_deadcode/version.c b/src/backend/commands/_deadcode/version.c new file mode 100644 index 00000000000..6dd311cee7e --- /dev/null +++ b/src/backend/commands/_deadcode/version.c @@ -0,0 +1,336 @@ +/*------------------------------------------------------------------------- + * + * version.c-- + * This file contains all the rules that govern all version semantics. + * + * Copyright (c) 1994, Regents of the University of California + * + * The version stuff has not been tested under postgres95 and probably doesn't + * work! - jolly 8/19/95 + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/_deadcode/Attic/version.c,v 1.1.1.1 1996/07/09 06:21:23 scrappy Exp $ + * + * NOTES + * At the point the version is defined, 2 physical relations are created + * <vname>_added and <vname>_deleted. + * + * In addition, 4 rules are defined which govern the semantics of versions + * w.r.t retrieves, appends, replaces and deletes. + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> + +#include "postgres.h" + +#include "utils/rel.h" +#include "access/heapam.h" +#include "utils/builtins.h" +#include "utils/elog.h" +#include "nodes/pg_list.h" +#include "commands/version.h" +#include "access/xact.h" /* for GetCurrentXactStartTime */ +#include "tcop/tcopprot.h" + +#define MAX_QUERY_LEN 1024 + +char rule_buf[MAX_QUERY_LEN]; +static char attr_list[MAX_QUERY_LEN]; + +static void setAttrList(char *bname); + +/* + * problem: the version system assumes that the rules it declares will + * be fired in the order of declaration, it also assumes + * goh's silly instead semantics. Unfortunately, it is a pain + * to make the version system work with the new semantics. + * However the whole problem can be solved, and some nice + * functionality can be achieved if we get multiple action rules + * to work. So thats what I did -- glass + * + * Well, at least they've been working for about 20 minutes. + * + * So any comments in this code about 1 rule per transction are false...:) + * + */ + +/* + * This is needed because the rule system only allows + * *1* rule to be defined per transaction. + * + * NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + * OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + * OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + * OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + * OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + * OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + * OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + * OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + * OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + * OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + * OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + * OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + * OOOOOOOOOOOOOOOOOOO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * + * DONT DO THAT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * + * If you commit the current Xact all the palloced memory GOES AWAY + * and could be re-palloced in the new Xact and the whole hell breaks + * loose and poor people like me spend 2 hours of their live chassing + * a strange memory bug instead of watching the "Get Smart" marathon + * in NICK ! + * DO NOT COMMIT THE XACT, just increase the Cid counter! + * _sp. + */ +static void +eval_as_new_xact(char *query) +{ + /* WARNING! do not uncomment the following lines WARNING! + * CommitTransactionCommand(); + * StartTransactionCommand(); + */ + CommandCounterIncrement(); + pg_eval(query, (char **) NULL, (Oid *) NULL, 0); +} + +/* + * Define a version. + */ +void +DefineVersion(char *name, char *fromRelname, char *date) +{ + char *bname; + static char saved_basename[512]; + static char saved_snapshot[512]; + + if (date == NULL) { + /* no time ranges */ + bname = fromRelname; + (void) strcpy(saved_basename, (char *) bname); + *saved_snapshot = (char)NULL; + } else { + /* version is a snapshot */ + bname = fromRelname; + (void) strcpy(saved_basename, (char *) bname); + sprintf(saved_snapshot, "['%s']", date); + } + + + /* + * Calls the routine ``GetAttrList'' get the list of attributes + * from the base relation. + * Code is put here so that we only need to look up the attribute once for + * both appends and replaces. + */ + setAttrList(bname); + + VersionCreate (name, saved_basename); + VersionAppend (name, saved_basename); + VersionDelete (name, saved_basename,saved_snapshot); + VersionReplace (name, saved_basename,saved_snapshot); + VersionRetrieve (name, saved_basename, saved_snapshot); +} + + +/* + * Creates the deltas. + */ +void +VersionCreate(char *vname, char *bname) +{ + static char query_buf [MAX_QUERY_LEN]; + + /* + * Creating the dummy version relation for triggering rules. + */ + sprintf(query_buf, "SELECT * INTO TABLE %s from %s where 1 =2", + vname, bname); + + pg_eval (query_buf, (char **) NULL, (Oid *) NULL, 0); + + /* + * Creating the ``v_added'' relation + */ + sprintf (query_buf, "SELECT * INTO TABLE %s_added from %s where 1 = 2", + vname, bname); + eval_as_new_xact (query_buf); + + /* + * Creating the ``v_deleted'' relation. + */ + sprintf (query_buf, "CREATE TABLE %s_del (DOID oid)", vname); + eval_as_new_xact (query_buf); +} + + +/* + * Given the relation name, does a catalog lookup for that relation and + * sets the global variable 'attr_list' with the list of attributes (names) + * for that relation. + */ +static void +setAttrList(char *bname) +{ + Relation rdesc; + int i = 0; + int maxattrs = 0; + char *attrname; + char temp_buf[512]; + int notfirst = 0; + + rdesc = heap_openr(bname); + if (rdesc == NULL ) { + elog(WARN,"Unable to expand all -- amopenr failed "); + return; + } + maxattrs = RelationGetNumberOfAttributes(rdesc); + + attr_list[0] = '\0'; + + for ( i = maxattrs-1 ; i > -1 ; --i ) { + attrname = (rdesc->rd_att->attrs[i]->attname).data; + + if (notfirst == 1) { + sprintf(temp_buf, ", %s = new.%s", attrname, attrname); + } else { + sprintf(temp_buf, "%s = new.%s", attrname, attrname); + notfirst = 1; + } + strcat(attr_list, temp_buf); + } + + heap_close(rdesc); + + return; +} + +/* + * This routine defines the rule governing the append semantics of + * versions. All tuples appended to a version gets appended to the + * <vname>_added relation. + */ +void +VersionAppend(char *vname, char *bname) +{ + sprintf(rule_buf, + "define rewrite rule %s_append is on INSERT to %s do instead append %s_added(%s)", + vname, vname, vname, attr_list); + + eval_as_new_xact(rule_buf); +} + + +/* + * This routine defines the rule governing the retrieval semantics of + * versions. To retrieve tuples from a version , we need to: + * + * 1. Retrieve all tuples in the <vname>_added relation. + * 2. Retrieve all tuples in the base relation which are not in + * the <vname>_del relation. + */ +void +VersionRetrieve(char *vname, char *bname, char *snapshot) +{ + + sprintf(rule_buf, + "define rewrite rule %s_retrieve is on SELECT to %s do instead\n\ +SELECT %s_1.oid, %s_1.* from _%s in %s%s, %s_1 in (%s_added | _%s) \ +where _%s.oid !!= '%s_del.DOID'", + vname, vname, vname, vname, bname, + bname, snapshot, + vname, vname, bname, bname, vname); + + eval_as_new_xact(rule_buf); + + /* printf("%s\n",rule_buf); */ + +} + +/* + * This routine defines the rules that govern the delete semantics of + * versions. Two things happens when we delete a tuple from a version: + * + * 1. If the tuple to be deleted was added to the version *after* + * the version was created, then we simply delete the tuple + * from the <vname>_added relation. + * 2. If the tuple to be deleted is actually in the base relation, + * then we have to mark that tuple as being deleted by adding + * it to the <vname>_del relation. + */ +void +VersionDelete(char *vname, char *bname, char *snapshot) +{ + + sprintf(rule_buf, + "define rewrite rule %s_delete1 is on delete to %s do instead\n \ +[delete %s_added where current.oid = %s_added.oid\n \ + append %s_del(DOID = current.oid) from _%s in %s%s \ + where current.oid = _%s.oid] \n", + vname,vname,vname,vname,vname, +bname,bname,snapshot,bname); + + eval_as_new_xact(rule_buf); +#ifdef OLD_REWRITE + sprintf(rule_buf, + "define rewrite rule %s_delete2 is on delete to %s do instead \n \ + append %s_del(DOID = current.oid) from _%s in %s%s \ + where current.oid = _%s.oid \n", + vname,vname,vname,bname,bname,snapshot,bname); + + eval_as_new_xact(rule_buf); +#endif /* OLD_REWRITE */ +} + +/* + * This routine defines the rules that govern the update semantics + * of versions. To update a tuple in a version: + * + * 1. If the tuple is in <vname>_added, we simply ``replace'' + * the tuple (as per postgres style). + * 2. if the tuple is in the base relation, then two things have to + * happen: + * 2.1 The tuple is marked ``deleted'' from the base relation by + * adding the tuple to the <vname>_del relation. + * 2.2 A copy of the tuple is appended to the <vname>_added relation + */ +void +VersionReplace(char *vname, char *bname, char *snapshot) +{ + sprintf(rule_buf, + "define rewrite rule %s_replace1 is on replace to %s do instead \n\ +[replace %s_added(%s) where current.oid = %s_added.oid \n\ + append %s_del(DOID = current.oid) from _%s in %s%s \ + where current.oid = _%s.oid\n\ + append %s_added(%s) from _%s in %s%s \ + where current.oid !!= '%s_added.oid' and current.oid = _%s.oid]\n", + vname,vname,vname,attr_list,vname, + vname,bname,bname,snapshot,bname, +vname,attr_list,bname,bname,snapshot,vname,bname); + + eval_as_new_xact(rule_buf); + +/* printf("%s\n",rule_buf); */ +#ifdef OLD_REWRITE + sprintf(rule_buf, + "define rewrite rule %s_replace2 is on replace to %s do \n\ + append %s_del(DOID = current.oid) from _%s in %s%s \ + where current.oid = _%s.oid\n", + vname,vname,vname,bname,bname,snapshot,bname); + + eval_as_new_xact(rule_buf); + + sprintf(rule_buf, + "define rewrite rule %s_replace3 is on replace to %s do instead\n\ + append %s_added(%s) from _%s in %s%s \ + where current.oid !!= '%s_added.oid' and current.oid = \ + _%s.oid\n", + vname,vname, vname,attr_list,bname,bname,snapshot,vname,bname); + + eval_as_new_xact(rule_buf); +#endif /* OLD_REWRITE */ +/* printf("%s\n",rule_buf); */ + +} + diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c new file mode 100644 index 00000000000..2d3064fa472 --- /dev/null +++ b/src/backend/commands/async.c @@ -0,0 +1,605 @@ +/*------------------------------------------------------------------------- + * + * async.c-- + * Asynchronous notification + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/async.c,v 1.1.1.1 1996/07/09 06:21:19 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* New Async Notification Model: + * 1. Multiple backends on same machine. Multiple backends listening on + * one relation. + * + * 2. One of the backend does a 'notify <relname>'. For all backends that + * are listening to this relation (all notifications take place at the + * end of commit), + * 2.a If the process is the same as the backend process that issued + * notification (we are notifying something that we are listening), + * signal the corresponding frontend over the comm channel using the + * out-of-band channel. + * 2.b For all other listening processes, we send kill(2) to wake up + * the listening backend. + * 3. Upon receiving a kill(2) signal from another backend process notifying + * that one of the relation that we are listening is being notified, + * we can be in either of two following states: + * 3.a We are sleeping, wake up and signal our frontend. + * 3.b We are in middle of another transaction, wait until the end of + * of the current transaction and signal our frontend. + * 4. Each frontend receives this notification and prcesses accordingly. + * + * -- jw, 12/28/93 + * + */ +/* + * The following is the old model which does not work. + */ +/* + * Model is: + * 1. Multiple backends on same machine. + * + * 2. Query on one backend sends stuff over an asynchronous portal by + * appending to a relation, and then doing an async. notification + * (which takes place after commit) to all listeners on this relation. + * + * 3. Async. notification results in all backends listening on relation + * to be woken up, by a process signal kill(2), with name of relation + * passed in shared memory. + * + * 4. Each backend notifies its respective frontend over the comm + * channel using the out-of-band channel. + * + * 5. Each frontend receives this notification and processes accordingly. + * + * #4,#5 are changing soon with pending rewrite of portal/protocol. + * + */ + +#include <string.h> +#include <signal.h> +#include <errno.h> +#include "postgres.h" + +#include "access/attnum.h" +#include "access/heapam.h" +#include "access/htup.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "utils/builtins.h" +#include "utils/tqual.h" +#include "access/xact.h" + +#include "commands/async.h" +#include "commands/copy.h" +#include "storage/buf.h" +#include "storage/itemptr.h" +#include "miscadmin.h" +#include "utils/portal.h" +#include "utils/excid.h" +#include "utils/elog.h" +#include "utils/mcxt.h" +#include "utils/palloc.h" +#include "utils/rel.h" + +#include "nodes/pg_list.h" +#include "tcop/dest.h" +#include "commands/command.h" + +#include "catalog/catname.h" +#include "utils/syscache.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_class.h" +#include "catalog/pg_type.h" +#include "catalog/pg_listener.h" + +#include "executor/execdefs.h" +/* #include "executor/execdesc.h"*/ + +#include "storage/bufmgr.h" +#include "lib/dllist.h" +#include "libpq/libpq.h" + + +static int notifyFrontEndPending = 0; +static int notifyIssued = 0; +static Dllist *pendingNotifies = NULL; + + +static int AsyncExistsPendingNotify(char *); +static void ClearPendingNotify(void); + +/* + *-------------------------------------------------------------- + * Async_NotifyHandler -- + * + * This is the signal handler for SIGUSR2. When the backend + * is signaled, the backend can be in two states. + * 1. If the backend is in the middle of another transaction, + * we set the flag, notifyFrontEndPending, and wait until + * the end of the transaction to notify the front end. + * 2. If the backend is not in the middle of another transaction, + * we notify the front end immediately. + * + * -- jw, 12/28/93 + * Results: + * none + * + * Side effects: + * none + */ +void +#if defined(PORTNAME_linux) +Async_NotifyHandler(int i) +#else +Async_NotifyHandler() +#endif +{ + extern TransactionState CurrentTransactionState; + + if ((CurrentTransactionState->state == TRANS_DEFAULT) && + (CurrentTransactionState->blockState == TRANS_DEFAULT)) { + + elog(DEBUG, "Waking up sleeping backend process"); + Async_NotifyFrontEnd(); + + }else { + elog(DEBUG, "Process is in the middle of another transaction, state = %d, block state = %d", + CurrentTransactionState->state, + CurrentTransactionState->blockState); + notifyFrontEndPending = 1; + } +} + +/* + *-------------------------------------------------------------- + * Async_Notify -- + * + * Adds the relation to the list of pending notifies. + * All notification happens at end of commit. + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * All notification of backend processes happens here, + * then each backend notifies its corresponding front end at + * the end of commit. + * + * This correspond to 'notify <relname>' command + * -- jw, 12/28/93 + * + * Results: + * XXX + * + * Side effects: + * All tuples for relname in pg_listener are updated. + * + *-------------------------------------------------------------- + */ +void +Async_Notify(char *relname) +{ + + HeapTuple lTuple, rTuple; + Relation lRel; + HeapScanDesc sRel; + TupleDesc tdesc; + ScanKeyData key; + Buffer b; + Datum d, value[3]; + bool isnull; + char repl[3], nulls[3]; + + char *notifyName; + + elog(DEBUG,"Async_Notify: %s",relname); + + if (!pendingNotifies) + pendingNotifies = DLNewList(); + + notifyName = pstrdup(relname); + DLAddHead(pendingNotifies, DLNewElem(notifyName)); + + ScanKeyEntryInitialize(&key, 0, + Anum_pg_listener_relname, + NameEqualRegProcedure, + PointerGetDatum(notifyName)); + + lRel = heap_openr(ListenerRelationName); + tdesc = RelationGetTupleDescriptor(lRel); + sRel = heap_beginscan(lRel, 0, NowTimeQual, 1, &key); + + nulls[0] = nulls[1] = nulls[2] = ' '; + repl[0] = repl[1] = repl[2] = ' '; + repl[Anum_pg_listener_notify - 1] = 'r'; + value[0] = value[1] = value[2] = (Datum) 0; + value[Anum_pg_listener_notify - 1] = Int32GetDatum(1); + + while (HeapTupleIsValid(lTuple = heap_getnext(sRel, 0, &b))) { + d = (Datum) heap_getattr(lTuple, b, Anum_pg_listener_notify, + tdesc, &isnull); + if (!DatumGetInt32(d)) { + rTuple = heap_modifytuple(lTuple, b, lRel, value, nulls, repl); + (void) heap_replace(lRel, &lTuple->t_ctid, rTuple); + } + ReleaseBuffer(b); + } + heap_endscan(sRel); + heap_close(lRel); + notifyIssued = 1; +} + +/* + *-------------------------------------------------------------- + * Async_NotifyAtCommit -- + * + * Signal our corresponding frontend process on relations that + * were notified. Signal all other backend process that + * are listening also. + * + * -- jw, 12/28/93 + * + * Results: + * XXX + * + * Side effects: + * Tuples in pg_listener that has our listenerpid are updated so + * that the notification is 0. We do not want to notify frontend + * more than once. + * + * -- jw, 12/28/93 + * + *-------------------------------------------------------------- + */ +void +Async_NotifyAtCommit() +{ + HeapTuple lTuple; + Relation lRel; + HeapScanDesc sRel; + TupleDesc tdesc; + ScanKeyData key; + Datum d; + int ourpid; + bool isnull; + Buffer b; + extern TransactionState CurrentTransactionState; + + if (!pendingNotifies) + pendingNotifies = DLNewList(); + + if ((CurrentTransactionState->state == TRANS_DEFAULT) && + (CurrentTransactionState->blockState == TRANS_DEFAULT)) { + + if (notifyIssued) { /* 'notify <relname>' issued by us */ + notifyIssued = 0; + StartTransactionCommand(); + elog(DEBUG, "Async_NotifyAtCommit."); + ScanKeyEntryInitialize(&key, 0, + Anum_pg_listener_notify, + Integer32EqualRegProcedure, + Int32GetDatum(1)); + lRel = heap_openr(ListenerRelationName); + sRel = heap_beginscan(lRel, 0, NowTimeQual, 1, &key); + tdesc = RelationGetTupleDescriptor(lRel); + ourpid = getpid(); + + while (HeapTupleIsValid(lTuple = heap_getnext(sRel,0, &b))) { + d = (Datum) heap_getattr(lTuple, b, Anum_pg_listener_relname, + tdesc, &isnull); + + if (AsyncExistsPendingNotify((char *) DatumGetPointer(d))) { + d = (Datum) heap_getattr(lTuple, b, Anum_pg_listener_pid, + tdesc, &isnull); + + if (ourpid == DatumGetInt32(d)) { + elog(DEBUG, "Notifying self, setting notifyFronEndPending to 1"); + notifyFrontEndPending = 1; + } else { + elog(DEBUG, "Notifying others"); +#ifndef WIN32 + if (kill(DatumGetInt32(d), SIGUSR2) < 0) { + if (errno == ESRCH) { + heap_delete(lRel, &lTuple->t_ctid); + } + } +#endif /* WIN32 */ + } + } + ReleaseBuffer(b); + } + CommitTransactionCommand(); + ClearPendingNotify(); + } + + if (notifyFrontEndPending) { /* we need to notify the frontend of + all pending notifies. */ + notifyFrontEndPending = 1; + Async_NotifyFrontEnd(); + } + } +} + +/* + *-------------------------------------------------------------- + * Async_NotifyAtAbort -- + * + * Gets rid of pending notifies. List elements are automatically + * freed through memory context. + * + * + * Results: + * XXX + * + * Side effects: + * XXX + * + *-------------------------------------------------------------- + */ +void +Async_NotifyAtAbort() +{ + extern TransactionState CurrentTransactionState; + + if (notifyIssued) { + ClearPendingNotify(); + } + notifyIssued = 0; + if (pendingNotifies) + DLFreeList(pendingNotifies); + pendingNotifies = DLNewList(); + + if ((CurrentTransactionState->state == TRANS_DEFAULT) && + (CurrentTransactionState->blockState == TRANS_DEFAULT)) { + if (notifyFrontEndPending) { /* don't forget to notify front end */ + Async_NotifyFrontEnd(); + } + } +} + +/* + *-------------------------------------------------------------- + * Async_Listen -- + * + * Register a backend (identified by its Unix PID) as listening + * on the specified relation. + * + * This corresponds to the 'listen <relation>' command in SQL + * + * One listener per relation, pg_listener relation is keyed + * on (relname,pid) to provide multiple listeners in future. + * + * Results: + * pg_listeners is updated. + * + * Side effects: + * XXX + * + *-------------------------------------------------------------- + */ +void +Async_Listen(char *relname, int pid) +{ + Datum values[Natts_pg_listener]; + char nulls[Natts_pg_listener]; + TupleDesc tdesc; + HeapScanDesc s; + HeapTuple htup,tup; + Relation lDesc; + Buffer b; + Datum d; + int i; + bool isnull; + int alreadyListener = 0; + int ourPid = getpid(); + char *relnamei; + TupleDesc tupDesc; + + elog(DEBUG,"Async_Listen: %s",relname); + for (i = 0 ; i < Natts_pg_listener; i++) { + nulls[i] = ' '; + values[i] = PointerGetDatum(NULL); + } + + i = 0; + values[i++] = (Datum) relname; + values[i++] = (Datum) pid; + values[i++] = (Datum) 0; /* no notifies pending */ + + lDesc = heap_openr(ListenerRelationName); + + /* is someone already listening. One listener per relation */ + tdesc = RelationGetTupleDescriptor(lDesc); + s = heap_beginscan(lDesc,0,NowTimeQual,0,(ScanKey)NULL); + while (HeapTupleIsValid(htup = heap_getnext(s,0,&b))) { + d = (Datum) heap_getattr(htup,b,Anum_pg_listener_relname,tdesc, + &isnull); + relnamei = DatumGetPointer(d); + if (!strncmp(relnamei,relname, NAMEDATALEN)) { + d = (Datum) heap_getattr(htup,b,Anum_pg_listener_pid,tdesc,&isnull); + pid = DatumGetInt32(d); + if (pid == ourPid) { + alreadyListener = 1; + } + } + ReleaseBuffer(b); + } + heap_endscan(s); + + if (alreadyListener) { + elog(NOTICE, "Async_Listen: We are already listening on %s", + relname); + return; + } + + tupDesc = lDesc->rd_att; + tup = heap_formtuple(tupDesc, + values, + nulls); + heap_insert(lDesc, tup); + + pfree(tup); + /* if (alreadyListener) { + elog(NOTICE,"Async_Listen: already one listener on %s (possibly dead)",relname); + }*/ + heap_close(lDesc); + + /* + * now that we are listening, we should make a note to ourselves + * to unlisten prior to dying. + */ + relnamei = malloc(NAMEDATALEN); /* persists to process exit */ + memset (relnamei, 0, NAMEDATALEN); + strncpy(relnamei, relname, NAMEDATALEN); + on_exitpg(Async_UnlistenOnExit, (caddr_t) relnamei); +} + +/* + *-------------------------------------------------------------- + * Async_Unlisten -- + * + * Remove the backend from the list of listening backends + * for the specified relation. + * + * This would correspond to the 'unlisten <relation>' + * command, but there isn't one yet. + * + * Results: + * pg_listeners is updated. + * + * Side effects: + * XXX + * + *-------------------------------------------------------------- + */ +void +Async_Unlisten(char *relname, int pid) +{ + Relation lDesc; + HeapTuple lTuple; + + lTuple = SearchSysCacheTuple(LISTENREL, PointerGetDatum(relname), + Int32GetDatum(pid), + 0,0); + lDesc = heap_openr(ListenerRelationName); + if (lTuple != NULL) { + heap_delete(lDesc,&lTuple->t_ctid); + } + heap_close(lDesc); +} + +void +Async_UnlistenOnExit(int code, /* from exitpg */ + char *relname) +{ + Async_Unlisten((char *) relname, getpid()); +} + +/* + * -------------------------------------------------------------- + * Async_NotifyFrontEnd -- + * + * Perform an asynchronous notification to front end over + * portal comm channel. The name of the relation which contains the + * data is sent to the front end. + * + * We remove the notification flag from the pg_listener tuple + * associated with our process. + * + * Results: + * XXX + * + * Side effects: + * + * We make use of the out-of-band channel to transmit the + * notification to the front end. The actual data transfer takes + * place at the front end's request. + * + * -------------------------------------------------------------- + */ +GlobalMemory notifyContext = NULL; + +void +Async_NotifyFrontEnd() +{ + extern CommandDest whereToSendOutput; + HeapTuple lTuple, rTuple; + Relation lRel; + HeapScanDesc sRel; + TupleDesc tdesc; + ScanKeyData key[2]; + Datum d, value[3]; + char repl[3], nulls[3]; + Buffer b; + int ourpid; + bool isnull; + + notifyFrontEndPending = 0; + + elog(DEBUG, "Async_NotifyFrontEnd: notifying front end."); + + StartTransactionCommand(); + ourpid = getpid(); + ScanKeyEntryInitialize(&key[0], 0, + Anum_pg_listener_notify, + Integer32EqualRegProcedure, + Int32GetDatum(1)); + ScanKeyEntryInitialize(&key[1], 0, + Anum_pg_listener_pid, + Integer32EqualRegProcedure, + Int32GetDatum(ourpid)); + lRel = heap_openr(ListenerRelationName); + tdesc = RelationGetTupleDescriptor(lRel); + sRel = heap_beginscan(lRel, 0, NowTimeQual, 2, key); + + nulls[0] = nulls[1] = nulls[2] = ' '; + repl[0] = repl[1] = repl[2] = ' '; + repl[Anum_pg_listener_notify - 1] = 'r'; + value[0] = value[1] = value[2] = (Datum) 0; + value[Anum_pg_listener_notify - 1] = Int32GetDatum(0); + + while (HeapTupleIsValid(lTuple = heap_getnext(sRel, 0,&b))) { + d = (Datum) heap_getattr(lTuple, b, Anum_pg_listener_relname, + tdesc, &isnull); + rTuple = heap_modifytuple(lTuple, b, lRel, value, nulls, repl); + (void) heap_replace(lRel, &lTuple->t_ctid, rTuple); + + /* notifying the front end */ + + if (whereToSendOutput == Remote) { + pq_putnchar("A", 1); + pq_putint(ourpid, 4); + pq_putstr(DatumGetName(d)->data); + pq_flush(); + } else { + elog(NOTICE, "Async_NotifyFrontEnd: no asynchronous notification to frontend on interactive sessions"); + } + ReleaseBuffer(b); + } + CommitTransactionCommand(); +} + +static int +AsyncExistsPendingNotify(char *relname) +{ + Dlelem* p; + for (p = DLGetHead(pendingNotifies); + p != NULL; + p = DLGetSucc(p)) { + if (!strcmp(DLE_VAL(p), relname)) + return 1; + } + + return 0; +} + +static void +ClearPendingNotify() +{ + Dlelem* p; + while ( (p = DLRemHead(pendingNotifies)) != NULL) + free(DLE_VAL(p)); +} + diff --git a/src/backend/commands/async.h b/src/backend/commands/async.h new file mode 100644 index 00000000000..65e4bd69d55 --- /dev/null +++ b/src/backend/commands/async.h @@ -0,0 +1,33 @@ +/*------------------------------------------------------------------------- + * + * async.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: async.h,v 1.1.1.1 1996/07/09 06:21:19 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef ASYNC_H +#define ASYNC_H + +#include "nodes/memnodes.h" + +#if defined(PORTNAME_linux) +extern void Async_NotifyHandler(int); +#else +extern void Async_NotifyHandler(void); +#endif +extern void Async_Notify(char *relname); +extern void Async_NotifyAtCommit(void); +extern void Async_NotifyAtAbort(void); +extern void Async_Listen(char *relname, int pid); +extern void Async_Unlisten(char *relname, int pid); +extern void Async_UnlistenOnExit(int code, char *relname); + +extern GlobalMemory notifyContext; +extern void Async_NotifyFrontEnd(void); + +#endif /* ASYNC_H */ diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c new file mode 100644 index 00000000000..8400832d6ac --- /dev/null +++ b/src/backend/commands/cluster.c @@ -0,0 +1,370 @@ +/*------------------------------------------------------------------------- + * + * cluster.c-- + * Paul Brown's implementation of cluster index. + * + * I am going to use the rename function as a model for this in the + * parser and executor, and the vacuum code as an example in this + * file. As I go - in contrast to the rest of postgres - there will + * be BUCKETS of comments. This is to allow reviewers to understand + * my (probably bogus) assumptions about the way this works. + * [pbrown '94] + * + * Copyright (c) 1994-5, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.1.1.1 1996/07/09 06:21:19 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <string.h> +#include <stdio.h> + +#include "postgres.h" + +#include "nodes/pg_list.h" + +#include "access/attnum.h" +#include "access/heapam.h" +#include "access/genam.h" +#include "access/htup.h" +#include "access/itup.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "access/xact.h" +#include "utils/tqual.h" + +#include "catalog/catname.h" +#include "utils/syscache.h" +#include "catalog/index.h" +#include "catalog/indexing.h" +#include "catalog/pg_type.h" + +#include "commands/copy.h" +#include "commands/cluster.h" +#include "commands/rename.h" + +#include "storage/buf.h" +#include "storage/bufmgr.h" +#include "storage/itemptr.h" + +#include "miscadmin.h" +#include "tcop/dest.h" +#include "commands/command.h" + +#include "utils/builtins.h" +#include "utils/excid.h" +#include "utils/elog.h" +#include "utils/mcxt.h" +#include "utils/palloc.h" +#include "utils/rel.h" + +#include "catalog/pg_attribute.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_class.h" + +#include "optimizer/internal.h" + +#ifndef NO_SECURITY +#include "utils/acl.h" +#include "utils/syscache.h" +#endif /* !NO_SECURITY */ + +/* + * cluster + * + * Check that the relation is a relation in the appropriate user + * ACL. I will use the same security that limits users on the + * renamerel() function. + * + * Check that the index specified is appropriate for the task + * ( ie it's an index over this relation ). This is trickier. + * + * Create a list of all the other indicies on this relation. Because + * the cluster will wreck all the tids, I'll need to destroy bogus + * indicies. The user will have to re-create them. Not nice, but + * I'm not a nice guy. The alternative is to try some kind of post + * destroy re-build. This may be possible. I'll check out what the + * index create functiond want in the way of paramaters. On the other + * hand, re-creating n indicies may blow out the space. + * + * Create new (temporary) relations for the base heap and the new + * index. + * + * Exclusively lock the relations. + * + * Create new clustered index and base heap relation. + * + */ +void +cluster(char oldrelname[], char oldindexname[]) +{ + Oid OIDOldHeap, OIDOldIndex, OIDNewHeap; + + Relation OldHeap, OldIndex; + Relation NewHeap; + + char *NewIndexName; + char *szNewHeapName; + + /* + * + * I'm going to force all checking back into the commands.c function. + * + * Get the list if indicies for this relation. If the index we want + * is among them, do not add it to the 'kill' list, as it will be + * handled by the 'clean up' code which commits this transaction. + * + * I'm not using the SysCache, because this will happen but + * once, and the slow way is the sure way in this case. + * + */ + /* + * Like vacuum, cluster spans transactions, so I'm going to handle it in + * the same way. + */ + + /* matches the StartTransaction in PostgresMain() */ + + OldHeap = heap_openr(oldrelname); + if (!RelationIsValid(OldHeap)) { + elog(WARN, "cluster: unknown relation: \"%-.*s\"", + NAMEDATALEN, oldrelname); + } + OIDOldHeap = OldHeap->rd_id; /* Get OID for the index scan */ + + OldIndex=index_openr(oldindexname);/* Open old index relation */ + if (!RelationIsValid(OldIndex)) { + elog(WARN, "cluster: unknown index: \"%-.*s\"", + NAMEDATALEN, oldindexname); + } + OIDOldIndex = OldIndex->rd_id; /* OID for the index scan */ + + heap_close(OldHeap); + index_close(OldIndex); + + /* + * I need to build the copies of the heap and the index. The Commit() + * between here is *very* bogus. If someone is appending stuff, they will + * get the lock after being blocked and add rows which won't be present in + * the new table. Bleagh! I'd be best to try and ensure that no-one's + * in the tables for the entire duration of this process with a pg_vlock. + */ + NewHeap = copy_heap(OIDOldHeap); + OIDNewHeap = NewHeap->rd_id; + szNewHeapName = pstrdup(NewHeap->rd_rel->relname.data); + + /* Need to do this to make the new heap visible. */ + CommandCounterIncrement(); + + rebuildheap(OIDNewHeap, OIDOldHeap, OIDOldIndex); + + /* Need to do this to make the new heap visible. */ + CommandCounterIncrement(); + + /* can't be found in the SysCache. */ + copy_index(OIDOldIndex, OIDNewHeap); /* No contention with the old */ + + /* + * make this really happen. Flush all the buffers. + */ + CommitTransactionCommand(); + StartTransactionCommand(); + + /* + * Questionable bit here. Because the renamerel destroys all trace of the + * pre-existing relation, I'm going to Destroy old, and then rename new + * to old. If this fails, it fails, and you lose your old. Tough - say + * I. Have good backups! + */ + + /* + Here lies the bogosity. The RelationNameGetRelation returns a bad + list of TupleDescriptors. Damn. Can't work out why this is. + */ + + heap_destroy(oldrelname); /* AAAAAAAAGH!! */ + + CommandCounterIncrement(); + + /* + * The Commit flushes all palloced memory, so I have to grab the + * New stuff again. This is annoying, but oh heck! + */ +/* + renamerel(szNewHeapName.data, oldrelname); + TypeRename(&szNewHeapName, &szOldRelName); + + sprintf(NewIndexName.data, "temp_%x", OIDOldIndex); + renamerel(NewIndexName.data, szOldIndexName.data); +*/ + NewIndexName = palloc(NAMEDATALEN+1); /* XXX */ + sprintf(NewIndexName, "temp_%x", OIDOldIndex); + renamerel(NewIndexName, oldindexname); +} + +Relation +copy_heap(Oid OIDOldHeap) +{ + char NewName[NAMEDATALEN]; + TupleDesc OldHeapDesc, tupdesc; + Oid OIDNewHeap; + Relation NewHeap, OldHeap; + + /* + * Create a new heap relation with a temporary name, which has the + * same tuple description as the old one. + */ + sprintf(NewName,"temp_%x", OIDOldHeap); + + OldHeap= heap_open(OIDOldHeap); + OldHeapDesc= RelationGetTupleDescriptor(OldHeap); + + /* + * Need to make a copy of the tuple descriptor, heap_create modifies + * it. + */ + + tupdesc = CreateTupleDescCopy(OldHeapDesc); + + OIDNewHeap=heap_create(NewName, + NULL, + OldHeap->rd_rel->relarch, + OldHeap->rd_rel->relsmgr, + tupdesc); + + if (!OidIsValid(OIDNewHeap)) + elog(WARN,"clusterheap: cannot create temporary heap relation\n"); + + NewHeap=heap_open(OIDNewHeap); + + heap_close(NewHeap); + heap_close(OldHeap); + + return NewHeap; +} + +void +copy_index(Oid OIDOldIndex, Oid OIDNewHeap) +{ + Relation OldIndex, NewHeap; + HeapTuple Old_pg_index_Tuple, Old_pg_index_relation_Tuple, pg_proc_Tuple; + IndexTupleForm Old_pg_index_Form; + Form_pg_class Old_pg_index_relation_Form; + Form_pg_proc pg_proc_Form; + char *NewIndexName; + AttrNumber *attnumP; + int natts; + FuncIndexInfo * finfo; + + NewHeap = heap_open(OIDNewHeap); + OldIndex = index_open(OIDOldIndex); + + /* + * OK. Create a new (temporary) index for the one that's already + * here. To do this I get the info from pg_index, re-build the + * FunctInfo if I have to, and add a new index with a temporary + * name. + */ + Old_pg_index_Tuple = + SearchSysCacheTuple(INDEXRELID, + ObjectIdGetDatum(OldIndex->rd_id), + 0,0,0); + + Assert(Old_pg_index_Tuple); + Old_pg_index_Form = (IndexTupleForm)GETSTRUCT(Old_pg_index_Tuple); + + Old_pg_index_relation_Tuple = + SearchSysCacheTuple(RELOID, + ObjectIdGetDatum(OldIndex->rd_id), + 0,0,0); + + Assert(Old_pg_index_relation_Tuple); + Old_pg_index_relation_Form = + (Form_pg_class)GETSTRUCT(Old_pg_index_relation_Tuple); + + NewIndexName = palloc(NAMEDATALEN+1); /* XXX */ + sprintf(NewIndexName, "temp_%x", OIDOldIndex); /* Set the name. */ + + /* + * Ugly as it is, the only way I have of working out the number of + * attribues is to count them. Mostly there'll be just one but + * I've got to be sure. + */ + for (attnumP = &(Old_pg_index_Form->indkey[0]), natts = 0; + *attnumP != InvalidAttrNumber; + attnumP++, natts++); + + /* + * If this is a functional index, I need to rebuild the functional + * component to pass it to the defining procedure. + */ + if (Old_pg_index_Form->indproc != InvalidOid) { + FIgetnArgs(finfo) = natts; + FIgetProcOid(finfo) = Old_pg_index_Form->indproc; + + pg_proc_Tuple = + SearchSysCacheTuple(PROOID, + ObjectIdGetDatum(Old_pg_index_Form->indproc), + 0,0,0); + + Assert(pg_proc_Tuple); + pg_proc_Form = (Form_pg_proc)GETSTRUCT(pg_proc_Tuple); + namecpy(&(finfo->funcName), &(pg_proc_Form->proname)); + } else { + finfo = (FuncIndexInfo *) NULL; + natts = 1; + } + + index_create((NewHeap->rd_rel->relname).data, + NewIndexName, + finfo, + Old_pg_index_relation_Form->relam, + natts, + Old_pg_index_Form->indkey, + Old_pg_index_Form->indclass, + (uint16)0, (Datum) NULL, NULL); + + heap_close(OldIndex); + heap_close(NewHeap); +} + + +void +rebuildheap(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) +{ + Relation LocalNewHeap, LocalOldHeap, LocalOldIndex; + IndexScanDesc ScanDesc; + RetrieveIndexResult ScanResult; + ItemPointer HeapTid; + HeapTuple LocalHeapTuple; + Buffer LocalBuffer; + Oid OIDNewHeapInsert; + + /* + * Open the relations I need. Scan through the OldHeap on the OldIndex and + * insert each tuple into the NewHeap. + */ + LocalNewHeap=(Relation)heap_open(OIDNewHeap); + LocalOldHeap=(Relation)heap_open(OIDOldHeap); + LocalOldIndex=(Relation)index_open(OIDOldIndex); + + ScanDesc=index_beginscan(LocalOldIndex, false, 0, (ScanKey) NULL); + + while ((ScanResult = + index_getnext(ScanDesc, ForwardScanDirection)) != NULL) { + + HeapTid = &ScanResult->heap_iptr; + LocalHeapTuple = heap_fetch(LocalOldHeap, 0, HeapTid, &LocalBuffer); + OIDNewHeapInsert = + heap_insert(LocalNewHeap, LocalHeapTuple); + pfree(ScanResult); + ReleaseBuffer(LocalBuffer); + } + + index_close(LocalOldIndex); + heap_close(LocalOldHeap); + heap_close(LocalNewHeap); +} + diff --git a/src/backend/commands/cluster.h b/src/backend/commands/cluster.h new file mode 100644 index 00000000000..2194e13f9a8 --- /dev/null +++ b/src/backend/commands/cluster.h @@ -0,0 +1,30 @@ +/*------------------------------------------------------------------------- + * + * cluster.h-- + * header file for postgres cluster command stuff + * + * Copyright (c) 1994-5, Regents of the University of California + * + * $Id: cluster.h,v 1.1.1.1 1996/07/09 06:21:19 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef CLUSTER_H +#define CLUSTER_H + +/* + * defines for contant stuff + */ +#define _TEMP_RELATION_KEY_ "clXXXXXXXX" +#define _SIZE_OF_TEMP_RELATION_KEY_ 11 + + +/* + * functions + */ +extern void cluster(char oldrelname[], char oldindexname[]); +extern Relation copy_heap(Oid OIDOldHeap); +extern void copy_index(Oid OIDOldIndex, Oid OIDNewHeap); +extern void rebuildheap(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex); + +#endif /* CLUSTER_H */ diff --git a/src/backend/commands/command.c b/src/backend/commands/command.c new file mode 100644 index 00000000000..4283b594d59 --- /dev/null +++ b/src/backend/commands/command.c @@ -0,0 +1,511 @@ +/*------------------------------------------------------------------------- + * + * command.c-- + * random postgres portal and utility support code + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.1.1.1 1996/07/09 06:21:19 scrappy Exp $ + * + * NOTES + * The PortalExecutorHeapMemory crap needs to be eliminated + * by designing a better executor / portal processing memory + * interface. + * + * The PerformAddAttribute() code, like most of the relation + * manipulating code in the commands/ directory, should go + * someplace closer to the lib/catalog code. + * + *------------------------------------------------------------------------- + */ +#include <string.h> +#include "postgres.h" + +#include "access/attnum.h" +#include "access/heapam.h" +#include "access/htup.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "utils/builtins.h" +#include "utils/tqual.h" + +#include "commands/copy.h" + +#include "storage/buf.h" +#include "storage/itemptr.h" + +#include "miscadmin.h" + +#include "utils/portal.h" +#include "utils/excid.h" +#include "utils/elog.h" +#include "utils/mcxt.h" +#include "utils/palloc.h" +#include "utils/rel.h" + +#include "nodes/pg_list.h" +#include "nodes/primnodes.h" +#include "tcop/dest.h" +#include "commands/command.h" + +#include "catalog/catalog.h" +#include "catalog/catname.h" +#include "utils/syscache.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_class.h" +#include "catalog/pg_type.h" +#include "catalog/indexing.h" + +#include "executor/executor.h" +#include "executor/execdefs.h" +#include "executor/execdesc.h" + +#include "optimizer/internal.h" +#include "optimizer/prep.h" /* for find_all_inheritors */ + + +#ifndef NO_SECURITY +#include "miscadmin.h" +#include "utils/acl.h" +#include "utils/syscache.h" +#endif /* !NO_SECURITY */ + +/* ---------------- + * PortalExecutorHeapMemory stuff + * + * This is where the XXXSuperDuperHacky code was. -cim 3/15/90 + * ---------------- + */ +MemoryContext PortalExecutorHeapMemory = NULL; + +/* -------------------------------- + * PortalCleanup + * -------------------------------- + */ +void +PortalCleanup(Portal portal) +{ + MemoryContext context; + + /* ---------------- + * sanity checks + * ---------------- + */ + AssertArg(PortalIsValid(portal)); + AssertArg(portal->cleanup == PortalCleanup); + + /* ---------------- + * set proper portal-executor context before calling ExecMain. + * ---------------- + */ + context = MemoryContextSwitchTo((MemoryContext) PortalGetHeapMemory(portal)); + PortalExecutorHeapMemory = (MemoryContext) + PortalGetHeapMemory(portal); + + /* ---------------- + * tell the executor to shutdown the query + * ---------------- + */ + ExecutorEnd(PortalGetQueryDesc(portal), PortalGetState(portal)); + + /* ---------------- + * switch back to previous context + * ---------------- + */ + (void) MemoryContextSwitchTo(context); + PortalExecutorHeapMemory = (MemoryContext) NULL; +} + +/* -------------------------------- + * PerformPortalFetch + * -------------------------------- + */ +void +PerformPortalFetch(char *name, + bool forward, + int count, + char *tag, + CommandDest dest) +{ + Portal portal; + int feature; + QueryDesc *queryDesc; + MemoryContext context; + + /* ---------------- + * sanity checks + * ---------------- + */ + if (name == NULL) { + elog(NOTICE, "PerformPortalFetch: blank portal unsupported"); + return; + } + + /* ---------------- + * get the portal from the portal name + * ---------------- + */ + portal = GetPortalByName(name); + if (! PortalIsValid(portal)) { + elog(NOTICE, "PerformPortalFetch: portal \"%-.*s\" not found", + NAMEDATALEN, name); + return; + } + + /* ---------------- + * switch into the portal context + * ---------------- + */ + context= MemoryContextSwitchTo((MemoryContext)PortalGetHeapMemory(portal)); + + AssertState(context == + (MemoryContext)PortalGetHeapMemory(GetPortalByName(NULL))); + + /* ---------------- + * setup "feature" to tell the executor what direction and + * how many tuples to fetch. + * ---------------- + */ + if (forward) + feature = EXEC_FOR; + else + feature = EXEC_BACK; + + /* ---------------- + * tell the destination to prepare to recieve some tuples + * ---------------- + */ + queryDesc = PortalGetQueryDesc(portal); + BeginCommand(name, + queryDesc->operation, + portal->attinfo,/* QueryDescGetTypeInfo(queryDesc), */ + false, /* portal fetches don't end up in relations */ + false, /* this is a portal fetch, not a "retrieve portal" */ + tag, + dest); + + /* ---------------- + * execute the portal fetch operation + * ---------------- + */ + PortalExecutorHeapMemory = (MemoryContext) + PortalGetHeapMemory(portal); + + ExecutorRun(queryDesc, PortalGetState(portal), feature, count); + + /* ---------------- + * Note: the "end-of-command" tag is returned by higher-level + * utility code + * + * Return blank portal for now. + * Otherwise, this named portal will be cleaned. + * Note: portals will only be supported within a BEGIN...END + * block in the near future. Later, someone will fix it to + * do what is possible across transaction boundries. + * ---------------- + */ + (void) MemoryContextSwitchTo( + (MemoryContext)PortalGetHeapMemory(GetPortalByName(NULL))); +} + +/* -------------------------------- + * PerformPortalClose + * -------------------------------- + */ +void +PerformPortalClose(char *name, CommandDest dest) +{ + Portal portal; + + /* ---------------- + * sanity checks + * ---------------- + */ + if (name == NULL) { + elog(NOTICE, "PerformPortalClose: blank portal unsupported"); + return; + } + + /* ---------------- + * get the portal from the portal name + * ---------------- + */ + portal = GetPortalByName(name); + if (! PortalIsValid(portal)) { + elog(NOTICE, "PerformPortalClose: portal \"%-.*s\" not found", + NAMEDATALEN, name); + return; + } + + /* ---------------- + * Note: PortalCleanup is called as a side-effect + * ---------------- + */ + PortalDestroy(&portal); +} + +/* ---------------- + * PerformAddAttribute + * + * adds an additional attribute to a relation + * + * Adds attribute field(s) to a relation. Each new attribute + * is given attnums in sequential order and is added to the + * ATTRIBUTE relation. If the AMI fails, defunct tuples will + * remain in the ATTRIBUTE relation for later vacuuming. + * Later, there may be some reserved attribute names??? + * + * (If needed, can instead use elog to handle exceptions.) + * + * Note: + * Initial idea of ordering the tuple attributes so that all + * the variable length domains occured last was scratched. Doing + * so would not speed access too much (in general) and would create + * many complications in formtuple, amgetattr, and addattribute. + * + * scan attribute catalog for name conflict (within rel) + * scan type catalog for absence of data type (if not arg) + * create attnum magically??? + * create attribute tuple + * insert attribute in attribute catalog + * modify reldesc + * create new relation tuple + * insert new relation in relation catalog + * delete original relation from relation catalog + * ---------------- + */ +void +PerformAddAttribute(char *relationName, + char *userName, + bool inherits, + ColumnDef *colDef) +{ + Relation relrdesc, attrdesc; + HeapScanDesc attsdesc; + HeapTuple reltup; + HeapTuple attributeTuple; + AttributeTupleForm attribute; + FormData_pg_attribute attributeD; + int i; + int minattnum, maxatts; + HeapTuple tup; + ScanKeyData key[2]; + ItemPointerData oldTID; + Relation idescs[Num_pg_attr_indices]; + Relation ridescs[Num_pg_class_indices]; + bool hasindex; + + /* + * permissions checking. this would normally be done in utility.c, + * but this particular routine is recursive. + * + * normally, only the owner of a class can change its schema. + */ + if (IsSystemRelationName(relationName)) + elog(WARN, "PerformAddAttribute: class \"%-.*s\" is a system catalog", + NAMEDATALEN, relationName); +#ifndef NO_SECURITY + if (!pg_ownercheck(userName, relationName, RELNAME)) + elog(WARN, "PerformAddAttribute: you do not own class \"%s\"", + relationName); +#endif + + /* + * if the first element in the 'schema' list is a "*" then we are + * supposed to add this attribute to all classes that inherit from + * 'relationName' (as well as to 'relationName'). + * + * any permissions or problems with duplicate attributes will cause + * the whole transaction to abort, which is what we want -- all or + * nothing. + */ + if (colDef != NULL) { + if (inherits) { + Oid myrelid, childrelid; + List *child, *children; + + relrdesc = heap_openr(relationName); + if (!RelationIsValid(relrdesc)) { + elog(WARN, "PerformAddAttribute: unknown relation: \"%-.*s\"", + NAMEDATALEN, relationName); + } + myrelid = relrdesc->rd_id; + heap_close(relrdesc); + + /* this routine is actually in the planner */ + children = find_all_inheritors(lconsi(myrelid,NIL), NIL); + + /* + * find_all_inheritors does the recursive search of the + * inheritance hierarchy, so all we have to do is process + * all of the relids in the list that it returns. + */ + foreach (child, children) { + childrelid = lfirsti(child); + if (childrelid == myrelid) + continue; + relrdesc = heap_open(childrelid); + if (!RelationIsValid(relrdesc)) { + elog(WARN, "PerformAddAttribute: can't find catalog entry for inheriting class with oid %d", + childrelid); + } + PerformAddAttribute((relrdesc->rd_rel->relname).data, + userName, false, colDef); + heap_close(relrdesc); + } + } + } + + relrdesc = heap_openr(RelationRelationName); + reltup = ClassNameIndexScan(relrdesc, relationName); + + if (!PointerIsValid(reltup)) { + heap_close(relrdesc); + elog(WARN, "PerformAddAttribute: relation \"%s\" not found", + relationName); + } + /* + * XXX is the following check sufficient? + */ + if (((Form_pg_class) GETSTRUCT(reltup))->relkind == RELKIND_INDEX) { + elog(WARN, "PerformAddAttribute: index relation \"%s\" not changed", + relationName); + return; + } + + minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts; + maxatts = minattnum + 1; + if (maxatts > MaxHeapAttributeNumber) { + pfree(reltup); /* XXX temp */ + heap_close(relrdesc); /* XXX temp */ + elog(WARN, "PerformAddAttribute: relations limited to %d attributes", + MaxHeapAttributeNumber); + return; + } + + attrdesc = heap_openr(AttributeRelationName); + + Assert(attrdesc); + Assert(RelationGetRelationTupleForm(attrdesc)); + + /* + * Open all (if any) pg_attribute indices + */ + hasindex = RelationGetRelationTupleForm(attrdesc)->relhasindex; + if (hasindex) + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); + + ScanKeyEntryInitialize(&key[0], + (bits16) NULL, + (AttrNumber) Anum_pg_attribute_attrelid, + (RegProcedure)ObjectIdEqualRegProcedure, + (Datum) reltup->t_oid); + + ScanKeyEntryInitialize(&key[1], + (bits16) NULL, + (AttrNumber) Anum_pg_attribute_attname, + (RegProcedure)NameEqualRegProcedure, + (Datum) NULL); + + attributeD.attrelid = reltup->t_oid; + attributeD.attdefrel = InvalidOid; /* XXX temporary */ + attributeD.attnvals = 0; /* XXX temporary */ + attributeD.atttyparg = InvalidOid; /* XXX temporary */ + attributeD.attbound = 0; /* XXX temporary */ + attributeD.attcanindex = 0; /* XXX need this info */ + attributeD.attproc = InvalidOid; /* XXX tempoirary */ + attributeD.attcacheoff = -1; + + attributeTuple = heap_addheader(Natts_pg_attribute, + sizeof attributeD, + (char *)&attributeD); + + attribute = (AttributeTupleForm)GETSTRUCT(attributeTuple); + + i = 1 + minattnum; + + { + HeapTuple typeTuple; + TypeTupleForm form; + char *p; + int attnelems; + + /* + * XXX use syscache here as an optimization + */ + key[1].sk_argument = (Datum)colDef->colname; + attsdesc = heap_beginscan(attrdesc, 0, NowTimeQual, 2, key); + + + tup = heap_getnext(attsdesc, 0, (Buffer *) NULL); + if (HeapTupleIsValid(tup)) { + pfree(reltup); /* XXX temp */ + heap_endscan(attsdesc); /* XXX temp */ + heap_close(attrdesc); /* XXX temp */ + heap_close(relrdesc); /* XXX temp */ + elog(WARN, "PerformAddAttribute: attribute \"%s\" already exists in class \"%s\"", + key[1].sk_argument, + relationName); + return; + } + heap_endscan(attsdesc); + + /* + * check to see if it is an array attribute. + */ + + p = colDef->typename->name; + + if (colDef->typename->arrayBounds) + { + attnelems = length(colDef->typename->arrayBounds); + p = makeArrayTypeName(colDef->typename->name); + } + else + attnelems = 0; + + typeTuple = SearchSysCacheTuple(TYPNAME, + PointerGetDatum(p), + 0,0,0); + form = (TypeTupleForm)GETSTRUCT(typeTuple); + + if (!HeapTupleIsValid(typeTuple)) { + elog(WARN, "Add: type \"%s\" nonexistant", p); + } + namestrcpy(&(attribute->attname), (char*) key[1].sk_argument); + attribute->atttypid = typeTuple->t_oid; + attribute->attlen = form->typlen; + attribute->attnum = i; + attribute->attbyval = form->typbyval; + attribute->attnelems = attnelems; + attribute->attcacheoff = -1; + attribute->attisset = (bool) (form->typtype == 'c'); + attribute->attalign = form->typalign; + + heap_insert(attrdesc, attributeTuple); + if (hasindex) + CatalogIndexInsert(idescs, + Num_pg_attr_indices, + attrdesc, + attributeTuple); + } + + if (hasindex) + CatalogCloseIndices(Num_pg_attr_indices, idescs); + heap_close(attrdesc); + + ((Form_pg_class) GETSTRUCT(reltup))->relnatts = maxatts; + oldTID = reltup->t_ctid; + (void) heap_replace(relrdesc, &oldTID, reltup); + + /* keep catalog indices current */ + CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs); + CatalogIndexInsert(ridescs, Num_pg_class_indices, relrdesc, reltup); + CatalogCloseIndices(Num_pg_class_indices, ridescs); + + pfree(reltup); + heap_close(relrdesc); +} diff --git a/src/backend/commands/command.h b/src/backend/commands/command.h new file mode 100644 index 00000000000..266c6b4be14 --- /dev/null +++ b/src/backend/commands/command.h @@ -0,0 +1,56 @@ +/*------------------------------------------------------------------------- + * + * command.h-- + * prototypes for command.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: command.h,v 1.1.1.1 1996/07/09 06:21:19 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef COMMAND_H +#define COMMAND_H + +#include "utils/portal.h" +#include "tcop/dest.h" + +extern MemoryContext PortalExecutorHeapMemory; + +/* + * PortalCleanup -- + * Cleans up the query state of the portal. + * + * Exceptions: + * BadArg if portal invalid. + */ +extern void PortalCleanup(Portal portal); + + +/* + * PerformPortalFetch -- + * Performs the POSTQUEL function FETCH. Fetches count (or all if 0) + * tuples in portal with name in the forward direction iff goForward. + * + * Exceptions: + * BadArg if forward invalid. + * "WARN" if portal not found. + */ +extern void PerformPortalFetch(char *name, bool forward, int count, + char *tag, CommandDest dest); + +/* + * PerformPortalClose -- + * Performs the POSTQUEL function CLOSE. + */ +extern void PerformPortalClose(char *name, CommandDest dest); + +/* + * PerformAddAttribute -- + * Performs the POSTQUEL function ADD. + */ +extern void PerformAddAttribute(char *relationName, char *userName, + bool inh, ColumnDef *colDef); + +#endif /* COMMAND_H */ diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c new file mode 100644 index 00000000000..7e10818abfc --- /dev/null +++ b/src/backend/commands/copy.c @@ -0,0 +1,782 @@ +/*------------------------------------------------------------------------- + * + * copy.c-- + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.1.1.1 1996/07/09 06:21:19 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> +#include <sys/types.h> /* for mode_t */ +#include <sys/stat.h> /* for umask(2) prototype */ + +#include "postgres.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/syscache.h" +#include "catalog/pg_type.h" +#include "catalog/pg_index.h" +#include "catalog/index.h" + +#include "access/heapam.h" +#include "access/htup.h" +#include "access/itup.h" +#include "access/relscan.h" +#include "access/funcindex.h" +#include "access/tupdesc.h" +#include "nodes/execnodes.h" +#include "nodes/plannodes.h" +#include "nodes/pg_list.h" +#include "executor/tuptable.h" +#include "executor/executor.h" +#include "utils/rel.h" +#include "utils/elog.h" +#include "utils/memutils.h" +#include "utils/palloc.h" +#include "fmgr.h" +#include "machine.h" + +/* + * New copy code. + * + * This code "knows" the following about tuples: + * + */ + +static bool reading_from_input = false; + +/* non-export function prototypes */ +static void CopyTo(Relation rel, bool binary, FILE *fp, char *delim); +static void CopyFrom(Relation rel, bool binary, FILE *fp, char *delim); +static Oid GetOutputFunction(Oid type); +static Oid GetTypeElement(Oid type); +static Oid GetInputFunction(Oid type); +static Oid IsTypeByVal(Oid type); +static void GetIndexRelations(Oid main_relation_oid, + int *n_indices, + Relation **index_rels); +static char *CopyReadAttribute(int attno, FILE *fp, bool *isnull, char *delim); +static void CopyAttributeOut(FILE *fp, char *string, char *delim); +static int CountTuples(Relation relation); + +extern FILE *Pfout, *Pfin; + +void +DoCopy(char *relname, bool binary, bool from, bool pipe, char *filename, + char *delim) +{ + FILE *fp; + Relation rel; + reading_from_input = pipe; + + rel = heap_openr(relname); + if (rel == NULL) elog(WARN, "Copy: class %s does not exist.", relname); + + if (from) { + if (pipe && IsUnderPostmaster) ReceiveCopyBegin(); + if (IsUnderPostmaster) { + fp = pipe ? Pfin : fopen(filename, "r"); + }else { + fp = pipe ? stdin : fopen(filename, "r"); + } + if (fp == NULL) { + elog(WARN, "COPY: file %s could not be open for reading", filename); + } + CopyFrom(rel, binary, fp, delim); + }else { + + mode_t oumask = umask((mode_t) 0); + + if (pipe && IsUnderPostmaster) SendCopyBegin(); + if (IsUnderPostmaster) { + fp = pipe ? Pfout : fopen(filename, "w"); + + }else { + fp = pipe ? stdout : fopen(filename, "w"); + } + (void) umask(oumask); + if (fp == NULL) { + elog(WARN, "COPY: file %s could not be open for writing", filename); + } + CopyTo(rel, binary, fp, delim); + } + if (!pipe) { + fclose(fp); + }else if (!from && !binary) { + fputs(".\n", fp); + if (IsUnderPostmaster) fflush(Pfout); + } +} + +static void +CopyTo(Relation rel, bool binary, FILE *fp, char *delim) +{ + HeapTuple tuple; + HeapScanDesc scandesc; + + int32 attr_count, i; + AttributeTupleForm *attr; + func_ptr *out_functions; + int dummy; + Oid out_func_oid; + Oid *elements; + Datum value; + bool isnull = (bool) true; + char *nulls; + char *string; + int32 ntuples; + TupleDesc tupDesc; + + scandesc = heap_beginscan(rel, 0, NULL, 0, NULL); + + attr_count = rel->rd_att->natts; + attr = rel->rd_att->attrs; + tupDesc = rel->rd_att; + + if (!binary) { + out_functions = (func_ptr *) + palloc(attr_count * sizeof(func_ptr)); + elements = (Oid *) palloc(attr_count * sizeof(Oid)); + for (i = 0; i < attr_count; i++) { + out_func_oid = (Oid) GetOutputFunction(attr[i]->atttypid); + fmgr_info(out_func_oid, &out_functions[i], &dummy); + elements[i] = GetTypeElement(attr[i]->atttypid); + } + }else { + nulls = (char *) palloc(attr_count); + for (i = 0; i < attr_count; i++) nulls[i] = ' '; + + /* XXX expensive */ + + ntuples = CountTuples(rel); + fwrite(&ntuples, sizeof(int32), 1, fp); + } + + for (tuple = heap_getnext(scandesc, 0, NULL); + tuple != NULL; + tuple = heap_getnext(scandesc, 0, NULL)) { + + for (i = 0; i < attr_count; i++) { + value = (Datum) + heap_getattr(tuple, InvalidBuffer, i+1, tupDesc, &isnull); + if (!binary) { + if (!isnull) { + string = (char *) (out_functions[i]) (value, elements[i]); + CopyAttributeOut(fp, string, delim); + pfree(string); + } + if (i == attr_count - 1) { + fputc('\n', fp); + }else { + /* when copying out, only use the first char of the delim + string */ + fputc(delim[0], fp); + } + }else { + /* + * only interesting thing heap_getattr tells us in this case + * is if we have a null attribute or not. + */ + if (isnull) nulls[i] = 'n'; + } + } + + if (binary) { + int32 null_ct = 0, length; + + for (i = 0; i < attr_count; i++) { + if (nulls[i] == 'n') null_ct++; + } + + length = tuple->t_len - tuple->t_hoff; + fwrite(&length, sizeof(int32), 1, fp); + fwrite(&null_ct, sizeof(int32), 1, fp); + if (null_ct > 0) { + for (i = 0; i < attr_count; i++) { + if (nulls[i] == 'n') { + fwrite(&i, sizeof(int32), 1, fp); + nulls[i] = ' '; + } + } + } + fwrite((char *) tuple + tuple->t_hoff, length, 1, fp); + } + } + + heap_endscan(scandesc); + if (binary) { + pfree(nulls); + }else { + pfree(out_functions); + pfree(elements); + } + + heap_close(rel); +} + +static void +CopyFrom(Relation rel, bool binary, FILE *fp, char *delim) +{ + HeapTuple tuple; + IndexTuple ituple; + AttrNumber attr_count; + AttributeTupleForm *attr; + func_ptr *in_functions; + int i, dummy; + Oid in_func_oid; + Datum *values; + char *nulls, *index_nulls; + bool *byval; + bool isnull; + bool has_index; + int done = 0; + char *string, *ptr; + Relation *index_rels; + int32 len, null_ct, null_id; + int32 ntuples, tuples_read = 0; + bool reading_to_eof = true; + Oid *elements; + FuncIndexInfo *finfo, **finfoP; + TupleDesc *itupdescArr; + HeapTuple pgIndexTup; + IndexTupleForm *pgIndexP; + int *indexNatts; + char *predString; + Node **indexPred; + TupleDesc rtupdesc; + ExprContext *econtext; + TupleTable tupleTable; + TupleTableSlot *slot; + int natts; + AttrNumber *attnumP; + Datum idatum; + int n_indices; + InsertIndexResult indexRes; + TupleDesc tupDesc; + + tupDesc = RelationGetTupleDescriptor(rel); + attr = tupDesc->attrs; + attr_count = tupDesc->natts; + + has_index = false; + + /* + * This may be a scalar or a functional index. We initialize all + * kinds of arrays here to avoid doing extra work at every tuple + * copy. + */ + + if (rel->rd_rel->relhasindex) { + GetIndexRelations(rel->rd_id, &n_indices, &index_rels); + if (n_indices > 0) { + has_index = true; + itupdescArr = + (TupleDesc *)palloc(n_indices * sizeof(TupleDesc)); + pgIndexP = + (IndexTupleForm *)palloc(n_indices * sizeof(IndexTupleForm)); + indexNatts = (int *) palloc(n_indices * sizeof(int)); + finfo = (FuncIndexInfo *) palloc(n_indices * sizeof(FuncIndexInfo)); + finfoP = (FuncIndexInfo **) palloc(n_indices * sizeof(FuncIndexInfo *)); + indexPred = (Node **) palloc(n_indices * sizeof(Node*)); + econtext = NULL; + for (i = 0; i < n_indices; i++) { + itupdescArr[i] = RelationGetTupleDescriptor(index_rels[i]); + pgIndexTup = + SearchSysCacheTuple(INDEXRELID, + ObjectIdGetDatum(index_rels[i]->rd_id), + 0,0,0); + Assert(pgIndexTup); + pgIndexP[i] = (IndexTupleForm)GETSTRUCT(pgIndexTup); + for (attnumP = &(pgIndexP[i]->indkey[0]), natts = 0; + *attnumP != InvalidAttrNumber; + attnumP++, natts++); + if (pgIndexP[i]->indproc != InvalidOid) { + FIgetnArgs(&finfo[i]) = natts; + natts = 1; + FIgetProcOid(&finfo[i]) = pgIndexP[i]->indproc; + *(FIgetname(&finfo[i])) = '\0'; + finfoP[i] = &finfo[i]; + } else + finfoP[i] = (FuncIndexInfo *) NULL; + indexNatts[i] = natts; + if (VARSIZE(&pgIndexP[i]->indpred) != 0) { + predString = fmgr(F_TEXTOUT, &pgIndexP[i]->indpred); + indexPred[i] = stringToNode(predString); + pfree(predString); + /* make dummy ExprContext for use by ExecQual */ + if (econtext == NULL) { +#ifndef OMIT_PARTIAL_INDEX + tupleTable = ExecCreateTupleTable(1); + slot = ExecAllocTableSlot(tupleTable); + econtext = makeNode(ExprContext); + econtext->ecxt_scantuple = slot; + rtupdesc = RelationGetTupleDescriptor(rel); + slot->ttc_tupleDescriptor = rtupdesc; + /* + * There's no buffer associated with heap tuples here, + * so I set the slot's buffer to NULL. Currently, it + * appears that the only way a buffer could be needed + * would be if the partial index predicate referred to + * the "lock" system attribute. If it did, then + * heap_getattr would call HeapTupleGetRuleLock, which + * uses the buffer's descriptor to get the relation id. + * Rather than try to fix this, I'll just disallow + * partial indexes on "lock", which wouldn't be useful + * anyway. --Nels, Nov '92 + */ + /* SetSlotBuffer(slot, (Buffer) NULL); */ + /* SetSlotShouldFree(slot, false); */ + slot->ttc_buffer = (Buffer)NULL; + slot->ttc_shouldFree = false; +#endif /* OMIT_PARTIAL_INDEX */ + } + } else { + indexPred[i] = NULL; + } + } + } + } + + if (!binary) + { + in_functions = (func_ptr *) palloc(attr_count * sizeof(func_ptr)); + elements = (Oid *) palloc(attr_count * sizeof(Oid)); + for (i = 0; i < attr_count; i++) + { + in_func_oid = (Oid) GetInputFunction(attr[i]->atttypid); + fmgr_info(in_func_oid, &in_functions[i], &dummy); + elements[i] = GetTypeElement(attr[i]->atttypid); + } + } + else + { + fread(&ntuples, sizeof(int32), 1, fp); + if (ntuples != 0) reading_to_eof = false; + } + + values = (Datum *) palloc(sizeof(Datum) * attr_count); + nulls = (char *) palloc(attr_count); + index_nulls = (char *) palloc(attr_count); + byval = (bool *) palloc(attr_count * sizeof(bool)); + + for (i = 0; i < attr_count; i++) { + nulls[i] = ' '; + index_nulls[i] = ' '; + byval[i] = (bool) IsTypeByVal(attr[i]->atttypid); + } + + while (!done) { + if (!binary) { + for (i = 0; i < attr_count && !done; i++) { + string = CopyReadAttribute(i, fp, &isnull, delim); + if (isnull) { + values[i] = PointerGetDatum(NULL); + nulls[i] = 'n'; + }else if (string == NULL) { + done = 1; + }else { + values[i] = + (Datum)(in_functions[i])(string, + elements[i], + attr[i]->attlen); + /* + * Sanity check - by reference attributes cannot return + * NULL + */ + if (!PointerIsValid(values[i]) && + !(rel->rd_att->attrs[i]->attbyval)) { + elog(WARN, "copy from: Bad file format"); + } + } + } + }else { /* binary */ + fread(&len, sizeof(int32), 1, fp); + if (feof(fp)) { + done = 1; + }else { + fread(&null_ct, sizeof(int32), 1, fp); + if (null_ct > 0) { + for (i = 0; i < null_ct; i++) { + fread(&null_id, sizeof(int32), 1, fp); + nulls[null_id] = 'n'; + } + } + + string = (char *) palloc(len); + fread(string, len, 1, fp); + + ptr = string; + + for (i = 0; i < attr_count; i++) { + if (byval[i] && nulls[i] != 'n') { + + switch(attr[i]->attlen) { + case sizeof(char): + values[i] = (Datum) *(unsigned char *) ptr; + ptr += sizeof(char); + break; + case sizeof(short): + ptr = (char *) SHORTALIGN(ptr); + values[i] = (Datum) *(unsigned short *) ptr; + ptr += sizeof(short); + break; + case sizeof(int32): + ptr = (char *) INTALIGN(ptr); + values[i] = (Datum) *(uint32 *) ptr; + ptr += sizeof(int32); + break; + default: + elog(WARN, "COPY BINARY: impossible size!"); + break; + } + }else if (nulls[i] != 'n') { + switch (attr[i]->attlen) { + case -1: + if (attr[i]->attalign == 'd') + ptr = (char *)DOUBLEALIGN(ptr); + else + ptr = (char *)INTALIGN(ptr); + values[i] = (Datum) ptr; + ptr += * (uint32 *) ptr; + break; + case sizeof(char): + values[i] = (Datum)ptr; + ptr += attr[i]->attlen; + break; + case sizeof(short): + ptr = (char*)SHORTALIGN(ptr); + values[i] = (Datum)ptr; + ptr += attr[i]->attlen; + break; + case sizeof(int32): + ptr = (char*)INTALIGN(ptr); + values[i] = (Datum)ptr; + ptr += attr[i]->attlen; + break; + default: + if (attr[i]->attalign == 'd') + ptr = (char *)DOUBLEALIGN(ptr); + else + ptr = (char *)LONGALIGN(ptr); + values[i] = (Datum) ptr; + ptr += attr[i]->attlen; + } + } + } + } + } + if (done) continue; + + tupDesc = CreateTupleDesc(attr_count, attr); + tuple = heap_formtuple(tupDesc, values, nulls); + heap_insert(rel, tuple); + + if (has_index) { + for (i = 0; i < n_indices; i++) { + if (indexPred[i] != NULL) { +#ifndef OMIT_PARTIAL_INDEX + /* if tuple doesn't satisfy predicate, + * don't update index + */ + slot->val = tuple; + /*SetSlotContents(slot, tuple); */ + if (ExecQual((List*)indexPred[i], econtext) == false) + continue; +#endif /* OMIT_PARTIAL_INDEX */ + } + FormIndexDatum(indexNatts[i], + (AttrNumber *)&(pgIndexP[i]->indkey[0]), + tuple, + tupDesc, + InvalidBuffer, + &idatum, + index_nulls, + finfoP[i]); + ituple = index_formtuple(itupdescArr[i], &idatum, index_nulls); + ituple->t_tid = tuple->t_ctid; + indexRes = index_insert(index_rels[i], ituple); + if (indexRes) pfree(indexRes); + pfree(ituple); + } + } + + if (binary) pfree(string); + + for (i = 0; i < attr_count; i++) { + if (!byval[i] && nulls[i] != 'n') { + if (!binary) pfree((void*)values[i]); + }else if (nulls[i] == 'n') { + nulls[i] = ' '; + } + } + + pfree(tuple); + tuples_read++; + + if (!reading_to_eof && ntuples == tuples_read) done = true; + } + pfree(values); + if (!binary) pfree(in_functions); + pfree(nulls); + pfree(byval); + heap_close(rel); +} + +static Oid +GetOutputFunction(Oid type) +{ + HeapTuple typeTuple; + + typeTuple = SearchSysCacheTuple(TYPOID, + ObjectIdGetDatum(type), + 0,0,0); + + if (HeapTupleIsValid(typeTuple)) + return((int) ((TypeTupleForm) GETSTRUCT(typeTuple))->typoutput); + + elog(WARN, "GetOutputFunction: Cache lookup of type %d failed", type); + return(InvalidOid); +} + +static Oid +GetTypeElement(Oid type) +{ + HeapTuple typeTuple; + + typeTuple = SearchSysCacheTuple(TYPOID, + ObjectIdGetDatum(type), + 0,0,0); + + + if (HeapTupleIsValid(typeTuple)) + return((int) ((TypeTupleForm) GETSTRUCT(typeTuple))->typelem); + + elog(WARN, "GetOutputFunction: Cache lookup of type %d failed", type); + return(InvalidOid); +} + +static Oid +GetInputFunction(Oid type) +{ + HeapTuple typeTuple; + + typeTuple = SearchSysCacheTuple(TYPOID, + ObjectIdGetDatum(type), + 0,0,0); + + if (HeapTupleIsValid(typeTuple)) + return((int) ((TypeTupleForm) GETSTRUCT(typeTuple))->typinput); + + elog(WARN, "GetInputFunction: Cache lookup of type %d failed", type); + return(InvalidOid); +} + +static Oid +IsTypeByVal(Oid type) +{ + HeapTuple typeTuple; + + typeTuple = SearchSysCacheTuple(TYPOID, + ObjectIdGetDatum(type), + 0,0,0); + + if (HeapTupleIsValid(typeTuple)) + return((int) ((TypeTupleForm) GETSTRUCT(typeTuple))->typbyval); + + elog(WARN, "GetInputFunction: Cache lookup of type %d failed", type); + + return(InvalidOid); +} + +/* + * Given the OID of a relation, return an array of index relation descriptors + * and the number of index relations. These relation descriptors are open + * using heap_open(). + * + * Space for the array itself is palloc'ed. + */ + +typedef struct rel_list { + Oid index_rel_oid; + struct rel_list *next; +} RelationList; + +static void +GetIndexRelations(Oid main_relation_oid, + int *n_indices, + Relation **index_rels) +{ + RelationList *head, *scan; + Relation pg_index_rel; + HeapScanDesc scandesc; + Oid index_relation_oid; + HeapTuple tuple; + TupleDesc tupDesc; + int i; + bool isnull; + + pg_index_rel = heap_openr(IndexRelationName); + scandesc = heap_beginscan(pg_index_rel, 0, NULL, 0, NULL); + tupDesc = RelationGetTupleDescriptor(pg_index_rel); + + *n_indices = 0; + + head = (RelationList *) palloc(sizeof(RelationList)); + scan = head; + head->next = NULL; + + for (tuple = heap_getnext(scandesc, 0, NULL); + tuple != NULL; + tuple = heap_getnext(scandesc, 0, NULL)) { + + index_relation_oid = + (Oid) DatumGetInt32(heap_getattr(tuple, InvalidBuffer, 2, + tupDesc, &isnull)); + if (index_relation_oid == main_relation_oid) { + scan->index_rel_oid = + (Oid) DatumGetInt32(heap_getattr(tuple, InvalidBuffer, + Anum_pg_index_indexrelid, + tupDesc, &isnull)); + (*n_indices)++; + scan->next = (RelationList *) palloc(sizeof(RelationList)); + scan = scan->next; + } + } + + heap_endscan(scandesc); + heap_close(pg_index_rel); + + *index_rels = (Relation *) palloc(*n_indices * sizeof(Relation)); + + for (i = 0, scan = head; i < *n_indices; i++, scan = scan->next) { + (*index_rels)[i] = index_open(scan->index_rel_oid); + } + + for (i = 0, scan = head; i < *n_indices + 1; i++) { + scan = head->next; + pfree(head); + head = scan; + } +} + +#define EXT_ATTLEN 5*8192 + +/* + returns 1 is c is in s +*/ +static bool +inString(char c, char* s) +{ + int i; + + if (s) { + i = 0; + while (s[i] != '\0') { + if (s[i] == c) + return 1; + i++; + } + } + return 0; +} + +/* + * Reads input from fp until eof is seen. If we are reading from standard + * input, AND we see a dot on a line by itself (a dot followed immediately + * by a newline), we exit as if we saw eof. This is so that copy pipelines + * can be used as standard input. + */ + +static char * +CopyReadAttribute(int attno, FILE *fp, bool *isnull, char *delim) +{ + static char attribute[EXT_ATTLEN]; + char c; + int done = 0; + int i = 0; + + if (feof(fp)) { + *isnull = (bool) false; + return(NULL); + } + + while (!done) { + c = getc(fp); + + if (feof(fp)) { + *isnull = (bool) false; + return(NULL); + }else if (reading_from_input && attno == 0 && i == 0 && c == '.') { + attribute[0] = c; + c = getc(fp); + if (c == '\n') { + *isnull = (bool) false; + return(NULL); + }else if (inString(c,delim)) { + attribute[1] = 0; + *isnull = (bool) false; + return(&attribute[0]); + }else { + attribute[1] = c; + i = 2; + } + }else if (c == '\\') { + c = getc(fp); + }else if (inString(c,delim) || c == '\n') { + done = 1; + } + if (!done) attribute[i++] = c; + if (i == EXT_ATTLEN - 1) + elog(WARN, "CopyReadAttribute - attribute length too long"); + } + attribute[i] = '\0'; + if (i == 0) { + *isnull = (bool) true; + return(NULL); + }else { + *isnull = (bool) false; + return(&attribute[0]); + } +} + +static void +CopyAttributeOut(FILE *fp, char *string, char *delim) +{ + int i; + int len = strlen(string); + + for (i = 0; i < len; i++) { + if (string[i] == delim[0] || string[i] == '\n' || string[i] == '\\') { + fputc('\\', fp); + } + fputc(string[i], fp); + } +} + +/* + * Returns the number of tuples in a relation. Unfortunately, currently + * must do a scan of the entire relation to determine this. + * + * relation is expected to be an open relation descriptor. + */ +static int +CountTuples(Relation relation) +{ + HeapScanDesc scandesc; + HeapTuple tuple; + + int i; + + scandesc = heap_beginscan(relation, 0, NULL, 0, NULL); + + for (tuple = heap_getnext(scandesc, 0, NULL), i = 0; + tuple != NULL; + tuple = heap_getnext(scandesc, 0, NULL), i++) + ; + heap_endscan(scandesc); + return(i); +} diff --git a/src/backend/commands/copy.h b/src/backend/commands/copy.h new file mode 100644 index 00000000000..ccd29555626 --- /dev/null +++ b/src/backend/commands/copy.h @@ -0,0 +1,21 @@ +/*------------------------------------------------------------------------- + * + * copy.h-- + * Definitions for using the POSTGRES copy command. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: copy.h,v 1.1.1.1 1996/07/09 06:21:19 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef COPY_H +#define COPY_H + +#include "postgres.h" + +void DoCopy(char *relname, bool binary, bool from, bool pipe, char *filename, + char *delim); + +#endif /* COPY_H */ diff --git a/src/backend/commands/creatinh.c b/src/backend/commands/creatinh.c new file mode 100644 index 00000000000..a0e3a9f682b --- /dev/null +++ b/src/backend/commands/creatinh.c @@ -0,0 +1,564 @@ +/*------------------------------------------------------------------------- + * + * creatinh.c-- + * POSTGRES create/destroy relation with inheritance utility code. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/Attic/creatinh.c,v 1.1.1.1 1996/07/09 06:21:19 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> /* for sprintf() */ +#include <string.h> +#include "postgres.h" + +#include "tcop/tcopdebug.h" + +#include "utils/builtins.h" +#include "utils/elog.h" +#include "utils/palloc.h" + +#include "nodes/pg_list.h" +#include "nodes/primnodes.h" +#include "nodes/plannodes.h" +#include "nodes/parsenodes.h" +#include "nodes/execnodes.h" + +#include "utils/syscache.h" +#include "utils/relcache.h" +#include "catalog/catname.h" +#include "catalog/pg_type.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_ipl.h" +#include "parser/catalog_utils.h" + +#include "commands/creatinh.h" + +#include "access/tupdesc.h" +#include "access/heapam.h" +#include "access/xact.h" + +/* ---------------- + * local stuff + * ---------------- + */ + +static int checkAttrExists(char *attributeName, + char *attributeType, List *schema); +static List *MergeAttributes(List *schema, List *supers); +static void StoreCatalogInheritance(Oid relationId, List *supers); + +/* ---------------------------------------------------------------- + * DefineRelation -- + * Creates a new relation. + * ---------------------------------------------------------------- + */ +void +DefineRelation(CreateStmt *stmt) +{ + char *relname = stmt->relname; + List *schema = stmt->tableElts; + int numberOfAttributes; + Oid relationId; + char archChar; + List *inheritList = NULL; + char *archiveName = NULL; + TupleDesc descriptor; + int heaploc, archloc; + + char* typename = NULL; /* the typename of this relation. not useod for now */ + + if ( strlen(relname) > NAMEDATALEN) + elog(WARN, "the relation name %s is > %d characters long", relname, + NAMEDATALEN); + + /* ---------------- + * Handle parameters + * XXX parameter handling missing below. + * ---------------- + */ + inheritList = stmt->inhRelnames; + + /* ---------------- + * determine archive mode + * XXX use symbolic constants... + * ---------------- + */ + archChar = 'n'; + + switch (stmt->archiveType) { + case ARCH_NONE: + archChar = 'n'; + break; + case ARCH_LIGHT: + archChar = 'l'; + break; + case ARCH_HEAVY: + archChar = 'h'; + break; + default: + elog(WARN, "Botched archive mode %d, ignoring", + stmt->archiveType); + break; + } + + if (stmt->location == -1) + heaploc = 0; + else + heaploc = stmt->location; + + /* + * For now, any user-defined relation defaults to the magnetic + * disk storgage manager. --mao 2 july 91 + */ + if (stmt->archiveLoc == -1) { + archloc = 0; + } else { + if (archChar == 'n') { + elog(WARN, "Set archive location, but not mode, for %s", + relname); + } + archloc = stmt->archiveLoc; + } + + /* ---------------- + * generate relation schema, including inherited attributes. + * ---------------- + */ + schema = MergeAttributes(schema, inheritList); + + numberOfAttributes = length(schema); + if (numberOfAttributes <= 0) { + elog(WARN, "DefineRelation: %s", + "please inherit from a relation or define an attribute"); + } + + /* ---------------- + * create a relation descriptor from the relation schema + * and create the relation. + * ---------------- + */ + descriptor = BuildDescForRelation(schema, relname); + relationId = heap_create(relname, + typename, + archChar, + heaploc, + descriptor); + + StoreCatalogInheritance(relationId, inheritList); + + /* ---------------- + * create an archive relation if necessary + * ---------------- + */ + if (archChar != 'n') { + /* + * Need to create an archive relation for this heap relation. + * We cobble up the command by hand, and increment the command + * counter ourselves. + */ + + CommandCounterIncrement(); + archiveName = MakeArchiveName(relationId); + + relationId = heap_create(archiveName, + typename, + 'n', /* archive isn't archived */ + archloc, + descriptor); + + pfree(archiveName); + } +} + +/* + * RemoveRelation -- + * Deletes a new relation. + * + * Exceptions: + * BadArg if name is invalid. + * + * Note: + * If the relation has indices defined on it, then the index relations + * themselves will be destroyed, too. + */ +void +RemoveRelation(char *name) +{ + AssertArg(name); + heap_destroy(name); +} + + +/* + * MergeAttributes -- + * Returns new schema given initial schema and supers. + * + * + * 'schema' is the column/attribute definition for the table. (It's a list + * of ColumnDef's.) It is destructively changed. + * 'inheritList' is the list of inherited relations (a list of Value(str)'s). + * + * Notes: + * The order in which the attributes are inherited is very important. + * Intuitively, the inherited attributes should come first. If a table + * inherits from multiple parents, the order of those attributes are + * according to the order of the parents specified in CREATE TABLE. + * + * Here's an example: + * + * create table person (name text, age int4, location point); + * create table emp (salary int4, manager char16) inherits(person); + * create table student (gpa float8) inherits (person); + * create table stud_emp (percent int4) inherits (emp, student); + * + * the order of the attributes of stud_emp is as follow: + * + * + * person {1:name, 2:age, 3:location} + * / \ + * {6:gpa} student emp {4:salary, 5:manager} + * \ / + * stud_emp {7:percent} + */ +static List * +MergeAttributes(List *schema, List *supers) +{ + List *entry; + List *inhSchema = NIL; + + /* + * Validates that there are no duplications. + * Validity checking of types occurs later. + */ + foreach (entry, schema) { + List *rest; + ColumnDef *coldef = lfirst(entry); + + foreach (rest, lnext(entry)) { + /* + * check for duplicated relation names + */ + ColumnDef *restdef = lfirst(rest); + + if (!strcmp(coldef->colname, restdef->colname)) { + elog(WARN, "attribute \"%s\" duplicated", + coldef->colname); + } + } + } + foreach (entry, supers) { + List *rest; + + foreach (rest, lnext(entry)) { + if (!strcmp(strVal(lfirst(entry)), strVal(lfirst(rest)))) { + elog(WARN, "relation \"%s\" duplicated", + strVal(lfirst(entry))); + } + } + } + + /* + * merge the inherited attributes into the schema + */ + foreach (entry, supers) { + char *name = strVal(lfirst(entry)); + Relation relation; + List *partialResult = NIL; + AttrNumber attrno; + TupleDesc tupleDesc; + + relation = heap_openr(name); + if (relation==NULL) { + elog(WARN, + "MergeAttr: Can't inherit from non-existent superclass '%s'", + name); + } + tupleDesc = RelationGetTupleDescriptor(relation); + + for (attrno = relation->rd_rel->relnatts - 1; attrno >= 0; attrno--) { + AttributeTupleForm attribute = tupleDesc->attrs[attrno]; + char *attributeName; + char *attributeType; + HeapTuple tuple; + ColumnDef *def; + TypeName *typename; + + /* + * form name and type + */ + attributeName = (attribute->attname).data; + tuple = + SearchSysCacheTuple(TYPOID, + ObjectIdGetDatum(attribute->atttypid), + 0,0,0); + AssertState(HeapTupleIsValid(tuple)); + attributeType = + (((TypeTupleForm)GETSTRUCT(tuple))->typname).data; + /* + * check validity + * + */ + if (checkAttrExists(attributeName, attributeType, inhSchema) || + checkAttrExists(attributeName, attributeType, schema)) { + /* + * this entry already exists + */ + continue; + } + + /* + * add an entry to the schema + */ + def = makeNode(ColumnDef); + typename = makeNode(TypeName); + def->colname = pstrdup(attributeName); + typename->name = pstrdup(attributeType); + def->typename = typename; + partialResult = lcons(def, partialResult); + } + + /* + * iteration cleanup and result collection + */ + heap_close(relation); + + /* + * wants the inherited schema to appear in the order they are + * specified in CREATE TABLE + */ + inhSchema = nconc(inhSchema, partialResult); + } + + /* + * put the inherited schema before our the schema for this table + */ + schema = nconc(inhSchema, schema); + + return (schema); +} + +/* + * StoreCatalogInheritance -- + * Updates the system catalogs with proper inheritance information. + */ +static void +StoreCatalogInheritance(Oid relationId, List *supers) +{ + Relation relation; + TupleDesc desc; + int16 seqNumber; + List *entry; + List *idList; + HeapTuple tuple; + + /* ---------------- + * sanity checks + * ---------------- + */ + AssertArg(OidIsValid(relationId)); + + if (supers==NIL) + return; + + /* ---------------- + * Catalog INHERITS information. + * ---------------- + */ + relation = heap_openr( InheritsRelationName ); + desc = RelationGetTupleDescriptor(relation); + + seqNumber = 1; + idList = NIL; + foreach (entry, supers) { + Datum datum[ Natts_pg_inherits ]; + char nullarr[ Natts_pg_inherits ]; + + tuple = SearchSysCacheTuple(RELNAME, + PointerGetDatum(strVal(lfirst(entry))), + 0,0,0); + AssertArg(HeapTupleIsValid(tuple)); + + /* + * build idList for use below + */ + idList = lappendi(idList, tuple->t_oid); + + datum[0] = ObjectIdGetDatum(relationId); /* inhrel */ + datum[1] = ObjectIdGetDatum(tuple->t_oid); /* inhparent */ + datum[2] = Int16GetDatum(seqNumber); /* inhseqno */ + + nullarr[0] = ' '; + nullarr[1] = ' '; + nullarr[2] = ' '; + + tuple = heap_formtuple(desc,datum, nullarr); + + (void) heap_insert(relation, tuple); + pfree(tuple); + + seqNumber += 1; + } + + heap_close(relation); + + /* ---------------- + * Catalog IPL information. + * + * Algorithm: + * 0. list superclasses (by Oid) in order given (see idList). + * 1. append after each relationId, its superclasses, recursively. + * 3. remove all but last of duplicates. + * 4. store result. + * ---------------- + */ + + /* ---------------- + * 1. + * ---------------- + */ + foreach (entry, idList) { + HeapTuple tuple; + Oid id; + int16 number; + List *next; + List *current; + + id = (Oid)lfirsti(entry); + current = entry; + next = lnext(entry); + + for (number = 1; ; number += 1) { + tuple = SearchSysCacheTuple(INHRELID, + ObjectIdGetDatum(id), + Int16GetDatum(number), + 0,0); + + if (! HeapTupleIsValid(tuple)) + break; + + lnext(current) = + lconsi(((InheritsTupleForm) + GETSTRUCT(tuple))->inhparent, + NIL); + + current = lnext(current); + } + lnext(current) = next; + } + + /* ---------------- + * 2. + * ---------------- + */ + foreach (entry, idList) { + Oid name; + List *rest; + bool found = false; + + again: + name = lfirsti(entry); + foreach (rest, lnext(entry)) { + if (name == lfirsti(rest)) { + found = true; + break; + } + } + if (found) { + /* + * entry list must be of length >= 2 or else no match + * + * so, remove this entry. + */ + lfirst(entry) = lfirst(lnext(entry)); + lnext(entry) = lnext(lnext(entry)); + + found = false; + goto again; + } + } + + /* ---------------- + * 3. + * ---------------- + */ + relation = heap_openr( InheritancePrecidenceListRelationName ); + desc = RelationGetTupleDescriptor(relation); + + seqNumber = 1; + + foreach (entry, idList) { + Datum datum[ Natts_pg_ipl ]; + char nullarr[ Natts_pg_ipl ]; + + datum[0] = ObjectIdGetDatum(relationId); /* iplrel */ + datum[1] = ObjectIdGetDatum(lfirsti(entry)); + /*iplinherits*/ + datum[2] = Int16GetDatum(seqNumber); /* iplseqno */ + + nullarr[0] = ' '; + nullarr[1] = ' '; + nullarr[2] = ' '; + + tuple = heap_formtuple( desc, datum, nullarr); + + (void) heap_insert(relation, tuple); + pfree(tuple); + + seqNumber += 1; + } + + heap_close(relation); +} + +/* + * returns 1 if attribute already exists in schema, 0 otherwise. + */ +static int +checkAttrExists(char *attributeName, char *attributeType, List *schema) +{ + List *s; + + foreach (s, schema) { + ColumnDef *def = lfirst(s); + + if (!strcmp(attributeName, def->colname)) { + /* + * attribute exists. Make sure the types are the same. + */ + if (strcmp(attributeType, def->typename->name) != 0) { + elog(WARN, "%s and %s conflict for %s", + attributeType, def->typename->name, attributeName); + } + return 1; + } + } + return 0; +} + +/* + * MakeArchiveName + * make an archive rel name out of a regular rel name + * +* the CALLER is responsible for freeing the memory allocated + */ + +char* +MakeArchiveName(Oid relationId) +{ + char *arch; + + /* + * Archive relations are named a,XXXXX where XXXXX == the OID + * of the relation they archive. Create a string containing + * this name and find the reldesc for the archive relation. + */ + arch = palloc(NAMEDATALEN); + sprintf(arch, "a,%d",relationId); + + return arch; +} + diff --git a/src/backend/commands/creatinh.h b/src/backend/commands/creatinh.h new file mode 100644 index 00000000000..a86fd4ed82b --- /dev/null +++ b/src/backend/commands/creatinh.h @@ -0,0 +1,20 @@ +/*------------------------------------------------------------------------- + * + * creatinh.h-- + * prototypes for creatinh.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: creatinh.h,v 1.1.1.1 1996/07/09 06:21:20 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef CREATINH_H +#define CREATINH_H + +extern void DefineRelation(CreateStmt *stmt); +extern void RemoveRelation(char *name); +extern char* MakeArchiveName(Oid relid); + +#endif /* CREATINH_H */ diff --git a/src/backend/commands/defind.c b/src/backend/commands/defind.c new file mode 100644 index 00000000000..da797e23cbb --- /dev/null +++ b/src/backend/commands/defind.c @@ -0,0 +1,505 @@ +/*------------------------------------------------------------------------- + * + * defind.c-- + * POSTGRES define, extend and remove index code. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/Attic/defind.c,v 1.1.1.1 1996/07/09 06:21:20 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/attnum.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup.h" +#include "access/funcindex.h" +#include "utils/builtins.h" +#include "utils/syscache.h" +#include "catalog/index.h" +#include "catalog/pg_index.h" +#include "catalog/pg_proc.h" +#include "nodes/pg_list.h" +#include "nodes/plannodes.h" +#include "nodes/primnodes.h" +#include "nodes/relation.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/relcache.h" +#include "utils/lsyscache.h" + +#include "commands/defrem.h" +#include "parser/parsetree.h" /* for getrelid() */ + +#include "optimizer/prep.h" +#include "optimizer/clauses.h" +#include "storage/lmgr.h" + +#define IsFuncIndex(ATTR_LIST) (((IndexElem*)lfirst(ATTR_LIST))->args!=NULL) + +/* non-export function prototypes */ +static void CheckPredicate(List *predList, List *rangeTable, Oid baseRelOid); +static void CheckPredExpr(Node *predicate, List *rangeTable, + Oid baseRelOid); +static void +CheckPredClause(Expr *predicate, List *rangeTable, Oid baseRelOid); +static void FuncIndexArgs(IndexElem *funcIndex, AttrNumber *attNumP, + Oid *argTypes, Oid *opOidP, Oid relId); +static void NormIndexAttrs(List *attList, AttrNumber *attNumP, + Oid *opOidP, Oid relId); + +/* + * DefineIndex -- + * Creates a new index. + * + * 'attributeList' is a list of IndexElem specifying either a functional + * index or a list of attributes to index on. + * 'parameterList' is a list of ParamString specified in the with clause. + * 'predicate' is the qual specified in the where clause. + * 'rangetable' is for the predicate + * + * Exceptions: + * XXX + */ +void +DefineIndex(char *heapRelationName, + char *indexRelationName, + char *accessMethodName, + List *attributeList, + List *parameterList, + Expr *predicate, + List *rangetable) +{ + Oid *classObjectId; + Oid accessMethodId; + Oid relationId; + int numberOfAttributes; + AttrNumber *attributeNumberA; + HeapTuple tuple; + uint16 parameterCount = 0; + Datum *parameterA = NULL; + FuncIndexInfo fInfo; + List *cnfPred = NULL; + + + /* + * Handle attributes + */ + numberOfAttributes = length(attributeList); + if (numberOfAttributes <= 0) { + elog(WARN, "DefineIndex: must specify at least one attribute"); + } + + /* + * compute heap relation id + */ + tuple = SearchSysCacheTuple(RELNAME, + PointerGetDatum(heapRelationName), + 0,0,0); + if (!HeapTupleIsValid(tuple)) { + elog(WARN, "DefineIndex: %s relation not found", + heapRelationName); + } + relationId = tuple->t_oid; + + /* + * compute access method id + */ + tuple = SearchSysCacheTuple(AMNAME, PointerGetDatum(accessMethodName), + 0,0,0); + if (!HeapTupleIsValid(tuple)) { + elog(WARN, "DefineIndex: %s access method not found", + accessMethodName); + } + accessMethodId = tuple->t_oid; + + + /* + * Handle parameters + * [param list is now different (NOT USED, really) - ay 10/94] + */ + + + /* + * Convert the partial-index predicate from parsetree form to plan + * form, so it can be readily evaluated during index creation. + * Note: "predicate" comes in as a list containing (1) the predicate + * itself (a where_clause), and (2) a corresponding range table. + * + * [(1) is 'predicate' and (2) is 'rangetable' now. - ay 10/94] + */ + if (predicate != NULL && rangetable != NIL) { + cnfPred = cnfify((Expr*)copyObject(predicate), true); + fix_opids(cnfPred); + CheckPredicate(cnfPred, rangetable, relationId); + } + + if (IsFuncIndex(attributeList)) { + IndexElem *funcIndex= lfirst(attributeList); + int nargs; + + nargs = length(funcIndex->args); + if (nargs > INDEX_MAX_KEYS) { + elog(WARN, + "Too many args to function, limit of %d", + INDEX_MAX_KEYS); + } + + FIsetnArgs(&fInfo,nargs); + + strcpy(FIgetname(&fInfo), funcIndex->name); + + attributeNumberA = + (AttrNumber *)palloc(nargs * sizeof attributeNumberA[0]); + + classObjectId = (Oid *)palloc(sizeof classObjectId[0]); + + + FuncIndexArgs(funcIndex, attributeNumberA, + &(FIgetArg(&fInfo, 0)), + classObjectId, relationId); + + index_create(heapRelationName, + indexRelationName, + &fInfo, accessMethodId, + numberOfAttributes, attributeNumberA, + classObjectId, parameterCount, parameterA, (Node*)cnfPred); + }else { + attributeNumberA = + (AttrNumber *)palloc(numberOfAttributes * + sizeof attributeNumberA[0]); + + classObjectId = + (Oid *)palloc(numberOfAttributes * sizeof classObjectId[0]); + + NormIndexAttrs(attributeList, attributeNumberA, + classObjectId, relationId); + + index_create(heapRelationName, indexRelationName, NULL, + accessMethodId, numberOfAttributes, attributeNumberA, + classObjectId, parameterCount, parameterA, (Node*)cnfPred); + } +} + + +/* + * ExtendIndex -- + * Extends a partial index. + * + * Exceptions: + * XXX + */ +void +ExtendIndex(char *indexRelationName, Expr *predicate, List *rangetable) +{ + Oid *classObjectId; + Oid accessMethodId; + Oid indexId, relationId; + Oid indproc; + int numberOfAttributes; + AttrNumber *attributeNumberA; + HeapTuple tuple; + FuncIndexInfo fInfo; + FuncIndexInfo *funcInfo = NULL; + IndexTupleForm index; + Node *oldPred = NULL; + List *cnfPred = NULL; + PredInfo *predInfo; + Relation heapRelation; + Relation indexRelation; + int i; + + /* + * compute index relation id and access method id + */ + tuple = SearchSysCacheTuple(RELNAME, PointerGetDatum(indexRelationName), + 0,0,0); + if (!HeapTupleIsValid(tuple)) { + elog(WARN, "ExtendIndex: %s index not found", + indexRelationName); + } + indexId = tuple->t_oid; + accessMethodId = ((Form_pg_class) GETSTRUCT(tuple))->relam; + + /* + * find pg_index tuple + */ + tuple = SearchSysCacheTuple(INDEXRELID, + ObjectIdGetDatum(indexId), + 0,0,0); + if (!HeapTupleIsValid(tuple)) { + elog(WARN, "ExtendIndex: %s is not an index", + indexRelationName); + } + + /* + * Extract info from the pg_index tuple + */ + index = (IndexTupleForm)GETSTRUCT(tuple); + Assert(index->indexrelid == indexId); + relationId = index->indrelid; + indproc = index->indproc; + + for (i=0; i<INDEX_MAX_KEYS; i++) + if (index->indkey[i] == 0) break; + numberOfAttributes = i; + + if (VARSIZE(&index->indpred) != 0) { + char *predString; + + predString = fmgr(F_TEXTOUT, &index->indpred); + oldPred = stringToNode(predString); + pfree(predString); + } + if (oldPred == NULL) + elog(WARN, "ExtendIndex: %s is not a partial index", + indexRelationName); + + /* + * Convert the extension predicate from parsetree form to plan + * form, so it can be readily evaluated during index creation. + * Note: "predicate" comes in as a list containing (1) the predicate + * itself (a where_clause), and (2) a corresponding range table. + */ + if (rangetable != NIL) { + cnfPred = cnfify((Expr*)copyObject(predicate), true); + fix_opids(cnfPred); + CheckPredicate(cnfPred, rangetable, relationId); + } + + /* make predInfo list to pass to index_build */ + predInfo = (PredInfo*)palloc(sizeof(PredInfo)); + predInfo->pred = (Node*)cnfPred; + predInfo->oldPred = oldPred; + + attributeNumberA = + (AttrNumber *)palloc(numberOfAttributes* + sizeof attributeNumberA[0]); + classObjectId = + (Oid *)palloc(numberOfAttributes * sizeof classObjectId[0]); + + + for (i=0; i<numberOfAttributes; i++) { + attributeNumberA[i] = index->indkey[i]; + classObjectId[i] = index->indclass[i]; + } + + if (indproc != InvalidOid) { + funcInfo = &fInfo; +/* FIgetnArgs(funcInfo) = numberOfAttributes; */ + FIsetnArgs(funcInfo,numberOfAttributes); + + tuple = SearchSysCacheTuple(PROOID, + ObjectIdGetDatum(indproc), + 0,0,0); + if (!HeapTupleIsValid(tuple)) + elog(WARN, "ExtendIndex: index procedure not found"); + + namecpy(&(funcInfo->funcName), + &(((Form_pg_proc) GETSTRUCT(tuple))->proname)); + + FIsetProcOid(funcInfo,tuple->t_oid); + } + + heapRelation = heap_open(relationId); + indexRelation = index_open(indexId); + + RelationSetLockForWrite(heapRelation); + + InitIndexStrategy(numberOfAttributes, indexRelation, accessMethodId); + + index_build(heapRelation, indexRelation, numberOfAttributes, + attributeNumberA, 0, NULL, funcInfo, predInfo); +} + + +/* + * CheckPredicate + * Checks that the given list of partial-index predicates refer + * (via the given range table) only to the given base relation oid, + * and that they're in a form the planner can handle, i.e., + * boolean combinations of "ATTR OP CONST" (yes, for now, the ATTR + * has to be on the left). + */ + +static void +CheckPredicate(List *predList, List *rangeTable, Oid baseRelOid) +{ + List *item; + + foreach (item, predList) { + CheckPredExpr(lfirst(item), rangeTable, baseRelOid); + } +} + +static void +CheckPredExpr(Node *predicate, List *rangeTable, Oid baseRelOid) +{ + List *clauses = NIL, *clause; + + if (is_opclause(predicate)) { + CheckPredClause((Expr*)predicate, rangeTable, baseRelOid); + return; + } else if (or_clause(predicate)) + clauses = ((Expr*)predicate)->args; + else if (and_clause(predicate)) + clauses = ((Expr*)predicate)->args; + else + elog(WARN, "Unsupported partial-index predicate expression type"); + + foreach (clause, clauses) { + CheckPredExpr(lfirst(clause), rangeTable, baseRelOid); + } +} + +static void +CheckPredClause(Expr *predicate, List *rangeTable, Oid baseRelOid) +{ + Var *pred_var; + Const *pred_const; + + pred_var = (Var *)get_leftop(predicate); + pred_const = (Const *)get_rightop(predicate); + + if (!IsA(predicate->oper,Oper) || + !IsA(pred_var,Var) || + !IsA(pred_const,Const)) { + elog(WARN, "Unsupported partial-index predicate clause type"); + } + + if (getrelid(pred_var->varno, rangeTable) != baseRelOid) + elog(WARN, + "Partial-index predicates may refer only to the base relation"); +} + + +static void +FuncIndexArgs(IndexElem *funcIndex, + AttrNumber *attNumP, + Oid *argTypes, + Oid *opOidP, + Oid relId) +{ + List *rest; + HeapTuple tuple; + AttributeTupleForm att; + + tuple = SearchSysCacheTuple(CLANAME, + PointerGetDatum(funcIndex->class), + 0,0,0); + + if (!HeapTupleIsValid(tuple)) + { + elog(WARN, "DefineIndex: %s class not found", + funcIndex->class); + } + *opOidP = tuple->t_oid; + + memset(argTypes, 0, 8 * sizeof(Oid)); + + /* + * process the function arguments + */ + for (rest=funcIndex->args; rest != NIL; rest = lnext(rest)) { + char *arg; + + arg = strVal(lfirst(rest)); + + tuple = SearchSysCacheTuple(ATTNAME, + ObjectIdGetDatum(relId), + PointerGetDatum(arg),0,0); + + if (!HeapTupleIsValid(tuple)) { + elog(WARN, + "DefineIndex: attribute \"%s\" not found", + arg); + } + att = (AttributeTupleForm)GETSTRUCT(tuple); + *attNumP++ = att->attnum; + *argTypes++ = att->atttypid; + } +} + +static void +NormIndexAttrs(List *attList, /* list of IndexElem's */ + AttrNumber *attNumP, + Oid *opOidP, + Oid relId) +{ + List *rest; + HeapTuple tuple; + + /* + * process attributeList + */ + + for (rest=attList; rest != NIL; rest = lnext(rest)) { + IndexElem *attribute; + + attribute = lfirst(rest); + + if (attribute->class == NULL) { + elog(WARN, + "DefineIndex: default index class unsupported"); + } + + if (attribute->name == NULL) + elog(WARN, "missing attribute for define index"); + + tuple = SearchSysCacheTuple(ATTNAME, + ObjectIdGetDatum(relId), + PointerGetDatum(attribute->name), + 0,0); + if (!HeapTupleIsValid(tuple)) { + elog(WARN, + "DefineIndex: attribute \"%s\" not found", + attribute->name); + } + *attNumP++ = ((AttributeTupleForm)GETSTRUCT(tuple))->attnum; + + tuple = SearchSysCacheTuple(CLANAME, + PointerGetDatum(attribute->class), + 0,0,0); + + if (!HeapTupleIsValid(tuple)) { + elog(WARN, "DefineIndex: %s class not found", + attribute->class); + } + *opOidP++ = tuple->t_oid; + } +} + +/* + * RemoveIndex -- + * Deletes an index. + * + * Exceptions: + * BadArg if name is invalid. + * "WARN" if index nonexistant. + * ... + */ +void +RemoveIndex(char *name) +{ + HeapTuple tuple; + + tuple = SearchSysCacheTuple(RELNAME, + PointerGetDatum(name), + 0,0,0); + + if (!HeapTupleIsValid(tuple)) { + elog(WARN, "index \"%s\" nonexistant", name); + } + + if (((Form_pg_class)GETSTRUCT(tuple))->relkind != RELKIND_INDEX) { + elog(WARN, "relation \"%s\" is of type \"%c\"", + name, + ((Form_pg_class)GETSTRUCT(tuple))->relkind); + } + + index_destroy(tuple->t_oid); +} diff --git a/src/backend/commands/define.c b/src/backend/commands/define.c new file mode 100644 index 00000000000..4ba38c793c7 --- /dev/null +++ b/src/backend/commands/define.c @@ -0,0 +1,564 @@ +/*------------------------------------------------------------------------- + * + * define.c-- + * POSTGRES "define" utility code. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/define.c,v 1.1.1.1 1996/07/09 06:21:20 scrappy Exp $ + * + * DESCRIPTION + * The "DefineFoo" routines take the parse tree and pick out the + * appropriate arguments/flags, passing the results to the + * corresponding "FooDefine" routines (in src/catalog) that do + * the actual catalog-munging. + * + * NOTES + * These things must be defined and committed in the following order: + * "define function": + * input/output, recv/send procedures + * "define type": + * type + * "define operator": + * operators + * + * Most of the parse-tree manipulation routines are defined in + * commands/manip.c. + * + *------------------------------------------------------------------------- + */ +#include <string.h> +#include <ctype.h> +#include <math.h> + +#include "postgres.h" + +#include "access/heapam.h" +#include "access/htup.h" +#include "utils/tqual.h" +#include "catalog/catname.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "utils/syscache.h" +#include "nodes/pg_list.h" +#include "nodes/parsenodes.h" +#include "fmgr.h" /* for fmgr */ + +#include "utils/builtins.h" /* prototype for textin() */ + +#include "utils/elog.h" +#include "utils/palloc.h" +#include "commands/defrem.h" +#include "optimizer/xfunc.h" +#include "tcop/dest.h" + +static char *defGetString(DefElem *def); +static int defGetTypeLength(DefElem *def); + +#define DEFAULT_TYPDELIM ',' + +/* + * DefineFunction -- + * Registers a new function. + * + */ +void +DefineFunction(ProcedureStmt *stmt, CommandDest dest) +{ + List *parameters = stmt->withClause; + char *proname = stmt->funcname; + char* probin_str; + char* prosrc_str; + char *prorettype; + char *languageName; + bool canCache; + bool trusted = TRUE; + List *argList; + int32 byte_pct, perbyte_cpu, percall_cpu, outin_ratio; + bool returnsSet; + int i; + + /* ---------------- + * figure out the language and convert it to lowercase. + * ---------------- + */ + languageName = stmt->language; + for (i = 0; i < NAMEDATALEN && languageName[i]; ++i) { + languageName[i] = tolower(languageName[i]); + } + + /* ---------------- + * handle "returntype = X". The function could return a singleton + * value or a set of values. Figure out which. + * ---------------- + */ + if (nodeTag(stmt->returnType)==T_TypeName) { + TypeName *setType = (TypeName *)stmt->returnType; + /* a set of values */ + prorettype = setType->name, + returnsSet = true; + }else { + /* singleton */ + prorettype = strVal(stmt->returnType); + returnsSet = false; + } + + /* Next attributes are only defined for C functions */ + if ( strcmp(languageName, "c") == 0 || + strcmp(languageName, "internal") == 0 ) { + List *pl; + + /* the defaults */ + canCache = FALSE; + byte_pct = BYTE_PCT; + perbyte_cpu = PERBYTE_CPU; + percall_cpu = PERCALL_CPU; + outin_ratio = OUTIN_RATIO; + + foreach(pl, parameters) { + int count; + char *ptr; + ParamString *param = (ParamString*)lfirst(pl); + + if (!strcasecmp(param->name, "isacachable")) { + /* ---------------- + * handle "[ iscachable ]": figure out if Postquel functions + * are cacheable automagically? + * ---------------- + */ + canCache = TRUE; + }else if (!strcasecmp(param->name, "trusted")) { + /* + * we don't have untrusted functions any more. The 4.2 + * implementation is lousy anyway so I took it out. + * -ay 10/94 + */ + elog(WARN, "untrusted function has been decommissioned."); + }else if (!strcasecmp(param->name, "byte_pct")) { + /* + ** handle expensive function parameters + */ + byte_pct = atoi(param->val); + }else if (!strcasecmp(param->name, "perbyte_cpu")) { + if (!sscanf(param->val, "%d", &perbyte_cpu)) { + for (count = 0, ptr = param->val; *ptr != '\0'; ptr++) { + if (*ptr == '!') { + count++; + } + } + perbyte_cpu = (int) pow(10.0, (double) count); + } + }else if (!strcasecmp(param->name, "percall_cpu")) { + if (!sscanf(param->val, "%d", &percall_cpu)) { + for (count = 0, ptr = param->val; *ptr != '\0'; ptr++) { + if (*ptr == '!') { + count++; + } + } + percall_cpu = (int) pow(10.0, (double) count); + } + }else if (!strcasecmp(param->name, "outin_ratio")) { + outin_ratio = atoi(param->val); + } + } + } else if (!strcmp(languageName, "sql")) { + canCache = false; + trusted = true; + + /* query optimizer groks sql, these are meaningless */ + perbyte_cpu = percall_cpu = 0; + byte_pct = outin_ratio = 100; + } else { + elog(WARN, "DefineFunction: language '%s' is not supported", + languageName); + } + + /* ---------------- + * handle "[ arg is (...) ]" + * XXX fix optional arg handling below + * ---------------- + */ + argList = stmt->defArgs; + + if ( strcmp(languageName, "c") == 0 || + strcmp(languageName, "internal") == 0 ) { + prosrc_str = "-"; + probin_str = stmt->as; + } else { + prosrc_str = stmt->as; + probin_str = "-"; + } + + /* C is stored uppercase in pg_language */ + if (!strcmp(languageName, "c")) { + languageName[0] = 'C'; + } + + /* ---------------- + * now have ProcedureDefine do all the work.. + * ---------------- + */ + ProcedureCreate(proname, + returnsSet, + prorettype, + languageName, + prosrc_str, /* converted to text later */ + probin_str, /* converted to text later */ + canCache, + trusted, + byte_pct, + perbyte_cpu, + percall_cpu, + outin_ratio, + argList, + dest); + +} + +/* -------------------------------- + * DefineOperator-- + * + * this function extracts all the information from the + * parameter list generated by the parser and then has + * OperatorCreate() do all the actual work. + * + * 'parameters' is a list of DefElem + * -------------------------------- + */ +void +DefineOperator(char *oprName, + List *parameters) +{ + uint16 precedence=0; /* operator precedence */ + bool canHash=false; /* operator hashes */ + bool isLeftAssociative=true; /* operator is left associative */ + char *functionName=NULL; /* function for operator */ + char *typeName1=NULL; /* first type name */ + char *typeName2=NULL; /* second type name */ + char *commutatorName=NULL; /* optional commutator operator name */ + char *negatorName=NULL; /* optional negator operator name */ + char *restrictionName=NULL; /* optional restrict. sel. procedure */ + char *joinName=NULL; /* optional join sel. procedure name */ + char *sortName1=NULL; /* optional first sort operator */ + char *sortName2=NULL; /* optional second sort operator */ + List *pl; + + /* + * loop over the definition list and extract the information we need. + */ + foreach (pl, parameters) { + DefElem *defel = (DefElem *)lfirst(pl); + + if (!strcasecmp(defel->defname, "leftarg")) { + /* see gram.y, must be setof */ + if (nodeTag(defel->arg)==T_TypeName) + elog(WARN, "setof type not implemented for leftarg"); + + if (nodeTag(defel->arg)==T_String) { + typeName1 = defGetString(defel); + }else { + elog(WARN, "type for leftarg is malformed."); + } + } else if (!strcasecmp(defel->defname, "rightarg")) { + /* see gram.y, must be setof */ + if (nodeTag(defel->arg)==T_TypeName) + elog(WARN, "setof type not implemented for rightarg"); + + if (nodeTag(defel->arg)==T_String) { + typeName2 = defGetString(defel); + }else { + elog(WARN, "type for rightarg is malformed."); + } + } else if (!strcasecmp(defel->defname, "procedure")) { + functionName = defGetString(defel); + } else if (!strcasecmp(defel->defname, "precedence")) { + /* NOT IMPLEMENTED (never worked in v4.2) */ + elog(NOTICE, "CREATE OPERATOR: precedence not implemented"); + } else if (!strcasecmp(defel->defname, "associativity")) { + /* NOT IMPLEMENTED (never worked in v4.2) */ + elog(NOTICE, "CREATE OPERATOR: associativity not implemented"); + } else if (!strcasecmp(defel->defname, "commutator")) { + commutatorName = defGetString(defel); + } else if (!strcasecmp(defel->defname, "negator")) { + negatorName = defGetString(defel); + } else if (!strcasecmp(defel->defname, "restrict")) { + restrictionName = defGetString(defel); + } else if (!strcasecmp(defel->defname, "join")) { + joinName = defGetString(defel); + } else if (!strcasecmp(defel->defname, "hashes")) { + canHash = TRUE; + } else if (!strcasecmp(defel->defname, "sort1")) { + /* ---------------- + * XXX ( ... [ , sort1 = oprname ] [ , sort2 = oprname ] ... ) + * XXX is undocumented in the reference manual source as of + * 89/8/22. + * ---------------- + */ + sortName1 = defGetString(defel); + } else if (!strcasecmp(defel->defname, "sort2")) { + sortName2 = defGetString(defel); + } else { + elog(NOTICE, "DefineOperator: attribute \"%s\" not recognized", + defel->defname); + } + } + + /* + * make sure we have our required definitions + */ + if (functionName==NULL) { + elog(WARN, "Define: \"procedure\" unspecified"); + } + + /* ---------------- + * now have OperatorCreate do all the work.. + * ---------------- + */ + OperatorCreate(oprName, /* operator name */ + typeName1, /* first type name */ + typeName2, /* second type name */ + functionName, /* function for operator */ + precedence, /* operator precedence */ + isLeftAssociative, /* operator is left associative */ + commutatorName, /* optional commutator operator name */ + negatorName, /* optional negator operator name */ + restrictionName, /* optional restrict. sel. procedure */ + joinName, /* optional join sel. procedure name */ + canHash, /* operator hashes */ + sortName1, /* optional first sort operator */ + sortName2); /* optional second sort operator */ + +} + +/* ------------------- + * DefineAggregate + * ------------------ + */ +void +DefineAggregate(char *aggName, List *parameters) + +{ + char *stepfunc1Name = NULL; + char *stepfunc2Name = NULL; + char *finalfuncName = NULL; + char *baseType = NULL; + char *stepfunc1Type = NULL; + char *stepfunc2Type = NULL; + char *init1 = NULL; + char *init2 = NULL; + List *pl; + + foreach (pl, parameters) { + DefElem *defel = (DefElem *)lfirst(pl); + + /* + * sfunc1 + */ + if (!strcasecmp(defel->defname, "sfunc1")) { + stepfunc1Name = defGetString(defel); + } else if (!strcasecmp(defel->defname, "basetype")) { + baseType = defGetString(defel); + } else if (!strcasecmp(defel->defname, "stype1")) { + stepfunc1Type = defGetString(defel); + + /* + * sfunc2 + */ + } else if (!strcasecmp(defel->defname, "sfunc2")) { + stepfunc2Name = defGetString(defel); + } else if (!strcasecmp(defel->defname, "stype2")) { + stepfunc2Type = defGetString(defel); + /* + * final + */ + } else if (!strcasecmp(defel->defname, "finalfunc")) { + finalfuncName = defGetString(defel); + /* + * initial conditions + */ + } else if (!strcasecmp(defel->defname, "initcond1")) { + init1 = defGetString(defel); + } else if (!strcasecmp(defel->defname, "initcond2")) { + init2 = defGetString(defel); + } else { + elog(NOTICE, "DefineAggregate: attribute \"%s\" not recognized", + defel->defname); + } + } + + /* + * make sure we have our required definitions + */ + if (baseType==NULL) + elog(WARN, "Define: \"basetype\" unspecified"); + if (stepfunc1Name!=NULL) { + if (stepfunc1Type==NULL) + elog(WARN, "Define: \"stype1\" unspecified"); + } + if (stepfunc2Name!=NULL) { + if (stepfunc2Type==NULL) + elog(WARN, "Define: \"stype2\" unspecified"); + } + + /* + * Most of the argument-checking is done inside of AggregateCreate + */ + AggregateCreate(aggName, /* aggregate name */ + stepfunc1Name, /* first step function name */ + stepfunc2Name, /* second step function name */ + finalfuncName, /* final function name */ + baseType, /* type of object being aggregated */ + stepfunc1Type, /* return type of first function */ + stepfunc2Type, /* return type of second function */ + init1, /* first initial condition */ + init2); /* second initial condition */ + + /* XXX free palloc'd memory */ +} + +/* + * DefineType -- + * Registers a new type. + * + */ +void +DefineType(char *typeName, List *parameters) +{ + int16 internalLength= 0; /* int2 */ + int16 externalLength= 0; /* int2 */ + char *elemName = NULL; + char *inputName = NULL; + char *outputName = NULL; + char *sendName = NULL; + char *receiveName = NULL; + char *defaultValue = NULL; /* Datum */ + bool byValue = false; + char delimiter = DEFAULT_TYPDELIM; + char *shadow_type; + List *pl; + char alignment = 'i'; /* default alignment */ + + /* + * Type names can only be 15 characters long, so that the shadow type + * can be created using the 16th character as necessary. + */ + if (strlen(typeName) >= (NAMEDATALEN - 1)) { + elog(WARN, "DefineType: type names must be %d characters or less", + NAMEDATALEN - 1); + } + + foreach(pl, parameters) { + DefElem *defel = (DefElem*)lfirst(pl); + + if (!strcasecmp(defel->defname, "internallength")) { + internalLength = defGetTypeLength(defel); + }else if (!strcasecmp(defel->defname, "externallength")) { + externalLength = defGetTypeLength(defel); + }else if (!strcasecmp(defel->defname, "input")) { + inputName = defGetString(defel); + }else if (!strcasecmp(defel->defname, "output")) { + outputName = defGetString(defel); + }else if (!strcasecmp(defel->defname, "send")) { + sendName = defGetString(defel); + }else if (!strcasecmp(defel->defname, "delimiter")) { + char *p = defGetString(defel); + delimiter = p[0]; + }else if (!strcasecmp(defel->defname, "receive")) { + receiveName = defGetString(defel); + }else if (!strcasecmp(defel->defname, "element")) { + elemName = defGetString(defel); + }else if (!strcasecmp(defel->defname, "default")) { + defaultValue = defGetString(defel); + }else if (!strcasecmp(defel->defname, "passedbyvalue")) { + byValue = true; + }else if (!strcasecmp(defel->defname, "alignment")) { + char *a = defGetString(defel); + if (!strcasecmp(a, "double")) { + alignment = 'd'; + } else if (!strcasecmp(a, "int")) { + alignment = 'i'; + } else { + elog(WARN, "DefineType: \"%s\" alignment not recognized", + a); + } + }else { + elog(NOTICE, "DefineType: attribute \"%s\" not recognized", + defel->defname); + } + } + + /* + * make sure we have our required definitions + */ + if (inputName==NULL) + elog(WARN, "Define: \"input\" unspecified"); + if (outputName==NULL) + elog(WARN, "Define: \"output\" unspecified"); + + /* ---------------- + * now have TypeCreate do all the real work. + * ---------------- + */ + (void) TypeCreate(typeName, /* type name */ + InvalidOid, /* relation oid (n/a here) */ + internalLength, /* internal size */ + externalLength, /* external size */ + 'b', /* type-type (base type) */ + delimiter, /* array element delimiter */ + inputName, /* input procedure */ + outputName, /* output procedure */ + sendName, /* send procedure */ + receiveName, /* receive procedure */ + elemName, /* element type name */ + defaultValue, /* default type value */ + byValue, /* passed by value */ + alignment); + + /* ---------------- + * When we create a true type (as opposed to a complex type) + * we need to have an shadow array entry for it in pg_type as well. + * ---------------- + */ + shadow_type = makeArrayTypeName(typeName); + + (void) TypeCreate(shadow_type, /* type name */ + InvalidOid, /* relation oid (n/a here) */ + -1, /* internal size */ + -1, /* external size */ + 'b', /* type-type (base type) */ + DEFAULT_TYPDELIM, /* array element delimiter */ + "array_in", /* input procedure */ + "array_out", /* output procedure */ + "array_out", /* send procedure */ + "array_in", /* receive procedure */ + typeName, /* element type name */ + defaultValue, /* default type value */ + false, /* never passed by value */ + alignment); + + pfree(shadow_type); +} + +static char * +defGetString(DefElem *def) +{ + if (nodeTag(def->arg)!=T_String) + elog(WARN, "Define: \"%s\" = what?", def->defname); + return (strVal(def->arg)); +} + +static int +defGetTypeLength(DefElem *def) +{ + if (nodeTag(def->arg)==T_Integer) + return (intVal(def->arg)); + else if (nodeTag(def->arg)==T_String && + !strcasecmp(strVal(def->arg),"variable")) + return -1; /* variable length */ + + elog(WARN, "Define: \"%s\" = what?", def->defname); + return -1; +} diff --git a/src/backend/commands/defrem.h b/src/backend/commands/defrem.h new file mode 100644 index 00000000000..3658dc50085 --- /dev/null +++ b/src/backend/commands/defrem.h @@ -0,0 +1,53 @@ +/*------------------------------------------------------------------------- + * + * defrem.h-- + * POSTGRES define and remove utility definitions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: defrem.h,v 1.1.1.1 1996/07/09 06:21:20 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef DEFREM_H +#define DEFREM_H + +#include "postgres.h" +#include "nodes/pg_list.h" +#include "nodes/primnodes.h" +#include "nodes/parsenodes.h" +#include "tcop/dest.h" + +/* + * prototypes in defind.c + */ +extern void DefineIndex(char *heapRelationName, + char *indexRelationName, + char *accessMethodName, + List *attributeList, + List *parameterList, Expr *predicate, + List *rangetable); +extern void ExtendIndex(char *indexRelationName, + Expr *predicate, + List *rangetable); +extern void RemoveIndex(char *name); + +/* + * prototypes in define.c + */ +extern void DefineFunction(ProcedureStmt *nameargsexe, CommandDest dest); +extern void DefineOperator(char *name, List *parameters); +extern void DefineAggregate(char *name, List *parameters); +extern void DefineType(char *name, List *parameters); + +/* + * prototypes in remove.c + */ +extern void RemoveFunction(char *functionName, int nargs, List *argNameList); +extern void RemoveOperator(char *operatorName, + char *typeName1, char *typeName2); +extern void RemoveType(char *typeName); +extern void RemoveAggregate(char *aggName); + +#endif /* DEFREM_H */ diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c new file mode 100644 index 00000000000..a37f0f9cf4b --- /dev/null +++ b/src/backend/commands/explain.c @@ -0,0 +1,219 @@ +/*------------------------------------------------------------------------- + * + * explain.c-- + * Explain the query execution plan + * + * Copyright (c) 1994-5, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.1.1.1 1996/07/09 06:21:21 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" +#include "parser/catalog_utils.h" +#include "parser/parse_query.h" /* for MakeTimeRange() */ +#include "nodes/plannodes.h" +#include "tcop/tcopprot.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "lib/stringinfo.h" +#include "commands/explain.h" +#include "optimizer/planner.h" +#include "access/xact.h" + +typedef struct ExplainState { + /* options */ + int printCost; /* print cost */ + int printNodes; /* do nodeToString() instead */ + /* other states */ + List *rtable; /* range table */ +} ExplainState; + +static char *Explain_PlanToString(Plan *plan, ExplainState *es); + +/* + * ExplainQuery - + * print out the execution plan for a given query + * + */ +void +ExplainQuery(Query *query, List *options, CommandDest dest) +{ + char *s; + Plan *plan; + ExplainState *es; + int len; + + if (IsAbortedTransactionBlockState()) { + char *tag = "*ABORT STATE*"; + EndCommand(tag, dest); + + elog(NOTICE, "(transaction aborted): %s", + "queries ignored until END"); + + return; + } + + /* plan the queries (XXX we've ignored rewrite!!) */ + plan = planner(query); + + /* pg_plan could have failed */ + if (plan == NULL) + return; + + es = (ExplainState*)malloc(sizeof(ExplainState)); + memset(es, 0, sizeof(ExplainState)); + + /* parse options */ + while (options) { + char *ostr = strVal(lfirst(options)); + if (!strcasecmp(ostr, "cost")) + es->printCost = 1; + else if (!strcasecmp(ostr, "full_plan")) + es->printNodes = 1; + + options = lnext(options); + } + es->rtable = query->rtable; + + if (es->printNodes) { + s = nodeToString(plan); + } else { + s = Explain_PlanToString(plan, es); + } + + /* output the plan */ + len = strlen(s); + elog(NOTICE, "QUERY PLAN:\n\n%.*s", ELOG_MAXLEN-64, s); + len -= ELOG_MAXLEN-64; + while (len > 0) { + s += ELOG_MAXLEN-64; + elog(NOTICE, "%.*s", ELOG_MAXLEN-64, s); + len -= ELOG_MAXLEN-64; + } + free(es); +} + +/***************************************************************************** + * + *****************************************************************************/ + +/* + * explain_outNode - + * converts a Node into ascii string and append it to 'str' + */ +static void +explain_outNode(StringInfo str, Plan *plan, int indent, ExplainState *es) +{ + char *pname; + char buf[1000]; + int i; + + if (plan==NULL) { + appendStringInfo(str, "\n"); + return; + } + + switch(nodeTag(plan)) { + case T_Result: + pname = "Result"; + break; + case T_Append: + pname = "Append"; + break; + case T_NestLoop: + pname = "Nested Loop"; + break; + case T_MergeJoin: + pname = "Merge Join"; + break; + case T_HashJoin: + pname = "Hash Join"; + break; + case T_SeqScan: + pname = "Seq Scan"; + break; + case T_IndexScan: + pname = "Index Scan"; + break; + case T_Temp: + pname = "Temp Scan"; + break; + case T_Sort: + pname = "Sort"; + break; + case T_Agg: + pname = "Aggregate"; + break; + case T_Unique: + pname = "Unique"; + break; + case T_Hash: + pname = "Hash"; + break; + case T_Tee: + pname = "Tee"; + break; + default: + break; + } + + for(i=0; i < indent; i++) + appendStringInfo(str, " "); + + appendStringInfo(str, pname); + switch(nodeTag(plan)) { + case T_SeqScan: + case T_IndexScan: + if (((Scan*)plan)->scanrelid > 0) { + RangeTblEntry *rte = nth(((Scan*)plan)->scanrelid-1, es->rtable); + sprintf(buf, " on %.*s", NAMEDATALEN, rte->refname); + appendStringInfo(str, buf); + } + break; + default: + break; + } + if (es->printCost) { + sprintf(buf, " (cost=%.2f size=%d width=%d)", + plan->cost, plan->plan_size, plan->plan_width); + appendStringInfo(str, buf); + } + appendStringInfo(str, "\n"); + + /* lefttree */ + if (outerPlan(plan)) { + for(i=0; i < indent; i++) + appendStringInfo(str, " "); + appendStringInfo(str, " -> "); + explain_outNode(str, outerPlan(plan), indent+1, es); + } + + /* righttree */ + if (innerPlan(plan)) { + for(i=0; i < indent; i++) + appendStringInfo(str, " "); + appendStringInfo(str, " -> "); + explain_outNode(str, innerPlan(plan), indent+1, es); + } + return; +} + +static char * +Explain_PlanToString(Plan *plan, ExplainState *es) +{ + StringInfo str; + char *s; + + if (plan==NULL) + return ""; + Assert(plan!=NULL); + str = makeStringInfo(); + explain_outNode(str, plan, 0, es); + s = str->data; + pfree(str); + + return s; +} diff --git a/src/backend/commands/explain.h b/src/backend/commands/explain.h new file mode 100644 index 00000000000..e0848bb7711 --- /dev/null +++ b/src/backend/commands/explain.h @@ -0,0 +1,17 @@ +/*------------------------------------------------------------------------- + * + * explain.h-- + * prototypes for explain.c + * + * Copyright (c) 1994-5, Regents of the University of California + * + * $Id: explain.h,v 1.1.1.1 1996/07/09 06:21:21 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef EXPLAIN_H +#define EXPLAIN_H + +extern void ExplainQuery(Query *query, List *options, CommandDest dest); + +#endif /* EXPLAIN_H*/ diff --git a/src/backend/commands/purge.c b/src/backend/commands/purge.c new file mode 100644 index 00000000000..b8b8317ab96 --- /dev/null +++ b/src/backend/commands/purge.c @@ -0,0 +1,168 @@ +/*------------------------------------------------------------------------- + * + * purge.c-- + * the POSTGRES purge command. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/Attic/purge.c,v 1.1.1.1 1996/07/09 06:21:21 scrappy Exp $ + * + * Note: + * XXX There are many instances of int32 instead of ...Time. These + * should be changed once it is decided the signed'ness will be. + * + *------------------------------------------------------------------------- + */ +#include "c.h" + +#include "access/heapam.h" +#include "access/xact.h" +#include "utils/tqual.h" /* for NowTimeQual */ +#include "catalog/catname.h" +#include "catalog/indexing.h" +#include "fmgr.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/nabstime.h" + +#include "catalog/pg_class.h" +#include "commands/purge.h" +#include "utils/builtins.h" /* for isreltime() */ + +static char cmdname[] = "RelationPurge"; + +#define RELATIVE 01 +#define ABSOLUTE 02 + +int32 +RelationPurge(char *relationName, + char *absoluteTimeString, + char *relativeTimeString) +{ + register i; + AbsoluteTime absoluteTime = INVALID_ABSTIME; + RelativeTime relativeTime = INVALID_RELTIME; + bits8 dateTag; + Relation relation; + HeapScanDesc scan; + static ScanKeyData key[1] = { + { 0, Anum_pg_class_relname, F_NAMEEQ } + }; + Buffer buffer; + HeapTuple newTuple, oldTuple; + AbsoluteTime currentTime; + char *values[Natts_pg_class]; + char nulls[Natts_pg_class]; + char replace[Natts_pg_class]; + Relation idescs[Num_pg_class_indices]; + + /* + * XXX for some reason getmyrelids (in inval.c) barfs when + * you heap_replace tuples from these classes. i thought + * setheapoverride would fix it but it didn't. for now, + * just disallow purge on these classes. + */ + if (strcmp(RelationRelationName, relationName) == 0 || + strcmp(AttributeRelationName, relationName) == 0 || + strcmp(AccessMethodRelationName, relationName) == 0 || + strcmp(AccessMethodOperatorRelationName, relationName) == 0) { + elog(WARN, "%s: cannot purge catalog \"%s\"", + cmdname, relationName); + } + + if (PointerIsValid(absoluteTimeString)) { + absoluteTime = (int32) nabstimein(absoluteTimeString); + absoluteTimeString[0] = '\0'; + if (absoluteTime == INVALID_ABSTIME) { + elog(NOTICE, "%s: bad absolute time string \"%s\"", + cmdname, absoluteTimeString); + elog(WARN, "purge not executed"); + } + } + +#ifdef PURGEDEBUG + elog(DEBUG, "%s: absolute time `%s' is %d.", + cmdname, absoluteTimeString, absoluteTime); +#endif /* defined(PURGEDEBUG) */ + + if (PointerIsValid(relativeTimeString)) { + if (isreltime(relativeTimeString, NULL, NULL, NULL) != 1) { + elog(WARN, "%s: bad relative time string \"%s\"", + cmdname, relativeTimeString); + } + relativeTime = reltimein(relativeTimeString); + +#ifdef PURGEDEBUG + elog(DEBUG, "%s: relative time `%s' is %d.", + cmdname, relativeTimeString, relativeTime); +#endif /* defined(PURGEDEBUG) */ + } + + /* + * Find the RELATION relation tuple for the given relation. + */ + relation = heap_openr(RelationRelationName); + key[0].sk_argument = PointerGetDatum(relationName); + fmgr_info(key[0].sk_procedure, &key[0].sk_func, &key[0].sk_nargs); + + scan = heap_beginscan(relation, 0, NowTimeQual, 1, key); + oldTuple = heap_getnext(scan, 0, &buffer); + if (!HeapTupleIsValid(oldTuple)) { + heap_endscan(scan); + heap_close(relation); + elog(WARN, "%s: no such relation: %s", cmdname, relationName); + return(0); + } + + /* + * Dig around in the tuple. + */ + currentTime = GetCurrentTransactionStartTime(); + if (!RelativeTimeIsValid(relativeTime)) { + dateTag = ABSOLUTE; + if (!AbsoluteTimeIsValid(absoluteTime)) + absoluteTime = currentTime; + } else if (!AbsoluteTimeIsValid(absoluteTime)) + dateTag = RELATIVE; + else + dateTag = ABSOLUTE | RELATIVE; + + for (i = 0; i < Natts_pg_class; ++i) { + nulls[i] = heap_attisnull(oldTuple, i+1) ? 'n' : ' '; + values[i] = NULL; + replace[i] = ' '; + } + if (dateTag & ABSOLUTE) { + values[Anum_pg_class_relexpires-1] = + (char *) UInt32GetDatum(absoluteTime); + replace[Anum_pg_class_relexpires-1] = 'r'; + } + if (dateTag & RELATIVE) { + values[Anum_pg_class_relpreserved-1] = + (char *) UInt32GetDatum(relativeTime); + replace[Anum_pg_class_relpreserved-1] = 'r'; + } + + /* + * Change the RELATION relation tuple for the given relation. + */ + newTuple = heap_modifytuple(oldTuple, buffer, relation, (Datum*)values, + nulls, replace); + + /* XXX How do you detect an insertion error?? */ + (void) heap_replace(relation, &newTuple->t_ctid, newTuple); + + /* keep the system catalog indices current */ + CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_class_indices, relation, newTuple); + CatalogCloseIndices(Num_pg_class_indices, idescs); + + pfree(newTuple); + + heap_endscan(scan); + heap_close(relation); + return(1); +} + diff --git a/src/backend/commands/purge.h b/src/backend/commands/purge.h new file mode 100644 index 00000000000..20174182880 --- /dev/null +++ b/src/backend/commands/purge.h @@ -0,0 +1,20 @@ +/*------------------------------------------------------------------------- + * + * purge.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: purge.h,v 1.1.1.1 1996/07/09 06:21:21 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef PURGE_H +#define PURGE_H + +extern int32 RelationPurge(char *relationName, + char *absoluteTimeString, + char *relativeTimeString); + +#endif /* PURGE_H */ diff --git a/src/backend/commands/recipe.c b/src/backend/commands/recipe.c new file mode 100644 index 00000000000..97d0df6d379 --- /dev/null +++ b/src/backend/commands/recipe.c @@ -0,0 +1,1181 @@ +/*------------------------------------------------------------------------- + * + * recipe.c-- + * routines for handling execution of Tioga recipes + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/Attic/recipe.c,v 1.1.1.1 1996/07/09 06:21:21 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + + +#include "include/postgres.h" +#include "nodes/parsenodes.h" +#include "nodes/plannodes.h" +#include "nodes/execnodes.h" +#include "nodes/pg_list.h" +#include "nodes/makefuncs.h" +#include "catalog/pg_type.h" +#include "commands/recipe.h" +#include "libpq/libpq-be.h" +#include "utils/builtins.h" +#include "utils/elog.h" +#include "utils/geo-decls.h" +#include "utils/relcache.h" /* for RelationNameGetRelation*/ +#include "parser/parse_query.h" +#include "rewrite/rewriteHandler.h" +#include "rewrite/rewriteManip.h" +#include "tcop/pquery.h" +#include "tcop/dest.h" +#include "optimizer/planner.h" +#include "executor/executor.h" + +/* from tcop/postgres.c */ +extern CommandDest whereToSendOutput; + +#ifndef TIOGA + +void beginRecipe(RecipeStmt *stmt) { + elog(NOTICE,"You must compile with TIOGA defined in order to use recipes\n"); +} +#else + +#include "tioga/tgRecipe.h" + +#define DEBUG_RECIPE 1 + +/* structure to keep track of the tee node plans */ +typedef struct _teePlanInfo { + char* tpi_relName; + Query* tpi_parsetree; + Plan* tpi_plan; +} TeePlanInfo; + +typedef struct _teeInfo { + int num; + TeePlanInfo *val; +} TeeInfo; + +QueryTreeList *appendQlist(QueryTreeList *q1, QueryTreeList *q2); +void OffsetVarAttno(Node* node, int varno, int offset); + +static void appendTeeQuery(TeeInfo *teeInfo, + QueryTreeList *q, + char* teeNodeName); + +static Plan* replaceTeeScans(Plan* plan, + Query* parsetree, + TeeInfo *teeInfo); +static void replaceSeqScan(Plan* plan, + Plan* parent, + int rt_ind, + Plan* tplan); + +static void tg_rewriteQuery(TgRecipe* r, TgNode *n, + QueryTreeList *q, + QueryTreeList *inputQlist); +static Node *tg_replaceNumberedParam(Node* expression, + int pnum, + int rt_ind, + char *teeRelName); +static Node *tg_rewriteParamsInExpr(Node *expression, + QueryTreeList *inputQlist); +static QueryTreeList *tg_parseSubQuery(TgRecipe* r, + TgNode* n, + TeeInfo* teeInfo); +static QueryTreeList* tg_parseTeeNode(TgRecipe *r, + TgNode *n, + int i, + QueryTreeList *qList, + TeeInfo* teeInfo); + + +/* + The Tioga recipe rewrite algorithm: + + To parse a Tioga recipe, we start from an eye node and go backwards through + its input nodes. To rewrite a Tioga node, we do the following: + + 1) parse the node we're at in the standard way (calling parser() ) + 2) rewrite its input nodes recursively using Tioga rewrite + 3) now, with the rewritten input parse trees and the original parse tree + of the node, we rewrite the the node. + To do the rewrite, we use the target lists, range tables, and + qualifications of the input parse trees +*/ + +/* + * beginRecipe: + * this is the main function to recipe execution + * this function is invoked for EXECUTE RECIPE ... statements + * + * takes in a RecipeStmt structure from the parser + * and returns a list of cursor names + */ + +void +beginRecipe(RecipeStmt* stmt) +{ + TgRecipe* r; + int i; + QueryTreeList *qList; + char portalName[1024]; + + Plan *plan; + TupleDesc attinfo; + QueryDesc *queryDesc; + Query *parsetree; + + int numTees; + TeeInfo* teeInfo; + + /* retrieveRecipe() reads the recipe from the database + and returns a TgRecipe* structure we can work with */ + + r = retrieveRecipe(stmt->recipeName); + + if (r == NULL) return; + + /* find the number of tees in the recipe */ + numTees = r->tees->num; + + if (numTees > 0) { + /* allocate a teePlan structure */ + teeInfo = (TeeInfo*)malloc(sizeof(TeeInfo)); + teeInfo->num = numTees; + teeInfo->val = (TeePlanInfo*)malloc(numTees * sizeof(TeePlanInfo)); + for (i=0;i<numTees;i++) { + teeInfo->val[i].tpi_relName = r->tees->val[i]->nodeName; + teeInfo->val[i].tpi_parsetree = NULL; + teeInfo->val[i].tpi_plan = NULL; + } + } else + teeInfo = NULL; + + /* + * for each viewer in the recipe, go backwards from each viewer input + * and generate a plan. Attach the plan to cursors. + **/ + for (i=0;i<r->eyes->num;i++) { + TgNodePtr e; + + e = r->eyes->val[i]; + if (e->inNodes->num > 1) { + elog(NOTICE, + "beginRecipe: Currently eyes cannot have more than one input"); + } + if (e->inNodes->num == 0) { + /* no input to this eye, skip it */ + continue; + } + +#ifdef DEBUG_RECIPE + elog(NOTICE,"beginRecipe: eyes[%d] = %s\n", i, e->nodeName); +#endif /* DEBUG_RECIPE */ + + qList = tg_parseSubQuery(r,e->inNodes->val[0], teeInfo); + + if (qList == NULL) { + /* eye is directly connected to a tee node */ + /* XXX TODO: handle this case */ + } + + /* now, plan the queries */ + /* should really do everything pg_plan() does, but for now, + we skip the rule rewrite and time qual stuff */ + + /* ---------------------------------------------------------- + * 1) plan the main query, everything from an eye node back to + a Tee + * ---------------------------------------------------------- */ + parsetree = qList->qtrees[0]; + + /* before we plan, we want to see all the changes + we did, during the rewrite phase, such as + creating the tee tables, + setheapoverride() allows us to see the changes */ + setheapoverride(true); + plan = planner(parsetree); + + /* ---------------------------------------------------------- + * 2) plan the tee queries, (subgraphs rooted from a Tee) + by the time the eye is processed, all tees that contribute + to that eye will have been included in the teeInfo list + * ---------------------------------------------------------- */ + if (teeInfo) { + int t; + Plan* tplan; + Tee* newplan; + + for (t=0; t<teeInfo->num;t++) { + if (teeInfo->val[t].tpi_plan == NULL) { + /* plan it in the usual fashion */ + tplan = planner(teeInfo->val[t].tpi_parsetree); + + /* now add a tee node to the root of the plan */ +elog(NOTICE, "adding tee plan node to the root of the %s\n", + teeInfo->val[t].tpi_relName); + newplan = (Tee*)makeNode(Tee); + newplan->plan.targetlist = tplan->targetlist; + newplan->plan.qual = NULL; /* tplan->qual; */ + newplan->plan.lefttree = tplan; + newplan->plan.righttree = NULL; + newplan->leftParent = NULL; + newplan->rightParent = NULL; + /* the range table of the tee is the range table + of the tplan */ + newplan->rtentries = teeInfo->val[t].tpi_parsetree->rtable; + strcpy(newplan->teeTableName, + teeInfo->val[t].tpi_relName); + teeInfo->val[t].tpi_plan = (Plan*)newplan; + } + } + + /* ---------------------------------------------------------- + * 3) replace the tee table scans in the main plan with + actual tee plannodes + * ---------------------------------------------------------- */ + + plan = replaceTeeScans(plan, parsetree, teeInfo); + + } /* if (teeInfo) */ + + setheapoverride(false); + + /* define a portal for this viewer input */ + /* for now, eyes can only have one input */ + sprintf(portalName, "%s%d",e->nodeName,0); + + queryDesc = CreateQueryDesc(parsetree, + plan, + whereToSendOutput); + /* ---------------- + * call ExecStart to prepare the plan for execution + * ---------------- + */ + attinfo = ExecutorStart(queryDesc,NULL); + + ProcessPortal(portalName, + parsetree, + plan, + attinfo, + whereToSendOutput); +elog(NOTICE, "beginRecipe: cursor named %s is now available", portalName); + } + +} + + + +/* + * tg_rewriteQuery - + * r - the recipe being rewritten + * n - the node that we're current at + * q - a QueryTree List containing the parse tree of the node + * inputQlist - the parsetrees of its input nodes, + * the size of inputQlist must be the same as the + * number of input nodes. Some elements in the inpuQlist + * may be null if the inputs to those nodes are unconnected + * + * this is the main routine for rewriting the recipe queries + * the original query tree 'q' is modified + */ + +static void +tg_rewriteQuery(TgRecipe* r, + TgNode *n, + QueryTreeList *q, + QueryTreeList *inputQlist) +{ + Query* orig; + Query* inputQ; + int i; + List *rtable; + List *input_rtable; + int rt_length; + + /* orig is the original parse tree of the node */ + orig = q->qtrees[0]; + + + /*------------------------------------------------------------------- + step 1: + + form a combined range table from all the range tables in the original + query as well as the input nodes + + form a combined qualification from the qual in the original plus + the quals of the input nodes + ------------------------------------------------------------------- + */ + + /* start with the original range table */ + rtable = orig->rtable; + rt_length = length(rtable); + + for (i=0;i<n->inNodes->num;i++) { + if (n->inNodes->val[i] != NULL && + n->inNodes->val[i]->nodeType != TG_TEE_NODE) { + inputQ = inputQlist->qtrees[i]; + input_rtable = inputQ->rtable; + + /* need to offset the var nodes in the qual and targetlist + because they are indexed off the original rtable */ + OffsetVarNodes((Node*)inputQ->qual, rt_length); + OffsetVarNodes((Node*)inputQ->targetList, rt_length); + + /* append the range tables from the children nodes */ + rtable = nconc (rtable, input_rtable); + + /* append the qualifications of the child node into the + original qual list */ + AddQual(orig, inputQ->qual); + } + } + orig->rtable = rtable; + + /* step 2: + rewrite the target list of the original parse tree + if there are any references to params, replace them with + the appropriate target list entry of the children node + */ + if (orig->targetList != NIL) { + List *tl; + TargetEntry *tle; + + foreach (tl, orig->targetList) { + tle = lfirst(tl); + if (tle->resdom != NULL) { + tle->expr = tg_rewriteParamsInExpr(tle->expr, inputQlist); + } + } + } + + /* step 3: + rewrite the qual of the original parse tree + if there are any references to params, replace them with + the appropriate target list entry of the children node + */ + if (orig->qual) { + if (nodeTag(orig->qual) == T_List) { + elog(WARN, "tg_rewriteQuery: Whoa! why is my qual a List???"); + } + orig->qual = tg_rewriteParamsInExpr(orig->qual, inputQlist); + } + + /* at this point, we're done with the rewrite, the querytreelist q + has been modified */ + +} + + +/* tg_replaceNumberedParam: + + this procedure replaces the specified numbered param with a + reference to a range table + + this procedure recursively calls itself + + it returns a (possibly modified) Node*. + +*/ +static Node* +tg_replaceNumberedParam(Node *expression, + int pnum, /* the number of the parameter */ + int rt_ind, /* the range table index */ + char *teeRelName) /* the relname of the tee table */ +{ + TargetEntry *param_tle; + Param* p; + Var *newVar,*oldVar; + + if (expression == NULL) return NULL; + + switch (nodeTag(expression)) { + case T_Param: + { + /* the node is a parameter, + substitute the entry from the target list of the child that + corresponds to the parameter number*/ + p = (Param*)expression; + + /* we only deal with the case of numbered parameters */ + if (p->paramkind == PARAM_NUM && p->paramid == pnum) { + + if (p->param_tlist) { + /* we have a parameter with an attribute like $N.foo + so replace it with a new var node */ + + /* param tlist can only have one entry in them! */ + param_tle = (TargetEntry*)(lfirst(p->param_tlist)); + oldVar = (Var*)param_tle->expr; + oldVar->varno = rt_ind; + oldVar->varnoold = rt_ind; + return (Node*)oldVar; + } else { + /* we have $N without the .foo */ + bool defined; + bool isRel; + /* TODO here, we need to check to see whether the type of the + tee is a complex type (relation) or a simple type */ + /* if it is a simple type, then we need to get the "result" + attribute from the tee relation */ + + isRel = (typeid_get_relid(p->paramtype) != 0); + if (isRel) { + newVar = makeVar(rt_ind, + 0, /* the whole tuple */ + TypeGet(teeRelName,&defined), + rt_ind, + 0); + return (Node*)newVar; + } else + newVar = makeVar(rt_ind, + 1, /* just the first field, which is 'result' */ + TypeGet(teeRelName,&defined), + rt_ind, + 0); + return (Node*)newVar; + + } + } + else { + elog(NOTICE, "tg_replaceNumberedParam: unexpected paramkind value of %d", p->paramkind); + } + } + break; + case T_Expr: + { + /* the node is an expression, we need to recursively + call ourselves until we find parameter nodes */ + List *l; + Expr *expr = (Expr*)expression; + List *newArgs; + + /* we have to make a new args lists because Params + can be replaced by Var nodes in tg_replaceNumberedParam()*/ + newArgs = NIL; + + /* we only care about argument to expressions, + it doesn't matter when the opType is */ + /* recursively rewrite the arguments of this expression */ + foreach (l, expr->args) { + newArgs = lappend(newArgs, + tg_replaceNumberedParam(lfirst(l), + pnum, + rt_ind, + teeRelName)); + } + /* change the arguments of the expression */ + expr->args = newArgs; + } + break; + default: + { + /* ignore other expr types */ + } + } + + return expression; +} + + + + + +/* tg_rewriteParamsInExpr: + + rewrite the params in expressions by using the targetlist entries + from the input parsetrees + + this procedure recursively calls itself + + it returns a (possibly modified) Node*. + +*/ +static Node* +tg_rewriteParamsInExpr(Node *expression, QueryTreeList *inputQlist) +{ + List *tl; + TargetEntry *param_tle, *tle; + Param* p; + int childno; + char *resname; + + if (expression == NULL) return NULL; + + switch (nodeTag(expression)) { + case T_Param: + { + /* the node is a parameter, + substitute the entry from the target list of the child that + corresponds to the parameter number*/ + p = (Param*)expression; + + /* we only deal with the case of numbered parameters */ + if (p->paramkind == PARAM_NUM) { + /* paramid's start from 1*/ + childno = p->paramid - 1; + + if (p->param_tlist) { + /* we have a parameter with an attribute like $N.foo + so match the resname "foo" against the target list + of the (N-1)th inputQlist */ + + /* param tlist can only have one entry in them! */ + param_tle = (TargetEntry*)(lfirst(p->param_tlist)); + resname = param_tle->resdom->resname; + + if (inputQlist->qtrees[childno]) { + foreach (tl, inputQlist->qtrees[childno]->targetList) { + tle = lfirst(tl); + if (strcmp(resname, tle->resdom->resname) == 0) { + return tle->expr; + } + } + } + else { + elog(WARN,"tg_rewriteParamsInExpr:can't substitute for parameter %d when that input is unconnected", p->paramid); + } + + } else { + /* we have $N without the .foo */ + /* use the first resdom in the targetlist of the */ + /* appropriate child query */ + tl = inputQlist->qtrees[childno]->targetList; + tle = lfirst(tl); + return tle->expr; + } + } + else { + elog(NOTICE, "tg_rewriteParamsInExpr: unexpected paramkind value of %d", p->paramkind); + } + } + break; + case T_Expr: + { + /* the node is an expression, we need to recursively + call ourselves until we find parameter nodes */ + List *l; + Expr *expr = (Expr*)expression; + List *newArgs; + + /* we have to make a new args lists because Params + can be replaced by Var nodes in tg_rewriteParamsInExpr()*/ + newArgs = NIL; + + /* we only care about argument to expressions, + it doesn't matter when the opType is */ + /* recursively rewrite the arguments of this expression */ + foreach (l, expr->args) { + newArgs = lappend(newArgs, + tg_rewriteParamsInExpr(lfirst(l), inputQlist)); + } + /* change the arguments of the expression */ + expr->args = newArgs; + } + break; + default: + { + /* ignore other expr types */ + } + } + + return expression; +} + + + +/* + getParamTypes: + given an element, finds its parameter types. + the typev array argument is set to the parameter types. + the parameterCount is returned + + this code is very similar to ProcedureDefine() in pg_proc.c +*/ +static int +getParamTypes (TgElement *elem, Oid typev[]) +{ + /* this code is similar to ProcedureDefine() */ + int16 parameterCount; + bool defined; + Oid toid; + char *t; + int i,j; + + parameterCount = 0; + for (i=0;i<8;i++) { + typev[i] = 0; + } + for (j=0;j<elem->inTypes->num;j++) { + if (parameterCount == 8) { + elog(WARN, + "getParamTypes: Ingredients cannot take > 8 arguments"); + } + t = elem->inTypes->val[j]; + if (strcmp(t,"opaque") == 0) { + elog(WARN, + "getParamTypes: Ingredient functions cannot take type 'opaque'"); + } else { + toid = TypeGet(elem->inTypes->val[j], &defined); + if (!OidIsValid(toid)) { + elog(WARN, "getParamTypes: arg type '%s' is not defined",t); + } + if (!defined) { + elog(NOTICE, "getParamTypes: arg type '%s' is only a shell",t); + } + } + typev[parameterCount++] = toid; + } + + return parameterCount; +} + + +/* + * tg_parseTeeNode + * + * handles the parsing of the tee node + * + * + */ + +static QueryTreeList* +tg_parseTeeNode(TgRecipe *r, + TgNode *n, /* the tee node */ + int i, /* which input this node is to its parent */ + QueryTreeList *qList, + TeeInfo* teeInfo) + +{ + QueryTreeList *q; + char* tt; + int rt_ind; + Query* orig; + + /* the input Node is a tee node, so we need to do the following: + * we need to parse the child of the tee node, + we add that to our query tree list + * we need the name of the tee node table + the tee node table is the table into which the tee node + may materialize results. Call it TT + * we add a range table to our existing query with TT in it + * we need to replace the parameter $i with TT + (otherwise the optimizer won't know to use the table + on expression containining $i) + After that rewrite, the optimizer will generate + sequential scans of TT + + Later, in the glue phase, we replace all instances of TT + sequential scans with the actual Tee node + */ + q = tg_parseSubQuery(r,n, teeInfo); + + /* tt is the name of the tee node table */ + tt = n->nodeName; + + if (q) + appendTeeQuery(teeInfo,q,tt); + + orig = qList->qtrees[0]; + rt_ind = RangeTablePosn(orig->rtable,tt); + /* check to see that this table is not part of + the range table already. This usually only + happens if multiple inputs are connected to the + same Tee. */ + if (rt_ind == 0) { + orig->rtable = lappend(orig->rtable, + makeRangeTableEntry(tt, + FALSE, + NULL, + tt)); + rt_ind = length(orig->rtable); + } + + orig->qual = tg_replaceNumberedParam(orig->qual, + i+1, /* params start at 1*/ + rt_ind, + tt); + return qList; +} + + +/* + * tg_parseSubQuery: + * go backwards from a node and parse the query + * + * the result parse tree is passed back + * + * could return NULL if trying to parse a teeNode + * that's already been processed by another parent + * + */ + +static QueryTreeList* +tg_parseSubQuery(TgRecipe* r, TgNode* n, TeeInfo* teeInfo) +{ + TgElement *elem; + char* funcName; + Oid typev[8]; /* eight arguments maximum */ + int i; + int parameterCount; + + QueryTreeList *qList; /* the parse tree of the nodeElement */ + QueryTreeList *inputQlist; /* the list of parse trees for the + inputs to this node */ + QueryTreeList *q; + Oid relid; + TgNode* child; + Relation rel; + unsigned int len; + TupleDesc tupdesc; + + qList = NULL; + + if (n->nodeType == TG_INGRED_NODE) { + /* parse each ingredient node in turn */ + + elem = n->nodeElem; + switch (elem->srcLang) { + case TG_SQL: + { + /* for SQL ingredients, the SQL query is contained in the + 'src' field */ + +#ifdef DEBUG_RECIPE +elog(NOTICE,"calling parser with %s",elem->src); +#endif /* DEBUG_RECIPE */ + + parameterCount = getParamTypes(elem,typev); + + qList = parser(elem->src,typev,parameterCount); + + if (qList->len > 1) { + elog(NOTICE, + "tg_parseSubQuery: parser produced > 1 query tree"); + } + } + break; + case TG_C: + { + /* C ingredients are registered functions in postgres */ + /* we create a new query string by using the function name + (found in the 'src' field) and adding parameters to it + so if the function was FOOBAR and took in two arguments, + we would create a string + select FOOBAR($1,$2) + */ + char newquery[1000]; + + funcName = elem->src; + parameterCount = getParamTypes(elem,typev); + + if (parameterCount > 0) { + int i; + sprintf(newquery,"select %s($1",funcName); + for (i=1;i<parameterCount;i++) { + sprintf(newquery,"%s,$%d",newquery,i); + } + sprintf(newquery,"%s)",newquery); + } else + sprintf(newquery,"select %s()",funcName); + +#ifdef DEBUG_RECIPE +elog(NOTICE,"calling parser with %s", newquery); +#endif /* DEBUG_RECIPE */ + + qList = parser(newquery,typev,parameterCount); + if (qList->len > 1) { + elog(NOTICE, + "tg_parseSubQuery: parser produced > 1 query tree"); + } + } + break; + case TG_RECIPE_GRAPH: + elog(NOTICE,"tg_parseSubQuery: can't parse recipe graph ingredients yet!"); + break; + case TG_COMPILED: + elog(NOTICE,"tg_parseSubQuery: can't parse compiled ingredients yet!"); + break; + default: + elog(NOTICE,"tg_parseSubQuery: unknown srcLang: %d",elem->srcLang); + } + + /* parse each of the subrecipes that are input to this node*/ + + if (n->inNodes->num > 0) { + inputQlist = malloc(sizeof(QueryTreeList)); + inputQlist->len = n->inNodes->num + 1 ; + inputQlist->qtrees = (Query**)malloc(inputQlist->len * sizeof(Query*)); + for (i=0;i<n->inNodes->num;i++) { + + inputQlist->qtrees[i] = NULL; + if (n->inNodes->val[i]) { + if (n->inNodes->val[i]->nodeType == TG_TEE_NODE) { + qList = tg_parseTeeNode(r,n->inNodes->val[i], + i,qList,teeInfo); + } + else + { /* input node is not a Tee */ + q = tg_parseSubQuery(r,n->inNodes->val[i], + teeInfo); + Assert (q->len == 1); + inputQlist->qtrees[i] = q->qtrees[0]; + } + } + } + + /* now, we have all the query trees from our input nodes */ + /* transform the original parse tree appropriately */ + tg_rewriteQuery(r,n,qList,inputQlist); + } + } + else if (n->nodeType == TG_EYE_NODE) { + /* if we hit an eye, we need to stop and make what we have + into a subrecipe query block*/ + elog(NOTICE,"tg_parseSubQuery: can't handle eye nodes yet"); + } + else if (n->nodeType == TG_TEE_NODE) { + /* if we hit a tee, check to see if the parsing has been done + for this tee already by the other parent */ + + rel = RelationNameGetRelation(n->nodeName); + if (RelationIsValid(rel)) { + /* this tee has already been visited, + no need to do any further processing */ + return NULL; + } else { + /* we need to process the child of the tee first, */ + child = n->inNodes->val[0]; + + if (child->nodeType == TG_TEE_NODE) { + /* nested Tee nodes */ + qList = tg_parseTeeNode(r,child,0,qList,teeInfo); + return qList; + } + + Assert (child != NULL); + + /* parse the input node */ + q = tg_parseSubQuery(r,child, teeInfo); + Assert (q->len == 1); + + /* add the parsed query to the main list of queries */ + qList = appendQlist(qList,q); + + /* need to create the tee table here */ + /* the tee table created is used both for materializing the values + at the tee node, and for parsing and optimization. + The optimization needs to have a real table before it will + consider scans on it */ + + /* first, find the type of the tuples being produced by the + tee. The type is the same as the output type of + the child node. + + NOTE: we are assuming that the child node only has a single + output here! */ + getParamTypes(child->nodeElem,typev); + + /* the output type is either a complex type, + (and is thus a relation) or is a simple type */ + + rel = RelationNameGetRelation(child->nodeElem->outTypes->val[0]); + + if (RelationIsValid(rel)) { + /* for complex types, create new relation with the same + tuple descriptor as the output table type*/ + len = length(q->qtrees[0]->targetList); + tupdesc = rel->rd_att; + + relid = heap_create(child->nodeElem->outTypes->val[0], + NULL, /* XXX */ + 'n', + DEFAULT_SMGR, + tupdesc); + } + else { + /* we have to create a relation with one attribute of + the simple base type. That attribute will have + an attr name of "result" */ + /*NOTE: ignore array types for the time being */ + + len = 1; + tupdesc = CreateTemplateTupleDesc(len); + + if ( !TupleDescInitEntry(tupdesc,1, + "result", + NULL, + 0, false)) { + elog(NOTICE,"tg_parseSubQuery: unexpected result from TupleDescInitEntry"); + } else { + relid = heap_create(child->nodeElem->outTypes->val[0], + NULL, /* XXX */ + 'n', + DEFAULT_SMGR, + tupdesc); + } + } + } + } + else if (n->nodeType == TG_RECIPE_NODE) { + elog(NOTICE,"tg_parseSubQuery: can't handle embedded recipes yet!"); + } else + elog (NOTICE, "unknown nodeType: %d", n->nodeType); + + return qList; +} + +/* + * OffsetVarAttno - + * recursively find all the var nodes with the specified varno + * and offset their varattno with the offset + * + * code is similar to OffsetVarNodes in rewriteManip.c + */ + +void +OffsetVarAttno(Node* node, int varno, int offset) +{ + if (node == NULL) return; + switch (nodeTag(node)) { + case T_TargetEntry: + { + TargetEntry *tle = (TargetEntry *)node; + OffsetVarAttno(tle->expr, varno, offset); + } + break; + case T_Expr: + { + Expr *expr = (Expr*)node; + OffsetVarAttno((Node*)expr->args, varno, offset); + } + break; + case T_Var: + { + Var *var = (Var*)node; + if (var->varno == varno) + var->varattno += offset; + } + break; + case T_List: + { + List *l; + + foreach(l, (List*)node) { + OffsetVarAttno(lfirst(l), varno, offset); + } + } + break; + default: + /* ignore the others */ + break; + } +} + +/* + * appendQlist + * add the contents of a QueryTreeList q2 to the end of the QueryTreeList + * q1 + * + * returns a new querytree list + */ + +QueryTreeList* +appendQlist(QueryTreeList *q1, QueryTreeList *q2) +{ + QueryTreeList* newq; + int i,j; + int newlen; + + if (q1 == NULL) + return q2; + + if (q2 == NULL) + return q1; + + newlen = q1->len + q2->len; + newq = (QueryTreeList*)malloc(sizeof(QueryTreeList)); + newq->len = newlen; + newq->qtrees = (Query**)malloc(newlen * sizeof(Query*)); + for (i=0;i<q1->len;i++) + newq->qtrees[i] = q1->qtrees[i]; + for (j=0;j<q2->len;j++) { + newq->qtrees[i + j] = q2->qtrees[j]; + } + return newq; +} + +/* + * appendTeeQuery + * + * modify the query field of the teeInfo list of the particular tee node + */ +static void +appendTeeQuery(TeeInfo *teeInfo, QueryTreeList *q, char* teeNodeName) +{ + int i; + + Assert(teeInfo); + + for (i=0;i<teeInfo->num;i++) { + if ( strcmp(teeInfo->val[i].tpi_relName, teeNodeName) == 0) { + + Assert(q->len == 1); + teeInfo->val[i].tpi_parsetree = q->qtrees[0]; + return; + } + } + elog(NOTICE, "appendTeeQuery: teeNodeName '%s' not found in teeInfo"); +} + + + +/* + * replaceSeqScan + * replaces sequential scans of a specified relation with the tee plan + * the relation is specified by its index in the range table, rt_ind + * + * returns the modified plan + * the offset_attno is the offset that needs to be added to the parent's + * qual or targetlist because the child plan has been replaced with a tee node + */ +static void +replaceSeqScan(Plan* plan, Plan* parent, + int rt_ind, Plan* tplan) +{ + Scan* snode; + Tee* teePlan; + Result* newPlan; + + if (plan == NULL) { + return; + } + + if (plan->type == T_SeqScan) { + snode = (Scan*)plan; + if (snode->scanrelid == rt_ind) { + /* found the sequential scan that should be replaced + with the tplan. */ + /* we replace the plan, but we also need to modify its parent*/ + + /* replace the sequential scan with a Result node + the reason we use a result node is so that we get the proper + projection behavior. The Result node is simply (ab)used as + a projection node */ + + newPlan = makeNode(Result); + newPlan->plan.cost = 0.0; + newPlan->plan.state = (EState*)NULL; + newPlan->plan.targetlist = plan->targetlist; + newPlan->plan.lefttree = tplan; + newPlan->plan.righttree = NULL; + newPlan->resconstantqual = NULL; + newPlan->resstate = NULL; + + /* change all the varno's to 1*/ + ChangeVarNodes((Node*)newPlan->plan.targetlist, + snode->scanrelid, 1); + + if (parent) { + teePlan = (Tee*)tplan; + + if (parent->lefttree == plan) + parent->lefttree = (Plan*)newPlan; + else + parent->righttree = (Plan*)newPlan; + + + if (teePlan->leftParent == NULL) + teePlan->leftParent = (Plan*)newPlan; + else + teePlan->rightParent = (Plan*)newPlan; + +/* comment for now to test out executor-stuff + if (parent->state) { + ExecInitNode((Plan*)newPlan, parent->state, (Plan*)newPlan); + } +*/ + } + } + + } else { + if (plan->lefttree) { + replaceSeqScan(plan->lefttree, plan, rt_ind, tplan); + } + if (plan->righttree) { + replaceSeqScan(plan->righttree, plan, rt_ind, tplan); + } + } +} + +/* + * replaceTeeScans + * places the sequential scans of the Tee table with + * a connection to the actual tee plan node + */ +static Plan* +replaceTeeScans(Plan* plan, Query* parsetree, TeeInfo *teeInfo) +{ + + int i; + List* rtable; + RangeTblEntry *rte; + char prefix[5]; + int rt_ind; + Plan* tplan; + + rtable = parsetree->rtable; + if (rtable == NULL) + return plan; + + /* look through the range table for the tee relation entry, + that will give use the varno we need to detect which + sequential scans need to be replaced with tee nodes*/ + + rt_ind = 0; + while (rtable != NIL) { + rte = lfirst(rtable); + rtable = lnext(rtable); + rt_ind++; /* range table references in varno fields start w/ 1 */ + + /* look for the "tee_" prefix in the refname, + also check to see that the relname and the refname are the same + this should eliminate any user-specified table and leave + us with the tee table entries only*/ + if ((strlen(rte->refname) < 4) || + (strcmp (rte->relname, rte->refname) != 0)) + continue; + strncpy(prefix,rte->refname,4); + prefix[4] = '\0'; + if (strcmp(prefix,"tee_") == 0) { + /* okay, we found a tee node entry in the range table */ + + /* find the appropriate plan in the teeInfo list */ + tplan = NULL; + for (i=0;i<teeInfo->num;i++) { + if (strcmp(teeInfo->val[i].tpi_relName, + rte->refname) == 0) { + tplan = teeInfo->val[i].tpi_plan; + } + } + if (tplan == NULL) { + elog(NOTICE, "replaceTeeScans didn't find the corresponding tee plan"); } + + /* replace the sequential scan node with that var number + with the tee plan node */ + replaceSeqScan(plan, NULL, rt_ind, tplan); + } + } + + return plan; +} + + + +#endif /* TIOGA */ diff --git a/src/backend/commands/recipe.h b/src/backend/commands/recipe.h new file mode 100644 index 00000000000..62fcc314a34 --- /dev/null +++ b/src/backend/commands/recipe.h @@ -0,0 +1,17 @@ +/*------------------------------------------------------------------------- + * + * recipe.h-- + * recipe handling routines + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: recipe.h,v 1.1.1.1 1996/07/09 06:21:21 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef RECIPE_H +#define RECIPE_H + +extern void beginRecipe(RecipeStmt* stmt); + +#endif /* RECIPE_H */ diff --git a/src/backend/commands/remove.c b/src/backend/commands/remove.c new file mode 100644 index 00000000000..95830c6cc08 --- /dev/null +++ b/src/backend/commands/remove.c @@ -0,0 +1,435 @@ +/*------------------------------------------------------------------------- + * + * remove.c-- + * POSTGRES remove (function | type | operator ) utilty code. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/Attic/remove.c,v 1.1.1.1 1996/07/09 06:21:22 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <string.h> +#include "c.h" + +#include "access/attnum.h" +#include "access/heapam.h" +#include "access/htup.h" +#include "access/skey.h" +#include "utils/builtins.h" +#include "utils/tqual.h" /* for NowTimeQual */ +#include "catalog/catname.h" +#include "commands/defrem.h" +#include "utils/elog.h" + +#include "miscadmin.h" + +#include "catalog/pg_aggregate.h" +#include "catalog/pg_language.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "utils/syscache.h" +#include "parser/catalog_utils.h" +#include "storage/bufmgr.h" +#include "fmgr.h" + +/* + * RemoveOperator -- + * Deletes an operator. + * + * Exceptions: + * BadArg if name is invalid. + * BadArg if type1 is invalid. + * "WARN" if operator nonexistant. + * ... + */ +void +RemoveOperator(char *operatorName, /* operator name */ + char *typeName1, /* first type name */ + char *typeName2) /* optional second type name */ +{ + Relation relation; + HeapScanDesc scan; + HeapTuple tup; + Oid typeId1 = InvalidOid; + Oid typeId2 = InvalidOid; + bool defined; + ItemPointerData itemPointerData; + Buffer buffer; + ScanKeyData operatorKey[3]; + char *userName; + + if (typeName1) { + typeId1 = TypeGet(typeName1, &defined); + if (!OidIsValid(typeId1)) { + elog(WARN, "RemoveOperator: type '%s' does not exist", typeName1); + return; + } + } + + if (typeName2) { + typeId2 = TypeGet(typeName2, &defined); + if (!OidIsValid(typeId2)) { + elog(WARN, "RemoveOperator: type '%s' does not exist", typeName2); + return; + } + } + + ScanKeyEntryInitialize(&operatorKey[0], 0x0, + Anum_pg_operator_oprname, + NameEqualRegProcedure, + PointerGetDatum(operatorName)); + + ScanKeyEntryInitialize(&operatorKey[1], 0x0, + Anum_pg_operator_oprleft, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(typeId1)); + + ScanKeyEntryInitialize(&operatorKey[2], 0x0, + Anum_pg_operator_oprright, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(typeId2)); + + relation = heap_openr(OperatorRelationName); + scan = heap_beginscan(relation, 0, NowTimeQual, 3, operatorKey); + tup = heap_getnext(scan, 0, &buffer); + if (HeapTupleIsValid(tup)) { +#ifndef NO_SECURITY + userName = GetPgUserName(); + if (!pg_ownercheck(userName, + (char *) ObjectIdGetDatum(tup->t_oid), + OPROID)) + elog(WARN, "RemoveOperator: operator '%s': permission denied", + operatorName); +#endif + ItemPointerCopy(&tup->t_ctid, &itemPointerData); + heap_delete(relation, &itemPointerData); + } else { + if (OidIsValid(typeId1) && OidIsValid(typeId2)) { + elog(WARN, "RemoveOperator: binary operator '%s' taking '%s' and '%s' does not exist", + operatorName, + typeName1, + typeName2); + } else if (OidIsValid(typeId1)) { + elog(WARN, "RemoveOperator: right unary operator '%s' taking '%s' does not exist", + operatorName, + typeName1); + } else { + elog(WARN, "RemoveOperator: left unary operator '%s' taking '%s' does not exist", + operatorName, + typeName2); + } + } + heap_endscan(scan); + heap_close(relation); +} + +#ifdef NOTYET +/* + * this stuff is to support removing all reference to a type + * don't use it - pma 2/1/94 + */ +/* + * SingleOpOperatorRemove + * Removes all operators that have operands or a result of type 'typeOid'. + */ +static void +SingleOpOperatorRemove(Oid typeOid) +{ + Relation rdesc; + ScanKeyData key[3]; + HeapScanDesc sdesc; + HeapTuple tup; + ItemPointerData itemPointerData; + Buffer buffer; + static attnums[3] = { 7, 8, 9 }; /* left, right, return */ + register i; + + ScanKeyEntryInitialize(&key[0], + 0, 0, ObjectIdEqualRegProcedure, (Datum)typeOid); + rdesc = heap_openr(OperatorRelationName); + for (i = 0; i < 3; ++i) { + key[0].sk_attno = attnums[i]; + sdesc = heap_beginscan(rdesc, 0, NowTimeQual, 1, key); + while (PointerIsValid(tup = heap_getnext(sdesc, 0, &buffer))) { + ItemPointerCopy(&tup->t_ctid, &itemPointerData); + /* XXX LOCK not being passed */ + heap_delete(rdesc, &itemPointerData); + } + heap_endscan(sdesc); + } + heap_close(rdesc); +} + +/* + * AttributeAndRelationRemove + * Removes all entries in the attribute and relation relations + * that contain entries of type 'typeOid'. + * Currently nothing calls this code, it is untested. + */ +static void +AttributeAndRelationRemove(Oid typeOid) +{ + struct oidlist { + Oid reloid; + struct oidlist *next; + }; + struct oidlist *oidptr, *optr; + Relation rdesc; + ScanKeyData key[1]; + HeapScanDesc sdesc; + HeapTuple tup; + ItemPointerData itemPointerData; + Buffer buffer; + + /* + * Get the oid's of the relations to be removed by scanning the + * entire attribute relation. + * We don't need to remove the attributes here, + * because amdestroy will remove all attributes of the relation. + * XXX should check for duplicate relations + */ + + ScanKeyEntryInitialize(&key[0], + 0, 3, ObjectIdEqualRegProcedure, (Datum)typeOid); + + oidptr = (struct oidlist *) palloc(sizeof(*oidptr)); + oidptr->next = NULL; + optr = oidptr; + rdesc = heap_openr(AttributeRelationName); + sdesc = heap_beginscan(rdesc, 0, NowTimeQual, 1, key); + while (PointerIsValid(tup = heap_getnext(sdesc, 0, &buffer))) { + ItemPointerCopy(&tup->t_ctid, &itemPointerData); + optr->reloid = ((AttributeTupleForm)GETSTRUCT(tup))->attrelid; + optr->next = (struct oidlist *) palloc(sizeof(*oidptr)); + optr = optr->next; + } + optr->next = NULL; + heap_endscan(sdesc); + heap_close(rdesc); + + + ScanKeyEntryInitialize(&key[0], 0, + ObjectIdAttributeNumber, + ObjectIdEqualRegProcedure, (Datum)0); + optr = oidptr; + rdesc = heap_openr(RelationRelationName); + while (PointerIsValid((char *) optr->next)) { + key[0].sk_argument = (Datum) (optr++)->reloid; + sdesc = heap_beginscan(rdesc, 0, NowTimeQual, 1, key); + tup = heap_getnext(sdesc, 0, &buffer); + if (PointerIsValid(tup)) { + char *name; + + name = (((Form_pg_class)GETSTRUCT(tup))->relname).data; + heap_destroy(name); + } + } + heap_endscan(sdesc); + heap_close(rdesc); +} +#endif /* NOTYET */ + +/* + * TypeRemove + * Removes the type 'typeName' and all attributes and relations that + * use it. + */ +void +RemoveType(char *typeName) /* type name to be removed */ +{ + Relation relation; + HeapScanDesc scan; + HeapTuple tup; + Oid typeOid; + ItemPointerData itemPointerData; + static ScanKeyData typeKey[1] = { + { 0, Anum_pg_type_typname, NameEqualRegProcedure } + }; + char *shadow_type; + char *userName; + +#ifndef NO_SECURITY + userName = GetPgUserName(); + if (!pg_ownercheck(userName, typeName, TYPNAME)) + elog(WARN, "RemoveType: type '%s': permission denied", + typeName); +#endif + + relation = heap_openr(TypeRelationName); + fmgr_info(typeKey[0].sk_procedure, &typeKey[0].sk_func, + &typeKey[0].sk_nargs); + + /* Delete the primary type */ + + typeKey[0].sk_argument = PointerGetDatum(typeName); + + scan = heap_beginscan(relation, 0, NowTimeQual, 1, typeKey); + tup = heap_getnext(scan, 0, (Buffer *) 0); + if (!HeapTupleIsValid(tup)) { + heap_endscan(scan); + heap_close(relation); + elog(WARN, "RemoveType: type '%s' does not exist", + typeName); + } + typeOid = tup->t_oid; + ItemPointerCopy(&tup->t_ctid, &itemPointerData); + heap_delete(relation, &itemPointerData); + heap_endscan(scan); + + /* Now, Delete the "array of" that type */ + shadow_type = makeArrayTypeName(typeName); + typeKey[0].sk_argument = NameGetDatum(shadow_type); + + scan = heap_beginscan(relation, 0, NowTimeQual, + 1, (ScanKey) typeKey); + tup = heap_getnext(scan, 0, (Buffer *) 0); + + if (!HeapTupleIsValid(tup)) + { + elog(WARN, "RemoveType: type '%s': array stub not found", + typeName); + } + typeOid = tup->t_oid; + ItemPointerCopy(&tup->t_ctid, &itemPointerData); + heap_delete(relation, &itemPointerData); + heap_endscan(scan); + + heap_close(relation); +} + +/* + * RemoveFunction -- + * Deletes a function. + * + * Exceptions: + * BadArg if name is invalid. + * "WARN" if function nonexistant. + * ... + */ +void +RemoveFunction(char *functionName, /* function name to be removed */ + int nargs, + List *argNameList /* list of TypeNames */) +{ + Relation relation; + HeapScanDesc scan; + HeapTuple tup; + Buffer buffer = InvalidBuffer; + bool bufferUsed = FALSE; + Oid argList[8]; + Form_pg_proc the_proc; + ItemPointerData itemPointerData; + static ScanKeyData key[3] = { + { 0, Anum_pg_proc_proname, NameEqualRegProcedure } + }; + char *userName; + char *typename; + int i; + + memset(argList, 0, 8 * sizeof(Oid)); + for (i=0; i<nargs; i++) { +/* typename = ((TypeName*)(lfirst(argNameList)))->name; */ + typename = strVal(lfirst(argNameList)); + argNameList = lnext(argNameList); + + if (strcmp(typename, "opaque") == 0) + argList[i] = 0; + else { + tup = SearchSysCacheTuple(TYPNAME, PointerGetDatum(typename), + 0,0,0); + + if (!HeapTupleIsValid(tup)) { + elog(WARN, "RemoveFunction: type '%s' not found",typename); + } + argList[i] = tup->t_oid; + } + } + + tup = SearchSysCacheTuple(PRONAME, PointerGetDatum(functionName), + Int32GetDatum(nargs), + PointerGetDatum(argList),0); + if (!HeapTupleIsValid(tup)) + func_error("RemoveFunction", functionName, nargs, (int*)argList); + +#ifndef NO_SECURITY + userName = GetPgUserName(); + if (!pg_func_ownercheck(userName, functionName, nargs, argList)) { + elog(WARN, "RemoveFunction: function '%s': permission denied", + functionName); + } +#endif + + key[0].sk_argument = PointerGetDatum(functionName); + + fmgr_info(key[0].sk_procedure, &key[0].sk_func, &key[0].sk_nargs); + + relation = heap_openr(ProcedureRelationName); + scan = heap_beginscan(relation, 0, NowTimeQual, 1, key); + + do { /* hope this is ok because it's indexed */ + if (bufferUsed) { + ReleaseBuffer(buffer); + bufferUsed = FALSE; + } + tup = heap_getnext(scan, 0, (Buffer *) &buffer); + if (!HeapTupleIsValid(tup)) + break; + bufferUsed = TRUE; + the_proc = (Form_pg_proc) GETSTRUCT(tup); + } while ( (namestrcmp(&(the_proc->proname), functionName) == 0) && + (the_proc->pronargs != nargs || + !oid8eq(&(the_proc->proargtypes[0]), &argList[0]))); + + + if (!HeapTupleIsValid(tup) || namestrcmp(&(the_proc->proname), + functionName) != 0) + { + heap_endscan(scan); + heap_close(relation); + func_error("RemoveFunction", functionName,nargs, (int*)argList); + } + + /* ok, function has been found */ + + if (the_proc->prolang == INTERNALlanguageId) + elog(WARN, "RemoveFunction: function \"%-.*s\" is built-in", + NAMEDATALEN, functionName); + + ItemPointerCopy(&tup->t_ctid, &itemPointerData); + heap_delete(relation, &itemPointerData); + heap_endscan(scan); + heap_close(relation); +} + +void +RemoveAggregate(char *aggName) +{ + Relation relation; + HeapScanDesc scan; + HeapTuple tup; + ItemPointerData itemPointerData; + static ScanKeyData key[3] = { + { 0, Anum_pg_aggregate_aggname, NameEqualRegProcedure } + }; + + key[0].sk_argument = PointerGetDatum(aggName); + + fmgr_info(key[0].sk_procedure, &key[0].sk_func, &key[0].sk_nargs); + relation = heap_openr(AggregateRelationName); + scan = heap_beginscan(relation, 0, NowTimeQual, 1, key); + tup = heap_getnext(scan, 0, (Buffer *) 0); + if (!HeapTupleIsValid(tup)) { + heap_endscan(scan); + heap_close(relation); + elog(WARN, "RemoveAggregate: aggregate '%s' does not exist", + aggName); + } + ItemPointerCopy(&tup->t_ctid, &itemPointerData); + heap_delete(relation, &itemPointerData); + heap_endscan(scan); + heap_close(relation); +} diff --git a/src/backend/commands/rename.c b/src/backend/commands/rename.c new file mode 100644 index 00000000000..83dc8944eac --- /dev/null +++ b/src/backend/commands/rename.c @@ -0,0 +1,275 @@ +/*------------------------------------------------------------------------- + * + * rename.c-- + * renameatt() and renamerel() reside here. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/Attic/rename.c,v 1.1.1.1 1996/07/09 06:21:22 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <string.h> + +#include "postgres.h" + +#include "nodes/pg_list.h" +#include "access/attnum.h" +#include "access/heapam.h" +#include "access/htup.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "utils/builtins.h" +#include "utils/tqual.h" + +#include "catalog/catname.h" +#include "utils/syscache.h" +#include "catalog/indexing.h" +#include "catalog/catalog.h" + +#include "commands/copy.h" + +#include "executor/execdefs.h" /* for EXEC_{FOR,BACK,FDEBUG,BDEBUG} */ + +#include "storage/buf.h" +#include "storage/itemptr.h" + +#include "miscadmin.h" +#include "utils/portal.h" +#include "tcop/dest.h" +#include "commands/command.h" + +#include "utils/excid.h" +#include "utils/elog.h" +#include "utils/mcxt.h" +#include "utils/palloc.h" +#include "utils/rel.h" + +#include "catalog/pg_attribute.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_class.h" + +#include "optimizer/internal.h" +#include "optimizer/prep.h" /* for find_all_inheritors */ + +#ifndef NO_SECURITY +#include "utils/acl.h" +#include "utils/syscache.h" +#endif /* !NO_SECURITY */ + +/* + * renameatt - changes the name of a attribute in a relation + * + * Attname attribute is changed in attribute catalog. + * No record of the previous attname is kept (correct?). + * + * get proper reldesc from relation catalog (if not arg) + * scan attribute catalog + * for name conflict (within rel) + * for original attribute (if not arg) + * modify attname in attribute tuple + * insert modified attribute in attribute catalog + * delete original attribute from attribute catalog + * + * XXX Renaming an indexed attribute must (eventually) also change + * the attribute name in the associated indexes. + */ +void +renameatt(char *relname, + char *oldattname, + char *newattname, + char *userName, + int recurse) +{ + Relation relrdesc, attrdesc; + HeapTuple reltup, oldatttup, newatttup; + ItemPointerData oldTID; + Relation idescs[Num_pg_attr_indices]; + + /* + * permissions checking. this would normally be done in utility.c, + * but this particular routine is recursive. + * + * normally, only the owner of a class can change its schema. + */ + if (IsSystemRelationName(relname)) + elog(WARN, "renameatt: class \"%-.*s\" is a system catalog", + NAMEDATALEN, relname); +#ifndef NO_SECURITY + if (!IsBootstrapProcessingMode() && + !pg_ownercheck(userName, relname, RELNAME)) + elog(WARN, "renameatt: you do not own class \"%-.*s\"", + NAMEDATALEN, relname); +#endif + + /* + * if the 'recurse' flag is set then we are supposed to rename this + * attribute in all classes that inherit from 'relname' (as well as + * in 'relname'). + * + * any permissions or problems with duplicate attributes will cause + * the whole transaction to abort, which is what we want -- all or + * nothing. + */ + if (recurse) { + Oid myrelid, childrelid; + List *child, *children; + + relrdesc = heap_openr(relname); + if (!RelationIsValid(relrdesc)) { + elog(WARN, "renameatt: unknown relation: \"%-.*s\"", + NAMEDATALEN, relname); + } + myrelid = relrdesc->rd_id; + heap_close(relrdesc); + + /* this routine is actually in the planner */ + children = find_all_inheritors(lconsi(myrelid, NIL), NIL); + + + /* + * find_all_inheritors does the recursive search of the + * inheritance hierarchy, so all we have to do is process + * all of the relids in the list that it returns. + */ + foreach (child, children) { + char *childname; + + childrelid = lfirsti(child); + if (childrelid == myrelid) + continue; + relrdesc = heap_open(childrelid); + if (!RelationIsValid(relrdesc)) { + elog(WARN, "renameatt: can't find catalog entry for inheriting class with oid %d", + childrelid); + } + childname = (relrdesc->rd_rel->relname).data; + heap_close(relrdesc); + renameatt(childname, oldattname, newattname, + userName, 0); /* no more recursion! */ + } + } + + relrdesc = heap_openr(RelationRelationName); + reltup = ClassNameIndexScan(relrdesc, relname); + if (!PointerIsValid(reltup)) { + heap_close(relrdesc); + elog(WARN, "renameatt: relation \"%-.*s\" nonexistent", + NAMEDATALEN, relname); + return; + } + heap_close(relrdesc); + + attrdesc = heap_openr(AttributeRelationName); + oldatttup = AttributeNameIndexScan(attrdesc, reltup->t_oid, oldattname); + if (!PointerIsValid(oldatttup)) { + heap_close(attrdesc); + elog(WARN, "renameatt: attribute \"%-.*s\" nonexistent", + NAMEDATALEN, oldattname); + } + if (((AttributeTupleForm ) GETSTRUCT(oldatttup))->attnum < 0) { + elog(WARN, "renameatt: system attribute \"%-.*s\" not renamed", + NAMEDATALEN, oldattname); + } + + newatttup = AttributeNameIndexScan(attrdesc, reltup->t_oid, newattname); + if (PointerIsValid(newatttup)) { + pfree(oldatttup); + heap_close(attrdesc); + elog(WARN, "renameatt: attribute \"%-.*s\" exists", + NAMEDATALEN, newattname); + } + + namestrcpy(&(((AttributeTupleForm)(GETSTRUCT(oldatttup)))->attname), + newattname); + oldTID = oldatttup->t_ctid; + + /* insert "fixed" tuple */ + (void) heap_replace(attrdesc, &oldTID, oldatttup); + + /* keep system catalog indices current */ + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_attr_indices, attrdesc, oldatttup); + CatalogCloseIndices(Num_pg_attr_indices, idescs); + + heap_close(attrdesc); + pfree(oldatttup); +} + +/* + * renamerel - change the name of a relation + * + * Relname attribute is changed in relation catalog. + * No record of the previous relname is kept (correct?). + * + * scan relation catalog + * for name conflict + * for original relation (if not arg) + * modify relname in relation tuple + * insert modified relation in relation catalog + * delete original relation from relation catalog + * + * XXX Will currently lose track of a relation if it is unable to + * properly replace the new relation tuple. + */ +void +renamerel(char oldrelname[], char newrelname[]) +{ + Relation relrdesc; /* for RELATION relation */ + HeapTuple oldreltup, newreltup; + ItemPointerData oldTID; + char oldpath[MAXPGPATH], newpath[MAXPGPATH]; + Relation idescs[Num_pg_class_indices]; + + if (IsSystemRelationName(oldrelname)) { + elog(WARN, "renamerel: system relation \"%-.*s\" not renamed", + NAMEDATALEN, oldrelname); + return; + } + if (IsSystemRelationName(newrelname)) { + elog(WARN, "renamerel: Illegal class name: \"%-.*s\" -- pg_ is reserved for system catalogs", + NAMEDATALEN, newrelname); + return; + } + + relrdesc = heap_openr(RelationRelationName); + oldreltup = ClassNameIndexScan(relrdesc, oldrelname); + + if (!PointerIsValid(oldreltup)) { + heap_close(relrdesc); + elog(WARN, "renamerel: relation \"%-.*s\" does not exist", + NAMEDATALEN, oldrelname); + } + + newreltup = ClassNameIndexScan(relrdesc, newrelname); + if (PointerIsValid(newreltup)) { + pfree(oldreltup); + heap_close(relrdesc); + elog(WARN, "renamerel: relation \"%-.*s\" exists", + NAMEDATALEN, newrelname); + } + + /* rename the directory first, so if this fails the rename's not done */ + (void) strcpy(oldpath, relpath(oldrelname)); + (void) strcpy(newpath, relpath(newrelname)); + if (rename(oldpath, newpath) < 0) + elog(WARN, "renamerel: unable to rename file: %m"); + + memmove((char *) (((Form_pg_class) GETSTRUCT(oldreltup))->relname.data), + newrelname, + NAMEDATALEN); + oldTID = oldreltup->t_ctid; + + /* insert fixed rel tuple */ + (void) heap_replace(relrdesc, &oldTID, oldreltup); + + /* keep the system catalog indices current */ + CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_class_indices, relrdesc, oldreltup); + CatalogCloseIndices(Num_pg_class_indices, idescs); + + pfree(oldreltup); + heap_close(relrdesc); +} diff --git a/src/backend/commands/rename.h b/src/backend/commands/rename.h new file mode 100644 index 00000000000..c3889e12f89 --- /dev/null +++ b/src/backend/commands/rename.h @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * + * rename.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: rename.h,v 1.1.1.1 1996/07/09 06:21:22 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef RENAME_H +#define RENAME_H + +extern void renameatt(char *relname, + char *oldattname, + char *newattname, + char *userName, int recurse); + +extern void renamerel(char *oldrelname, + char *newrelname); + +#endif /* RENAME_H */ diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c new file mode 100644 index 00000000000..7e1514cd2a3 --- /dev/null +++ b/src/backend/commands/vacuum.c @@ -0,0 +1,853 @@ +/*------------------------------------------------------------------------- + * + * vacuum.c-- + * the postgres vacuum cleaner + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.1.1.1 1996/07/09 06:21:22 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <sys/file.h> + +#include "postgres.h" +#include "utils/portal.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/xact.h" +#include "storage/bufmgr.h" +#include "access/transam.h" +#include "utils/tqual.h" +#include "access/htup.h" + +#include "catalog/pg_index.h" +#include "catalog/catname.h" +#include "catalog/pg_class.h" +#include "catalog/pg_proc.h" + +#include "storage/fd.h" /* for O_ */ +#include "storage/itemid.h" +#include "storage/bufmgr.h" +#include "storage/bufpage.h" +#include "storage/smgr.h" + +#include "utils/elog.h" +#include "utils/mcxt.h" +#include "utils/palloc.h" + +#include "commands/vacuum.h" + +bool VacuumRunning = false; + +/* non-export function prototypes */ +static void _vc_init(char *vacrel); +static void _vc_shutdown(char *vacrel); +static void _vc_vacuum(char *vacrel); +static VRelList _vc_getrels(Portal p, char *vacrel); +static void _vc_vacone(Portal p, VRelList curvrl); +static void _vc_vacheap(Portal p, VRelList curvrl, Relation onerel); +static void _vc_vacindices(VRelList curvrl, Relation onerel); +static void _vc_vaconeind(VRelList curvrl, Relation indrel); +static void _vc_updstats(Oid relid, int npages, int ntuples, bool hasindex); +static void _vc_setpagelock(Relation rel, BlockNumber blkno); +static bool _vc_ontidlist(ItemPointer itemptr, VTidList tidlist); +static void _vc_reaptid(Portal p, VRelList curvrl, BlockNumber blkno, + OffsetNumber offnum); +static void _vc_free(Portal p, VRelList vrl); +static Relation _vc_getarchrel(Relation heaprel); +static void _vc_archive(Relation archrel, HeapTuple htup); +static bool _vc_isarchrel(char *rname); + +void +vacuum(char *vacrel) +{ + /* initialize vacuum cleaner */ + _vc_init(vacrel); + + /* vacuum the database */ + _vc_vacuum(vacrel); + + /* clean up */ + _vc_shutdown(vacrel); +} + +/* + * _vc_init(), _vc_shutdown() -- start up and shut down the vacuum cleaner. + * + * We run exactly one vacuum cleaner at a time. We use the file system + * to guarantee an exclusive lock on vacuuming, since a single vacuum + * cleaner instantiation crosses transaction boundaries, and we'd lose + * postgres-style locks at the end of every transaction. + * + * The strangeness with committing and starting transactions in the + * init and shutdown routines is due to the fact that the vacuum cleaner + * is invoked via a sql command, and so is already executing inside + * a transaction. We need to leave ourselves in a predictable state + * on entry and exit to the vacuum cleaner. We commit the transaction + * started in PostgresMain() inside _vc_init(), and start one in + * _vc_shutdown() to match the commit waiting for us back in + * PostgresMain(). + */ +static void +_vc_init(char *vacrel) +{ + int fd; + + if ((fd = open("pg_vlock", O_CREAT|O_EXCL, 0600)) < 0) + elog(WARN, "can't create lock file -- another vacuum cleaner running?"); + + close(fd); + + /* + * By here, exclusive open on the lock file succeeded. If we abort + * for any reason during vacuuming, we need to remove the lock file. + * This global variable is checked in the transaction manager on xact + * abort, and the routine vc_abort() is called if necessary. + */ + + VacuumRunning = true; + + /* matches the StartTransaction in PostgresMain() */ + CommitTransactionCommand(); +} + +static void +_vc_shutdown(char *vacrel) +{ + /* on entry, not in a transaction */ + if (unlink("pg_vlock") < 0) + elog(WARN, "vacuum: can't destroy lock file!"); + + /* okay, we're done */ + VacuumRunning = false; + + /* matches the CommitTransaction in PostgresMain() */ + StartTransactionCommand(); +} + +void +vc_abort() +{ + /* on abort, remove the vacuum cleaner lock file */ + (void) unlink("pg_vlock"); + + VacuumRunning = false; +} + +/* + * _vc_vacuum() -- vacuum the database. + * + * This routine builds a list of relations to vacuum, and then calls + * code that vacuums them one at a time. We are careful to vacuum each + * relation in a separate transaction in order to avoid holding too many + * locks at one time. + */ +static void +_vc_vacuum(char *vacrel) +{ + VRelList vrl, cur; + char *pname; + Portal p; + + /* + * Create a portal for safe memory across transctions. We need to + * palloc the name space for it because our hash function expects + * the name to be on a longword boundary. CreatePortal copies the + * name to safe storage for us. + */ + + pname = (char *) palloc(strlen(VACPNAME) + 1); + strcpy(pname, VACPNAME); + p = CreatePortal(pname); + pfree(pname); + + /* get list of relations */ + vrl = _vc_getrels(p, vacrel); + + /* vacuum each heap relation */ + for (cur = vrl; cur != (VRelList) NULL; cur = cur->vrl_next) + _vc_vacone(p, cur); + + _vc_free(p, vrl); + + PortalDestroy(&p); +} + +static VRelList +_vc_getrels(Portal p, char *vacrel) +{ + Relation pgclass; + TupleDesc pgcdesc; + HeapScanDesc pgcscan; + HeapTuple pgctup; + Buffer buf; + PortalVariableMemory portalmem; + MemoryContext old; + VRelList vrl, cur; + Datum d; + char *rname; + int16 smgrno; + bool n; + ScanKeyData pgckey; + + StartTransactionCommand(); + + if (vacrel) { + ScanKeyEntryInitialize(&pgckey, 0x0, Anum_pg_class_relname, + NameEqualRegProcedure, + PointerGetDatum(vacrel)); + } else { + ScanKeyEntryInitialize(&pgckey, 0x0, Anum_pg_class_relkind, + CharacterEqualRegProcedure, CharGetDatum('r')); + } + + portalmem = PortalGetVariableMemory(p); + vrl = (VRelList) NULL; + + pgclass = heap_openr(RelationRelationName); + pgcdesc = RelationGetTupleDescriptor(pgclass); + + pgcscan = heap_beginscan(pgclass, false, NowTimeQual, 1, &pgckey); + + while (HeapTupleIsValid(pgctup = heap_getnext(pgcscan, 0, &buf))) { + + /* + * We have to be careful not to vacuum the archive (since it + * already contains vacuumed tuples), and not to vacuum + * relations on write-once storage managers like the Sony + * jukebox at Berkeley. + */ + + d = (Datum) heap_getattr(pgctup, buf, Anum_pg_class_relname, + pgcdesc, &n); + rname = (char*)d; + + /* skip archive relations */ + if (_vc_isarchrel(rname)) { + ReleaseBuffer(buf); + continue; + } + + d = (Datum) heap_getattr(pgctup, buf, Anum_pg_class_relsmgr, + pgcdesc, &n); + smgrno = DatumGetInt16(d); + + /* skip write-once storage managers */ + if (smgriswo(smgrno)) { + ReleaseBuffer(buf); + continue; + } + + /* get a relation list entry for this guy */ + old = MemoryContextSwitchTo((MemoryContext)portalmem); + if (vrl == (VRelList) NULL) { + vrl = cur = (VRelList) palloc(sizeof(VRelListData)); + } else { + cur->vrl_next = (VRelList) palloc(sizeof(VRelListData)); + cur = cur->vrl_next; + } + (void) MemoryContextSwitchTo(old); + + cur->vrl_relid = pgctup->t_oid; + cur->vrl_attlist = (VAttList) NULL; + cur->vrl_tidlist = (VTidList) NULL; + cur->vrl_npages = cur->vrl_ntups = 0; + cur->vrl_hasindex = false; + cur->vrl_next = (VRelList) NULL; + + /* wei hates it if you forget to do this */ + ReleaseBuffer(buf); + } + + heap_close(pgclass); + heap_endscan(pgcscan); + + CommitTransactionCommand(); + + return (vrl); +} + +/* + * _vc_vacone() -- vacuum one heap relation + * + * This routine vacuums a single heap, cleans out its indices, and + * updates its statistics npages and ntuples statistics. + * + * Doing one heap at a time incurs extra overhead, since we need to + * check that the heap exists again just before we vacuum it. The + * reason that we do this is so that vacuuming can be spread across + * many small transactions. Otherwise, two-phase locking would require + * us to lock the entire database during one pass of the vacuum cleaner. + */ +static void +_vc_vacone(Portal p, VRelList curvrl) +{ + Relation pgclass; + TupleDesc pgcdesc; + HeapTuple pgctup; + Buffer pgcbuf; + HeapScanDesc pgcscan; + Relation onerel; + ScanKeyData pgckey; + + StartTransactionCommand(); + + ScanKeyEntryInitialize(&pgckey, 0x0, ObjectIdAttributeNumber, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(curvrl->vrl_relid)); + + pgclass = heap_openr(RelationRelationName); + pgcdesc = RelationGetTupleDescriptor(pgclass); + pgcscan = heap_beginscan(pgclass, false, NowTimeQual, 1, &pgckey); + + /* + * Race condition -- if the pg_class tuple has gone away since the + * last time we saw it, we don't need to vacuum it. + */ + + if (!HeapTupleIsValid(pgctup = heap_getnext(pgcscan, 0, &pgcbuf))) { + heap_endscan(pgcscan); + heap_close(pgclass); + CommitTransactionCommand(); + return; + } + + /* now open the class and vacuum it */ + onerel = heap_open(curvrl->vrl_relid); + + /* we require the relation to be locked until the indices are cleaned */ + RelationSetLockForWrite(onerel); + + /* vacuum it */ + _vc_vacheap(p, curvrl, onerel); + + /* if we vacuumed any heap tuples, vacuum the indices too */ + if (curvrl->vrl_tidlist != (VTidList) NULL) + _vc_vacindices(curvrl, onerel); + else + curvrl->vrl_hasindex = onerel->rd_rel->relhasindex; + + /* all done with this class */ + heap_close(onerel); + heap_endscan(pgcscan); + heap_close(pgclass); + + /* update statistics in pg_class */ + _vc_updstats(curvrl->vrl_relid, curvrl->vrl_npages, curvrl->vrl_ntups, + curvrl->vrl_hasindex); + + CommitTransactionCommand(); +} + +/* + * _vc_vacheap() -- vacuum an open heap relation + * + * This routine sets commit times, vacuums dead tuples, cleans up + * wasted space on the page, and maintains statistics on the number + * of live tuples in a heap. In addition, it records the tids of + * all tuples removed from the heap for any reason. These tids are + * used in a scan of indices on the relation to get rid of dead + * index tuples. + */ +static void +_vc_vacheap(Portal p, VRelList curvrl, Relation onerel) +{ + int nblocks, blkno; + ItemId itemid; + HeapTuple htup; + Buffer buf; + Page page; + OffsetNumber offnum, maxoff; + Relation archrel; + bool isarchived; + int nvac; + int ntups; + bool pgchanged, tupgone; + AbsoluteTime purgetime, expiretime; + RelativeTime preservetime; + + nvac = 0; + ntups = 0; + nblocks = RelationGetNumberOfBlocks(onerel); + + { + char *relname; + relname = (RelationGetRelationName(onerel))->data; + + if ( (strlen(relname) > 4) && + relname[0] == 'X' && + relname[1] == 'i' && + relname[2] == 'n' && + (relname[3] == 'v' || relname[3] == 'x')) + return; + } + + + /* if the relation has an archive, open it */ + if (onerel->rd_rel->relarch != 'n') { + isarchived = true; + archrel = _vc_getarchrel(onerel); + } else + isarchived = false; + + /* don't vacuum large objects for now. + something breaks when we do*/ + { + char *relname; + relname = (RelationGetRelationName(onerel))->data; + + if ( (strlen(relname) > 4) && + relname[0] == 'X' && + relname[1] == 'i' && + relname[2] == 'n' && + (relname[3] == 'v' || relname[3] == 'x')) + return; + } + + /* calculate the purge time: tuples that expired before this time + will be archived or deleted */ + purgetime = GetCurrentTransactionStartTime(); + expiretime = (AbsoluteTime)onerel->rd_rel->relexpires; + preservetime = (RelativeTime)onerel->rd_rel->relpreserved; + + if (RelativeTimeIsValid(preservetime) && (preservetime)) { + purgetime -= preservetime; + if (AbsoluteTimeIsBackwardCompatiblyValid(expiretime) && + expiretime > purgetime) + purgetime = expiretime; + } + + else if (AbsoluteTimeIsBackwardCompatiblyValid(expiretime)) + purgetime = expiretime; + + for (blkno = 0; blkno < nblocks; blkno++) { + buf = ReadBuffer(onerel, blkno); + page = BufferGetPage(buf); + + if (PageIsEmpty(page)) { + ReleaseBuffer(buf); + continue; + } + + pgchanged = false; + maxoff = PageGetMaxOffsetNumber(page); + for (offnum = FirstOffsetNumber; + offnum <= maxoff; + offnum = OffsetNumberNext(offnum)) { + itemid = PageGetItemId(page, offnum); + + if (!ItemIdIsUsed(itemid)) + continue; + + htup = (HeapTuple) PageGetItem(page, itemid); + tupgone = false; + + if (!AbsoluteTimeIsBackwardCompatiblyValid(htup->t_tmin) && + TransactionIdIsValid((TransactionId)htup->t_xmin)) { + + if (TransactionIdDidAbort(htup->t_xmin)) { + _vc_reaptid(p, curvrl, blkno, offnum); + pgchanged = true; + tupgone = true; + } else if (TransactionIdDidCommit(htup->t_xmin)) { + htup->t_tmin = TransactionIdGetCommitTime(htup->t_xmin); + pgchanged = true; + } + } + + if (TransactionIdIsValid((TransactionId)htup->t_xmax)) { + if (TransactionIdDidAbort(htup->t_xmax)) { + StoreInvalidTransactionId(&(htup->t_xmax)); + pgchanged = true; + } else if (TransactionIdDidCommit(htup->t_xmax)) { + if (!AbsoluteTimeIsBackwardCompatiblyReal(htup->t_tmax)) { + + htup->t_tmax = TransactionIdGetCommitTime(htup->t_xmax); + pgchanged = true; + } + + /* + * Reap the dead tuple if its expiration time is + * before purgetime. + */ + + if (!tupgone && htup->t_tmax < purgetime) { + _vc_reaptid(p, curvrl, blkno, offnum); + tupgone = true; + pgchanged = true; + } + } + } + + if (tupgone) { + ItemId lpp = &(((PageHeader) page)->pd_linp[offnum - 1]); + + /* write the tuple to the archive, if necessary */ + if (isarchived) + _vc_archive(archrel, htup); + + /* mark it unused */ + lpp->lp_flags &= ~LP_USED; + + ++nvac; + } else { + ntups++; + } + } + + if (pgchanged) { + PageRepairFragmentation(page); + WriteBuffer(buf); + } else { + ReleaseBuffer(buf); + } + } + + if (isarchived) + heap_close(archrel); + + /* save stats in the rel list for use later */ + curvrl->vrl_ntups = ntups; + curvrl->vrl_npages = nblocks; +} + +/* + * _vc_vacindices() -- vacuum all the indices for a particular heap relation. + * + * On entry, curvrl points at the relation currently being vacuumed. + * We already have a write lock on the relation, so we don't need to + * worry about anyone building an index on it while we're doing the + * vacuuming. The tid list for curvrl is sorted in reverse tid order: + * that is, tids on higher page numbers are before those on lower page + * numbers, and tids high on the page are before those low on the page. + * We use this ordering to cut down the search cost when we look at an + * index entry. + * + * We're executing inside the transaction that vacuumed the heap. + */ +static void +_vc_vacindices(VRelList curvrl, Relation onerel) +{ + Relation pgindex; + TupleDesc pgidesc; + HeapTuple pgitup; + HeapScanDesc pgiscan; + Buffer buf; + Relation indrel; + Oid indoid; + Datum d; + bool n; + int nindices; + ScanKeyData pgikey; + + /* see if we can dodge doing any work at all */ + if (!(onerel->rd_rel->relhasindex)) + return; + + nindices = 0; + + /* prepare a heap scan on the pg_index relation */ + pgindex = heap_openr(IndexRelationName); + pgidesc = RelationGetTupleDescriptor(pgindex); + + ScanKeyEntryInitialize(&pgikey, 0x0, Anum_pg_index_indrelid, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(curvrl->vrl_relid)); + + pgiscan = heap_beginscan(pgindex, false, NowTimeQual, 1, &pgikey); + + /* vacuum all the indices */ + while (HeapTupleIsValid(pgitup = heap_getnext(pgiscan, 0, &buf))) { + d = (Datum) heap_getattr(pgitup, buf, Anum_pg_index_indexrelid, + pgidesc, &n); + indoid = DatumGetObjectId(d); + indrel = index_open(indoid); + _vc_vaconeind(curvrl, indrel); + heap_close(indrel); + nindices++; + } + + heap_endscan(pgiscan); + heap_close(pgindex); + + if (nindices > 0) + curvrl->vrl_hasindex = true; + else + curvrl->vrl_hasindex = false; +} + +/* + * _vc_vaconeind() -- vacuum one index relation. + * + * Curvrl is the VRelList entry for the heap we're currently vacuuming. + * It's locked. The vrl_tidlist entry in curvrl is the list of deleted + * heap tids, sorted in reverse (page, offset) order. Onerel is an + * index relation on the vacuumed heap. We don't set locks on the index + * relation here, since the indexed access methods support locking at + * different granularities. We let them handle it. + * + * Finally, we arrange to update the index relation's statistics in + * pg_class. + */ +static void +_vc_vaconeind(VRelList curvrl, Relation indrel) +{ + RetrieveIndexResult res; + IndexScanDesc iscan; + ItemPointer heapptr; + int nvac; + int nitups; + int nipages; + + /* walk through the entire index */ + iscan = index_beginscan(indrel, false, 0, (ScanKey) NULL); + nvac = 0; + nitups = 0; + + while ((res = index_getnext(iscan, ForwardScanDirection)) + != (RetrieveIndexResult) NULL) { + heapptr = &res->heap_iptr; + + if (_vc_ontidlist(heapptr, curvrl->vrl_tidlist)) { +#if 0 + elog(DEBUG, "<%x,%x> -> <%x,%x>", + ItemPointerGetBlockNumber(&(res->index_iptr)), + ItemPointerGetOffsetNumber(&(res->index_iptr)), + ItemPointerGetBlockNumber(&(res->heap_iptr)), + ItemPointerGetOffsetNumber(&(res->heap_iptr))); +#endif + ++nvac; + index_delete(indrel, &res->index_iptr); + } else { + nitups++; + } + + /* be tidy */ + pfree(res); + } + + index_endscan(iscan); + + /* now update statistics in pg_class */ + nipages = RelationGetNumberOfBlocks(indrel); + _vc_updstats(indrel->rd_id, nipages, nitups, false); +} + +/* + * _vc_updstats() -- update pg_class statistics for one relation + * + * This routine works for both index and heap relation entries in + * pg_class. We violate no-overwrite semantics here by storing new + * values for ntuples, npages, and hasindex directly in the pg_class + * tuple that's already on the page. The reason for this is that if + * we updated these tuples in the usual way, then every tuple in pg_class + * would be replaced every day. This would make planning and executing + * historical queries very expensive. + */ +static void +_vc_updstats(Oid relid, int npages, int ntuples, bool hasindex) +{ + Relation rd; + HeapScanDesc sdesc; + HeapTuple tup; + Buffer buf; + Form_pg_class pgcform; + ScanKeyData skey; + + /* + * update number of tuples and number of pages in pg_class + */ + ScanKeyEntryInitialize(&skey, 0x0, ObjectIdAttributeNumber, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(relid)); + + rd = heap_openr(RelationRelationName); + sdesc = heap_beginscan(rd, false, NowTimeQual, 1, &skey); + + if (!HeapTupleIsValid(tup = heap_getnext(sdesc, 0, &buf))) + elog(WARN, "pg_class entry for relid %d vanished during vacuuming", + relid); + + /* overwrite the existing statistics in the tuple */ + _vc_setpagelock(rd, BufferGetBlockNumber(buf)); + pgcform = (Form_pg_class) GETSTRUCT(tup); + pgcform->reltuples = ntuples; + pgcform->relpages = npages; + pgcform->relhasindex = hasindex; + + /* XXX -- after write, should invalidate relcache in other backends */ + WriteNoReleaseBuffer(buf); + + /* that's all, folks */ + heap_endscan(sdesc); + heap_close(rd); + +} + +static void _vc_setpagelock(Relation rel, BlockNumber blkno) +{ + ItemPointerData itm; + + ItemPointerSet(&itm, blkno, 1); + + RelationSetLockForWritePage(rel, &itm); +} + +/* + * _vc_ontidlist() -- is a particular tid on the supplied tid list? + * + * Tidlist is sorted in reverse (page, offset) order. + */ +static bool +_vc_ontidlist(ItemPointer itemptr, VTidList tidlist) +{ + BlockNumber ibkno; + OffsetNumber ioffno; + ItemPointer check; + BlockNumber ckbkno; + OffsetNumber ckoffno; + + ibkno = ItemPointerGetBlockNumber(itemptr); + ioffno = ItemPointerGetOffsetNumber(itemptr); + + while (tidlist != (VTidList) NULL) { + check = &(tidlist->vtl_tid); + ckbkno = ItemPointerGetBlockNumber(check); + ckoffno = ItemPointerGetOffsetNumber(check); + + /* see if we've looked far enough down the list */ + if ((ckbkno < ibkno) || (ckbkno == ibkno && ckoffno < ioffno)) + return (false); + + /* see if we have a match */ + if (ckbkno == ibkno && ckoffno == ioffno) + return (true); + + /* check next */ + tidlist = tidlist->vtl_next; + } + + /* ran off the end of the list without finding a match */ + return (false); +} + +/* + * _vc_reaptid() -- save a tid on the list of reaped tids for the current + * entry on the vacuum relation list. + * + * As a side effect of the way that the vacuuming loop for a given + * relation works, the tids of vacuumed tuples wind up in reverse + * order in the list -- highest tid on a page is first, and higher + * pages come before lower pages. This is important later when we + * vacuum the indices, as it gives us a way of stopping the search + * for a tid if we notice we've passed the page it would be on. + */ +static void +_vc_reaptid(Portal p, + VRelList curvrl, + BlockNumber blkno, + OffsetNumber offnum) +{ + PortalVariableMemory pmem; + MemoryContext old; + VTidList newvtl; + + /* allocate a VTidListData entry in the portal memory context */ + pmem = PortalGetVariableMemory(p); + old = MemoryContextSwitchTo((MemoryContext) pmem); + newvtl = (VTidList) palloc(sizeof(VTidListData)); + MemoryContextSwitchTo(old); + + /* fill it in */ + ItemPointerSet(&(newvtl->vtl_tid), blkno, offnum); + newvtl->vtl_next = curvrl->vrl_tidlist; + curvrl->vrl_tidlist = newvtl; +} + +static void +_vc_free(Portal p, VRelList vrl) +{ + VRelList p_vrl; + VAttList p_val, val; + VTidList p_vtl, vtl; + MemoryContext old; + PortalVariableMemory pmem; + + pmem = PortalGetVariableMemory(p); + old = MemoryContextSwitchTo((MemoryContext)pmem); + + while (vrl != (VRelList) NULL) { + + /* free attribute list */ + val = vrl->vrl_attlist; + while (val != (VAttList) NULL) { + p_val = val; + val = val->val_next; + pfree(p_val); + } + + /* free tid list */ + vtl = vrl->vrl_tidlist; + while (vtl != (VTidList) NULL) { + p_vtl = vtl; + vtl = vtl->vtl_next; + pfree(p_vtl); + } + + /* free rel list entry */ + p_vrl = vrl; + vrl = vrl->vrl_next; + pfree(p_vrl); + } + + (void) MemoryContextSwitchTo(old); +} + +/* + * _vc_getarchrel() -- open the archive relation for a heap relation + * + * The archive relation is named 'a,XXXXX' for the heap relation + * whose relid is XXXXX. + */ + +#define ARCHIVE_PREFIX "a," + +static Relation +_vc_getarchrel(Relation heaprel) +{ + Relation archrel; + char *archrelname; + + archrelname = palloc(sizeof(ARCHIVE_PREFIX) + NAMEDATALEN); /* bogus */ + sprintf(archrelname, "%s%d", ARCHIVE_PREFIX, heaprel->rd_id); + + archrel = heap_openr(archrelname); + + pfree(archrelname); + return (archrel); +} + +/* + * _vc_archive() -- write a tuple to an archive relation + * + * In the future, this will invoke the archived accessd method. For + * now, archive relations are on mag disk. + */ +static void +_vc_archive(Relation archrel, HeapTuple htup) +{ + doinsert(archrel, htup); +} + +static bool +_vc_isarchrel(char *rname) +{ + if (strncmp(ARCHIVE_PREFIX, rname,strlen(ARCHIVE_PREFIX)) == 0) + return (true); + + return (false); +} diff --git a/src/backend/commands/vacuum.h b/src/backend/commands/vacuum.h new file mode 100644 index 00000000000..f5994d7d6d5 --- /dev/null +++ b/src/backend/commands/vacuum.h @@ -0,0 +1,48 @@ +/*------------------------------------------------------------------------- + * + * vacuum.h-- + * header file for postgres vacuum cleaner + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: vacuum.h,v 1.1.1.1 1996/07/09 06:21:23 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef VACUUM_H +#define VACUUM_H + +typedef struct VAttListData { + int val_dummy; + struct VAttListData *val_next; +} VAttListData; + +typedef VAttListData *VAttList; + +typedef struct VTidListData { + ItemPointerData vtl_tid; + struct VTidListData *vtl_next; +} VTidListData; + +typedef VTidListData *VTidList; + +typedef struct VRelListData { + Oid vrl_relid; + VAttList vrl_attlist; + VTidList vrl_tidlist; + int vrl_ntups; + int vrl_npages; + bool vrl_hasindex; + struct VRelListData *vrl_next; +} VRelListData; + +typedef VRelListData *VRelList; + +extern bool VacuumRunning; + +extern void vc_abort(void); +extern void vacuum(char *vacrel); + + +#endif /* VACUUM_H */ diff --git a/src/backend/commands/version.h b/src/backend/commands/version.h new file mode 100644 index 00000000000..20d49d2c0c7 --- /dev/null +++ b/src/backend/commands/version.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * version.h-- + * Header file for versions. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: version.h,v 1.1.1.1 1996/07/09 06:21:23 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef VERSION_H +#define VERSION_H + +#include "postgres.h" +#include "nodes/pg_list.h" + +extern void DefineVersion(char *name, char *fromRelname, char *date); +extern void VersionCreate(char *vname, char *bname); +extern void VersionAppend(char *vname, char *bname); +extern void VersionRetrieve(char *vname, char *bname, char *snapshot); +extern void VersionDelete(char *vname, char *bname, char *snapshot); +extern void VersionReplace(char *vname, char *bname, char *snapshot); + +#endif /* VERSION_H */ diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c new file mode 100644 index 00000000000..f6023ca08de --- /dev/null +++ b/src/backend/commands/view.c @@ -0,0 +1,325 @@ +/*------------------------------------------------------------------------- + * + * view.c-- + * use rewrite rules to construct views + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/commands/view.c,v 1.1.1.1 1996/07/09 06:21:22 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> /* for sprintf() */ +#include "postgres.h" +#include "access/heapam.h" +#include "access/xact.h" +#include "utils/builtins.h" +#include "utils/syscache.h" +#include "utils/elog.h" +#include "utils/palloc.h" +#include "nodes/relation.h" +#include "nodes/primnodes.h" +#include "nodes/parsenodes.h" +#include "parser/catalog_utils.h" +#include "parser/parse_query.h" +#include "rewrite/rewriteDefine.h" +#include "rewrite/rewriteHandler.h" +#include "rewrite/rewriteManip.h" +#include "rewrite/rewriteRemove.h" +#include "commands/creatinh.h" + +/*--------------------------------------------------------------------- + * DefineVirtualRelation + * + * Create the "view" relation. + * `DefineRelation' does all the work, we just provide the correct + * arguments! + * + * If the relation already exists, then 'DefineRelation' will abort + * the xact... + *--------------------------------------------------------------------- + */ +static void +DefineVirtualRelation(char *relname, List *tlist) +{ + CreateStmt createStmt; + List *attrList, *t; + TargetEntry *entry; + Resdom *res; + char *resname; + char *restypename; + + /* + * create a list with one entry per attribute of this relation. + * Each entry is a two element list. The first element is the + * name of the attribute (a string) and the second the name of the type + * (NOTE: a string, not a type id!). + */ + attrList = NIL; + if (tlist!=NIL) { + foreach (t, tlist ) { + ColumnDef *def = makeNode(ColumnDef); + TypeName *typename; + + /* + * find the names of the attribute & its type + */ + entry = lfirst(t); + res = entry->resdom; + resname = res->resname; + restypename = tname(get_id_type((long)res->restype)); + + typename = makeNode(TypeName); + + typename->name = pstrdup(restypename); + def->colname = pstrdup(resname); + + def->typename = typename; + + attrList = lappend(attrList, def); + } + } else { + elog ( WARN, "attempted to define virtual relation with no attrs"); + } + + /* + * now create the parametesr for keys/inheritance etc. + * All of them are nil... + */ + createStmt.relname = relname; + createStmt.tableElts = attrList; +/* createStmt.tableType = NULL;*/ + createStmt.inhRelnames = NIL; + createStmt.archiveType = ARCH_NONE; + createStmt.location = -1; + createStmt.archiveLoc = -1; + + /* + * finally create the relation... + */ + DefineRelation(&createStmt); +} + +/*------------------------------------------------------------------ + * makeViewRetrieveRuleName + * + * Given a view name, returns the name for the 'on retrieve to "view"' + * rule. + * This routine is called when defining/removing a view. + * + * NOTE: it quarantees that the name is at most 15 chars long + * + * XXX it also means viewName cannot be 16 chars long! - ay 11/94 + *------------------------------------------------------------------ + */ +char * +MakeRetrieveViewRuleName(char *viewName) +{ +/* + char buf[100]; + + memset(buf, 0, sizeof(buf)); + sprintf(buf, "_RET%.*s", NAMEDATALEN, viewName->data); + buf[15] = '\0'; + namestrcpy(rule_name, buf); +*/ + + char *buf; + buf = palloc(strlen(viewName) + 5); + sprintf(buf, "_RET%s",viewName); + return buf; +} + +static RuleStmt * +FormViewRetrieveRule(char *viewName, Query *viewParse) +{ + RuleStmt *rule; + char *rname; + Attr *attr; + + /* + * Create a RuleStmt that corresponds to the suitable + * rewrite rule args for DefineQueryRewrite(); + */ + rule = makeNode(RuleStmt); + rname = MakeRetrieveViewRuleName(viewName); + + attr = makeNode(Attr); + attr->relname = pstrdup(viewName); +/* attr->refname = pstrdup(viewName);*/ + rule->rulename = pstrdup(rname); + rule->whereClause = NULL; + rule->event = CMD_SELECT; + rule->object = attr; + rule->instead = true; + rule->actions = lcons(viewParse, NIL); + + return rule; +} + +static void +DefineViewRules(char *viewName, Query *viewParse) +{ + RuleStmt *retrieve_rule = NULL; +#ifdef NOTYET + RuleStmt *replace_rule = NULL; + RuleStmt *append_rule = NULL; + RuleStmt *delete_rule = NULL; +#endif + + retrieve_rule = + FormViewRetrieveRule(viewName, viewParse); + +#ifdef NOTYET + + replace_rule = + FormViewReplaceRule(viewName, viewParse); + append_rule = + FormViewAppendRule(viewName, viewParse); + delete_rule = + FormViewDeleteRule(viewName, viewParse); + +#endif + + DefineQueryRewrite(retrieve_rule); + +#ifdef NOTYET + DefineQueryRewrite(replace_rule); + DefineQueryRewrite(append_rule); + DefineQueryRewrite(delete_rule); +#endif + +} + +/*--------------------------------------------------------------- + * UpdateRangeTableOfViewParse + * + * Update the range table of the given parsetree. + * This update consists of adding two new entries IN THE BEGINNING + * of the range table (otherwise the rule system will die a slow, + * horrible and painful death, and we do not want that now, do we?) + * one for the CURRENT relation and one for the NEW one (both of + * them refer in fact to the "view" relation). + * + * Of course we must also increase the 'varnos' of all the Var nodes + * by 2... + * + * NOTE: these are destructive changes. It would be difficult to + * make a complete copy of the parse tree and make the changes + * in the copy. + *--------------------------------------------------------------- + */ +static void +UpdateRangeTableOfViewParse(char *viewName, Query *viewParse) +{ + List *old_rt; + List *new_rt; + RangeTblEntry *rt_entry1, *rt_entry2; + + /* + * first offset all var nodes by 2 + */ + OffsetVarNodes((Node*)viewParse->targetList, 2); + OffsetVarNodes(viewParse->qual, 2); + + /* + * find the old range table... + */ + old_rt = viewParse->rtable; + + /* + * create the 2 new range table entries and form the new + * range table... + * CURRENT first, then NEW.... + */ + rt_entry1 = + makeRangeTableEntry((char*)viewName, FALSE, NULL, "*CURRENT*"); + rt_entry2 = + makeRangeTableEntry((char*)viewName, FALSE, NULL, "*NEW*"); + new_rt = lcons(rt_entry2, old_rt); + new_rt = lcons(rt_entry1, new_rt); + + /* + * Now the tricky part.... + * Update the range table in place... Be careful here, or + * hell breaks loooooooooooooOOOOOOOOOOOOOOOOOOSE! + */ + viewParse->rtable = new_rt; +} + +/*------------------------------------------------------------------- + * DefineView + * + * - takes a "viewname", "parsetree" pair and then + * 1) construct the "virtual" relation + * 2) commit the command but NOT the transaction, + * so that the relation exists + * before the rules are defined. + * 2) define the "n" rules specified in the PRS2 paper + * over the "virtual" relation + *------------------------------------------------------------------- + */ +void +DefineView(char *viewName, Query *viewParse) +{ + List *viewTlist; + + viewTlist = viewParse->targetList; + + /* + * Create the "view" relation + * NOTE: if it already exists, the xaxt will be aborted. + */ + DefineVirtualRelation(viewName, viewTlist); + + /* + * The relation we have just created is not visible + * to any other commands running with the same transaction & + * command id. + * So, increment the command id counter (but do NOT pfree any + * memory!!!!) + */ + CommandCounterIncrement(); + + /* + * The range table of 'viewParse' does not contain entries + * for the "CURRENT" and "NEW" relations. + * So... add them! + * NOTE: we make the update in place! After this call 'viewParse' + * will never be what it used to be... + */ + UpdateRangeTableOfViewParse(viewName, viewParse); + DefineViewRules(viewName, viewParse); +} + +/*------------------------------------------------------------------ + * RemoveView + * + * Remove a view given its name + *------------------------------------------------------------------ + */ +void +RemoveView(char *viewName) +{ + char* rname; + + /* + * first remove all the "view" rules... + * Currently we only have one! + */ + rname = MakeRetrieveViewRuleName(viewName); + RemoveRewriteRule(rname); + + /* + * we don't really need that, but just in case... + */ + CommandCounterIncrement(); + + /* + * now remove the relation. + */ + heap_destroy(viewName); + pfree(rname); +} diff --git a/src/backend/commands/view.h b/src/backend/commands/view.h new file mode 100644 index 00000000000..15151237715 --- /dev/null +++ b/src/backend/commands/view.h @@ -0,0 +1,20 @@ +/*------------------------------------------------------------------------- + * + * view.h-- + * + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: view.h,v 1.1.1.1 1996/07/09 06:21:23 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef VIEW_H +#define VIEW_H + +extern char *MakeRetrieveViewRuleName(char *view_name); +extern void DefineView(char *view_name, Query *view_parse); +extern void RemoveView(char *view_name); + +#endif /* VIEW_H */ diff --git a/src/backend/executor/Makefile.inc b/src/backend/executor/Makefile.inc new file mode 100644 index 00000000000..211e725cec4 --- /dev/null +++ b/src/backend/executor/Makefile.inc @@ -0,0 +1,29 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for the executor module +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/backend/executor/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:24 scrappy Exp $ +# +#------------------------------------------------------------------------- + +VPATH:= $(VPATH):$(CURDIR)/executor + +SRCS_EXECUTOR= execAmi.c execFlatten.c execJunk.c execMain.c \ + execProcnode.c execQual.c execScan.c execTuples.c \ + execUtils.c functions.c nodeAppend.c nodeAgg.c nodeHash.c \ + nodeHashjoin.c nodeIndexscan.c nodeMaterial.c nodeMergejoin.c \ + nodeNestloop.c nodeResult.c nodeSeqscan.c nodeSort.c \ + nodeUnique.c nodeTee.c nodeGroup.c + +HEADERS+= execFlatten.h execdebug.h execdefs.h execdesc.h \ + executor.h functions.h hashjoin.h nodeAgg.h nodeAppend.h \ + nodeHash.h nodeHashjoin.h nodeIndexscan.h nodeMaterial.h \ + nodeMergejoin.h nodeNestloop.h nodeResult.h \ + nodeSeqscan.h nodeSort.h nodeUnique.h tuptable.h nodeTee.h \ + nodeGroup.h + diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c new file mode 100644 index 00000000000..08d3e70d8b9 --- /dev/null +++ b/src/backend/executor/execAmi.c @@ -0,0 +1,439 @@ +/*------------------------------------------------------------------------- + * + * execAmi.c-- + * miscellanious executor access method routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/execAmi.c,v 1.1.1.1 1996/07/09 06:21:24 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * + * ExecOpenScanR \ / amopen + * ExecBeginScan \ / ambeginscan + * ExecCloseR \ / amclose + * ExecInsert \ executor interface / aminsert + * ExecReScanNode / to access methods \ amrescan + * ExecReScanR / \ amrescan + * ExecMarkPos / \ ammarkpos + * ExecRestrPos / \ amrestpos + * + * ExecCreatR function to create temporary relations + * + */ +#include <stdio.h> /* for sprintf() */ +#include "executor/executor.h" +#include "storage/smgr.h" +#include "executor/nodeSeqscan.h" +#include "executor/nodeIndexscan.h" +#include "executor/nodeSort.h" +#include "executor/nodeTee.h" +#include "optimizer/internal.h" /* for _TEMP_RELATION_ID_ */ + +/* ---------------------------------------------------------------- + * ExecOpenScanR + * + * old comments: + * Parameters: + * relation -- relation to be opened and scanned. + * nkeys -- number of keys + * skeys -- keys to restrict scanning + * isindex -- if this is true, the relation is the relid of + * an index relation, else it is an index into the + * range table. + * Returns the relation as(relDesc scanDesc) + * If this structure is changed, need to modify the access macros + * defined in execInt.h. + * ---------------------------------------------------------------- + */ +void +ExecOpenScanR(Oid relOid, + int nkeys, + ScanKey skeys, + bool isindex, + ScanDirection dir, + TimeQual timeRange, + Relation *returnRelation, /* return */ + Pointer *returnScanDesc) /* return */ +{ + Relation relation; + Pointer scanDesc; + + /* ---------------- + * note: scanDesc returned by ExecBeginScan can be either + * a HeapScanDesc or an IndexScanDesc so for now we + * make it a Pointer. There should be a better scan + * abstraction someday -cim 9/9/89 + * ---------------- + */ + relation = ExecOpenR(relOid, isindex); + scanDesc = ExecBeginScan(relation, + nkeys, + skeys, + isindex, + dir, + timeRange); + + if (returnRelation != NULL) + *returnRelation = relation; + if (scanDesc != NULL) + *returnScanDesc = scanDesc; +} + +/* ---------------------------------------------------------------- + * ExecOpenR + * + * returns a relation descriptor given an object id. + * ---------------------------------------------------------------- + */ +Relation +ExecOpenR(Oid relationOid, bool isindex) +{ + Relation relation; + relation = (Relation) NULL; + + /* ---------------- + * open the relation with the correct call depending + * on whether this is a heap relation or an index relation. + * ---------------- + */ + if (isindex) { + relation = index_open(relationOid); + } else + relation = heap_open(relationOid); + + if (relation == NULL) + elog(DEBUG, "ExecOpenR: relation == NULL, heap_open failed."); + + return relation; +} + +/* ---------------------------------------------------------------- + * ExecBeginScan + * + * beginscans a relation in current direction. + * + * XXX fix parameters to AMbeginscan (and btbeginscan) + * currently we need to pass a flag stating whether + * or not the scan should begin at an endpoint of + * the relation.. Right now we always pass false + * -cim 9/14/89 + * ---------------------------------------------------------------- + */ +Pointer +ExecBeginScan(Relation relation, + int nkeys, + ScanKey skeys, + bool isindex, + ScanDirection dir, + TimeQual time_range) +{ + Pointer scanDesc; + + scanDesc = NULL; + + /* ---------------- + * open the appropriate type of scan. + * + * Note: ambeginscan()'s second arg is a boolean indicating + * that the scan should be done in reverse.. That is, + * if you pass it true, then the scan is backward. + * ---------------- + */ + if (isindex) { + scanDesc = (Pointer) index_beginscan(relation, + false, /* see above comment */ + nkeys, + skeys); + } else { + scanDesc = (Pointer) heap_beginscan(relation, + ScanDirectionIsBackward(dir), + time_range, + nkeys, + skeys); + } + + if (scanDesc == NULL) + elog(DEBUG, "ExecBeginScan: scanDesc = NULL, heap_beginscan failed."); + + + return scanDesc; +} + +/* ---------------------------------------------------------------- + * ExecCloseR + * + * closes the relation and scan descriptor for a scan or sort + * node. Also closes index relations and scans for index scans. + * + * old comments + * closes the relation indicated in 'relID' + * ---------------------------------------------------------------- + */ +void +ExecCloseR(Plan *node) +{ + CommonScanState *state; + Relation relation; + HeapScanDesc scanDesc; + + /* ---------------- + * shut down the heap scan and close the heap relation + * ---------------- + */ + switch (nodeTag(node)) { + + case T_SeqScan: + state = ((SeqScan *)node)->scanstate; + break; + + case T_IndexScan: + state = ((IndexScan *)node)->scan.scanstate; + break; + + case T_Material: + state = &(((Material *)node)->matstate->csstate); + break; + + case T_Sort: + state = &(((Sort *)node)->sortstate->csstate); + break; + + case T_Agg: + state = &(((Agg *)node)->aggstate->csstate); + break; + + default: + elog(DEBUG, "ExecCloseR: not a scan, material, or sort node!"); + return; + } + + relation = state->css_currentRelation; + scanDesc = state->css_currentScanDesc; + + if (scanDesc != NULL) + heap_endscan(scanDesc); + + if (relation != NULL) + heap_close(relation); + + /* ---------------- + * if this is an index scan then we have to take care + * of the index relations as well.. + * ---------------- + */ + if (nodeTag(node) == T_IndexScan) { + IndexScan *iscan= (IndexScan *)node; + IndexScanState *indexstate; + int numIndices; + RelationPtr indexRelationDescs; + IndexScanDescPtr indexScanDescs; + int i; + + indexstate = iscan->indxstate; + numIndices = indexstate->iss_NumIndices; + indexRelationDescs = indexstate->iss_RelationDescs; + indexScanDescs = indexstate->iss_ScanDescs; + + for (i = 0; i<numIndices; i++) { + /* ---------------- + * shut down each of the scans and + * close each of the index relations + * ---------------- + */ + if (indexScanDescs[i] != NULL) + index_endscan(indexScanDescs[i]); + + if (indexRelationDescs[i] != NULL) + index_close(indexRelationDescs[i]); + } + } +} + +/* ---------------------------------------------------------------- + * ExecReScan + * + * XXX this should be extended to cope with all the node types.. + * + * takes the new expression context as an argument, so that + * index scans needn't have their scan keys updated separately + * - marcel 09/20/94 + * ---------------------------------------------------------------- + */ +void +ExecReScan(Plan *node, ExprContext *exprCtxt, Plan *parent) +{ + switch(nodeTag(node)) { + case T_SeqScan: + ExecSeqReScan((SeqScan *) node, exprCtxt, parent); + return; + + case T_IndexScan: + ExecIndexReScan((IndexScan *) node, exprCtxt, parent); + return; + + case T_Material: + /* the first call to ExecReScan should have no effect because + * everything is initialized properly already. the following + * calls will be handled by ExecSeqReScan() because the nodes + * below the Material node have already been materialized into + * a temp relation. + */ + return; + + case T_Tee: + ExecTeeReScan((Tee*) node, exprCtxt, parent); + break; + + default: + elog(WARN, "ExecReScan: not a seqscan or indexscan node."); + return; + } +} + +/* ---------------------------------------------------------------- + * ExecReScanR + * + * XXX this does not do the right thing with indices yet. + * ---------------------------------------------------------------- + */ +HeapScanDesc +ExecReScanR(Relation relDesc, /* LLL relDesc unused */ + HeapScanDesc scanDesc, + ScanDirection direction, + int nkeys, /* LLL nkeys unused */ + ScanKey skeys) +{ + if (scanDesc != NULL) + heap_rescan(scanDesc, /* scan desc */ + ScanDirectionIsBackward(direction), /* backward flag */ + skeys); /* scan keys */ + + return scanDesc; +} + +/* ---------------------------------------------------------------- + * ExecMarkPos + * + * Marks the current scan position. + * + * XXX Needs to be extended to include all the node types. + * ---------------------------------------------------------------- + */ +void +ExecMarkPos(Plan *node) +{ + switch(nodeTag(node)) { + case T_SeqScan: + ExecSeqMarkPos((SeqScan *) node); + break; + + case T_IndexScan: + ExecIndexMarkPos((IndexScan *) node); + break; + + case T_Sort: + ExecSortMarkPos((Sort *) node); + break; + + default: + /* elog(DEBUG, "ExecMarkPos: unsupported node type"); */ + break; + } + return; +} + +/* ---------------------------------------------------------------- + * ExecRestrPos + * + * restores the scan position previously saved with ExecMarkPos() + * ---------------------------------------------------------------- + */ +void +ExecRestrPos(Plan *node) +{ + switch(nodeTag(node)) { + case T_SeqScan: + ExecSeqRestrPos((SeqScan *) node); + return; + + case T_IndexScan: + ExecIndexRestrPos((IndexScan *) node); + return; + + case T_Sort: + ExecSortRestrPos((Sort *) node); + return; + + default: + /* elog(DEBUG, "ExecRestrPos: node type not supported"); */ + return; + } +} + +/* ---------------------------------------------------------------- + * ExecCreatR + * + * old comments + * Creates a relation. + * + * Parameters: + * attrType -- type information on the attributes. + * accessMtd -- access methods used to access the created relation. + * relation -- optional. Either an index to the range table or + * negative number indicating a temporary relation. + * A temporary relation is assume is this field is absent. + * ---------------------------------------------------------------- + */ + +Relation +ExecCreatR(TupleDesc tupType, + Oid relationOid) +{ + Relation relDesc; + + EU4_printf("ExecCreatR: %s numatts=%d type=%d oid=%d\n", + "entering: ", numberAttributes, tupType, relationOid); + CXT1_printf("ExecCreatR: context is %d\n", CurrentMemoryContext); + + relDesc = NULL; + + if (relationOid == _TEMP_RELATION_ID_ ) { + /* ---------------- + * create a temporary relation + * (currently the planner always puts a _TEMP_RELATION_ID + * in the relation argument so we expect this to be the case although + * it's possible that someday we'll get the name from + * from the range table.. -cim 10/12/89) + * ---------------- + */ +/* + sprintf(tempname, "temp_%d.%d", getpid(), tmpcnt++); + EU1_printf("ExecCreatR: attempting to create %s\n", tempname); +*/ + /* heap_creatr creates a name if the argument to heap_creatr is '\0 ' */ + relDesc = heap_creatr("", + DEFAULT_SMGR, + tupType); + } else { + /* ---------------- + * use a relation from the range table + * ---------------- + */ + elog(DEBUG, "ExecCreatR: %s", + "stuff using range table id's is not functional"); + } + + if (relDesc == NULL) + elog(DEBUG, "ExecCreatR: failed to create relation."); + + EU1_printf("ExecCreatR: returning relDesc=%d\n", relDesc); + + return relDesc; +} + diff --git a/src/backend/executor/execFlatten.c b/src/backend/executor/execFlatten.c new file mode 100644 index 00000000000..646c571506e --- /dev/null +++ b/src/backend/executor/execFlatten.c @@ -0,0 +1,236 @@ +/*------------------------------------------------------------------------- + * + * execFlatten.c-- + * This file handles the nodes associated with flattening sets in the + * target list of queries containing functions returning sets. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/Attic/execFlatten.c,v 1.1.1.1 1996/07/09 06:21:24 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +/* + * ExecEvalIter() - + * Iterate through the all return tuples/base types from a function one + * at time (i.e. one per ExecEvalIter call). Not really needed for + * postquel functions, but for reasons of orthogonality, these nodes + * exist above pq functions as well as c functions. + * + * ExecEvalFjoin() - + * Given N Iter nodes return a vector of all combinations of results + * one at a time (i.e. one result vector per ExecEvalFjoin call). This + * node does the actual flattening work. + */ +#include "postgres.h" +#include "nodes/primnodes.h" +#include "nodes/relation.h" +#include "nodes/execnodes.h" +#include "executor/executor.h" +#include "executor/execFlatten.h" + +Datum +ExecEvalIter(Iter *iterNode, + ExprContext *econtext, + bool *resultIsNull, + bool *iterIsDone) +{ + Node *expression; + + expression = iterNode->iterexpr; + + /* + * Really Iter nodes are only needed for C functions, postquel function + * by their nature return 1 result at a time. For now we are only worrying + * about postquel functions, c functions will come later. + */ + return ExecEvalExpr(expression, econtext, resultIsNull, iterIsDone); +} + +void +ExecEvalFjoin(TargetEntry *tlist, + ExprContext *econtext, + bool *isNullVect, + bool *fj_isDone) +{ + +#ifdef SETS_FIXED + bool isDone; + int curNode; + List *tlistP; + + Fjoin *fjNode = tlist->fjoin; + DatumPtr resVect = fjNode->fj_results; + BoolPtr alwaysDone = fjNode->fj_alwaysDone; + + if (fj_isDone) *fj_isDone = false; + /* + * For the next tuple produced by the plan, we need to re-initialize + * the Fjoin node. + */ + if (!fjNode->fj_initialized) + { + /* + * Initialize all of the Outer nodes + */ + curNode = 1; + foreach(tlistP, lnext(tlist)) + { + TargetEntry *tle = lfirst(tlistP); + + resVect[curNode] = ExecEvalIter((Iter*)tle->expr, + econtext, + &isNullVect[curNode], + &isDone); + if (isDone) + isNullVect[curNode] = alwaysDone[curNode] = true; + else + alwaysDone[curNode] = false; + + curNode++; + } + + /* + * Initialize the inner node + */ + resVect[0] = ExecEvalIter((Iter*)fjNode->fj_innerNode->expr, + econtext, + &isNullVect[0], + &isDone); + if (isDone) + isNullVect[0] = alwaysDone[0] = true; + else + alwaysDone[0] = false; + + /* + * Mark the Fjoin as initialized now. + */ + fjNode->fj_initialized = TRUE; + + /* + * If the inner node is always done, then we are done for now + */ + if (isDone) + return; + } + else + { + /* + * If we're already initialized, all we need to do is get the + * next inner result and pair it up with the existing outer node + * result vector. Watch out for the degenerate case, where the + * inner node never returns results. + */ + + /* + * Fill in nulls for every function that is always done. + */ + for (curNode=fjNode->fj_nNodes-1; curNode >= 0; curNode--) + isNullVect[curNode] = alwaysDone[curNode]; + + if (alwaysDone[0] == true) + { + *fj_isDone = FjoinBumpOuterNodes(tlist, + econtext, + resVect, + isNullVect); + return; + } + else + resVect[0] = ExecEvalIter((Iter*)fjNode->fj_innerNode->expr, + econtext, + &isNullVect[0], + &isDone); + } + + /* + * if the inner node is done + */ + if (isDone) + { + *fj_isDone = FjoinBumpOuterNodes(tlist, + econtext, + resVect, + isNullVect); + if (*fj_isDone) + return; + + resVect[0] = ExecEvalIter((Iter*)fjNode->fj_innerNode->expr, + econtext, + &isNullVect[0], + &isDone); + + } +#endif + return; +} + +bool +FjoinBumpOuterNodes(TargetEntry *tlist, + ExprContext *econtext, + DatumPtr results, + char *nulls) +{ +#ifdef SETS_FIXED + bool funcIsDone = true; + Fjoin *fjNode = tlist->fjoin; + char *alwaysDone = fjNode->fj_alwaysDone; + List *outerList = lnext(tlist); + List *trailers = lnext(tlist); + int trailNode = 1; + int curNode = 1; + + /* + * Run through list of functions until we get to one that isn't yet + * done returning values. Watch out for funcs that are always done. + */ + while ((funcIsDone == true) && (outerList != NIL)) + { + TargetEntry *tle = lfirst(outerList); + + if (alwaysDone[curNode] == true) + nulls[curNode] = 'n'; + else + results[curNode] = ExecEvalIter((Iter)tle->expr, + econtext, + &nulls[curNode], + &funcIsDone); + curNode++; + outerList = lnext(outerList); + } + + /* + * If every function is done, then we are done flattening. + * Mark the Fjoin node unitialized, it is time to get the + * next tuple from the plan and redo all of the flattening. + */ + if (funcIsDone) + { + set_fj_initialized(fjNode, false); + return (true); + } + + /* + * We found a function that wasn't done. Now re-run every function + * before it. As usual watch out for functions that are always done. + */ + trailNode = 1; + while (trailNode != curNode-1) + { + TargetEntry *tle = lfirst(trailers); + + if (alwaysDone[trailNode] != true) + results[trailNode] = ExecEvalIter((Iter)tle->expr, + econtext, + &nulls[trailNode], + &funcIsDone); + trailNode++; + trailers = lnext(trailers); + } + return false; +#endif + return false; +} diff --git a/src/backend/executor/execFlatten.h b/src/backend/executor/execFlatten.h new file mode 100644 index 00000000000..fe06823619f --- /dev/null +++ b/src/backend/executor/execFlatten.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * execFlatten.h-- + * prototypes for execFlatten.c. + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: execFlatten.h,v 1.1.1.1 1996/07/09 06:21:24 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef EXECFLATTEN_H +#define EXECFLATTEN_H + +extern Datum ExecEvalIter(Iter *iterNode, ExprContext *econtext, bool *resultIsNull, bool *iterIsDone); + +extern void ExecEvalFjoin(TargetEntry *tlist, ExprContext *econtext, bool *isNullVect, bool *fj_isDone); + +extern bool FjoinBumpOuterNodes(TargetEntry *tlist, ExprContext *econtext, DatumPtr results, char *nulls); + + +#endif /* EXECFLATTEN_H */ + + + diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c new file mode 100644 index 00000000000..7ee1543299a --- /dev/null +++ b/src/backend/executor/execJunk.c @@ -0,0 +1,389 @@ +/*------------------------------------------------------------------------- + * + * junk.c-- + * Junk attribute support stuff.... + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/execJunk.c,v 1.1.1.1 1996/07/09 06:21:24 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "utils/palloc.h" +#include "executor/executor.h" +#include "nodes/relation.h" +#include "optimizer/tlist.h" /* for MakeTLE */ + +/*------------------------------------------------------------------------- + * XXX this stuff should be rewritten to take advantage + * of ExecProject() and the ProjectionInfo node. + * -cim 6/3/91 + * + * An attribute of a tuple living inside the executor, can be + * either a normal attribute or a "junk" attribute. "junk" attributes + * never make it out of the executor, i.e. they are never printed, + * returned or stored in disk. Their only purpose in life is to + * store some information useful only to the executor, mainly the values + * of some system attributes like "ctid" or rule locks. + * + * The general idea is the following: A target list consists of a list of + * Resdom nodes & expression pairs. Each Resdom node has an attribute + * called 'resjunk'. If the value of this attribute is 1 then the + * corresponding attribute is a "junk" attribute. + * + * When we initialize a plan we call 'ExecInitJunkFilter' to create + * and store the appropriate information in the 'es_junkFilter' attribute of + * EState. + * + * We then execute the plan ignoring the "resjunk" attributes. + * + * Finally, when at the top level we get back a tuple, we can call + * 'ExecGetJunkAttribute' to retrieve the value of the junk attributes we + * are interested in, and 'ExecRemoveJunk' to remove all the junk attributes + * from a tuple. This new "clean" tuple is then printed, replaced, deleted + * or inserted. + * + *------------------------------------------------------------------------- + */ + +/*------------------------------------------------------------------------- + * ExecInitJunkFilter + * + * Initialize the Junk filter. + *------------------------------------------------------------------------- + */ +JunkFilter * +ExecInitJunkFilter(List *targetList) +{ + JunkFilter *junkfilter; + List *cleanTargetList; + int len, cleanLength; + TupleDesc tupType, cleanTupType; + List *t; + TargetEntry *tle; + Resdom *resdom, *cleanResdom; + int resjunk; + AttrNumber cleanResno; + AttrNumber *cleanMap; + Size size; + Node *expr; + + /* --------------------- + * First find the "clean" target list, i.e. all the entries + * in the original target list which have a zero 'resjunk' + * NOTE: make copy of the Resdom nodes, because we have + * to change the 'resno's... + * --------------------- + */ + cleanTargetList = NIL; + cleanResno = 1; + + foreach (t, targetList) { + TargetEntry *rtarget = lfirst(t); + if (rtarget->resdom != NULL) { + resdom = rtarget->resdom; + expr = rtarget->expr; + resjunk = resdom->resjunk; + if (resjunk == 0) { + /* + * make a copy of the resdom node, changing its resno. + */ + cleanResdom = (Resdom *) copyObject(resdom); + cleanResdom->resno = cleanResno; + cleanResno ++; + /* + * create a new target list entry + */ + tle = makeNode(TargetEntry); + tle->resdom = cleanResdom; + tle->expr = expr; + cleanTargetList = lappend(cleanTargetList, tle); + } + } + else { +#ifdef SETS_FIXED + List *fjListP; + Fjoin *cleanFjoin; + List *cleanFjList; + List *fjList = lfirst(t); + Fjoin *fjNode = (Fjoin *)tl_node(fjList); + + cleanFjoin = (Fjoin)copyObject((Node) fjNode); + cleanFjList = lcons(cleanFjoin, NIL); + + resdom = (Resdom) lfirst(get_fj_innerNode(fjNode)); + expr = lsecond(get_fj_innerNode(fjNode)); + cleanResdom = (Resdom) copyObject((Node) resdom); + set_resno(cleanResdom, cleanResno); + cleanResno++; + tle = (List) MakeTLE(cleanResdom, (Expr) expr); + set_fj_innerNode(cleanFjoin, tle); + + foreach(fjListP, lnext(fjList)) { + TargetEntry *tle = lfirst(fjListP); + + resdom = tle->resdom; + expr = tle->expr; + cleanResdom = (Resdom*) copyObject((Node) resdom); + cleanResno++; + cleanResdom->Resno = cleanResno; + /* + * create a new target list entry + */ + tle = (List) MakeTLE(cleanResdom, (Expr) expr); + cleanFjList = lappend(cleanFjList, tle); + } + lappend(cleanTargetList, cleanFjList); +#endif + } + } + + /* --------------------- + * Now calculate the tuple types for the original and the clean tuple + * + * XXX ExecTypeFromTL should be used sparingly. Don't we already + * have the tupType corresponding to the targetlist we are passed? + * -cim 5/31/91 + * --------------------- + */ + tupType = (TupleDesc) ExecTypeFromTL(targetList); + cleanTupType = (TupleDesc) ExecTypeFromTL(cleanTargetList); + + len = ExecTargetListLength(targetList); + cleanLength = ExecTargetListLength(cleanTargetList); + + /* --------------------- + * Now calculate the "map" between the original tuples attributes + * and the "clean" tuple's attributes. + * + * The "map" is an array of "cleanLength" attribute numbers, i.e. + * one entry for every attribute of the "clean" tuple. + * The value of this entry is the attribute number of the corresponding + * attribute of the "original" tuple. + * --------------------- + */ + if (cleanLength > 0) { + size = cleanLength * sizeof(AttrNumber); + cleanMap = (AttrNumber*) palloc(size); + cleanResno = 1; + foreach (t, targetList) { + TargetEntry *tle = lfirst(t); + if (tle->resdom != NULL) { + resdom = tle->resdom; + expr = tle->expr; + resjunk = resdom->resjunk; + if (resjunk == 0) { + cleanMap[cleanResno-1] = resdom->resno; + cleanResno ++; + } + } else { +#ifdef SETS_FIXED + List fjListP; + List fjList = lfirst(t); + Fjoin fjNode = (Fjoin)lfirst(fjList); + + /* what the hell is this????? */ + resdom = (Resdom) lfirst(get_fj_innerNode(fjNode)); +#endif + + cleanMap[cleanResno-1] = tle->resdom->resno; + cleanResno++; + +#ifdef SETS_FIXED + foreach(fjListP, lnext(fjList)) { + TargetEntry *tle = lfirst(fjListP); + + resdom = tle->resdom; + cleanMap[cleanResno-1] = resdom->resno; + cleanResno++; + } +#endif + } + } + } else { + cleanMap = NULL; + } + + /* --------------------- + * Finally create and initialize the JunkFilter. + * --------------------- + */ + junkfilter = makeNode(JunkFilter); + + junkfilter->jf_targetList = targetList; + junkfilter->jf_length = len; + junkfilter->jf_tupType = tupType; + junkfilter->jf_cleanTargetList = cleanTargetList; + junkfilter->jf_cleanLength = cleanLength; + junkfilter->jf_cleanTupType = cleanTupType; + junkfilter->jf_cleanMap = cleanMap; + + return(junkfilter); + +} + +/*------------------------------------------------------------------------- + * ExecGetJunkAttribute + * + * Given a tuple (slot), the junk filter and a junk attribute's name, + * extract & return the value of this attribute. + * + * It returns false iff no junk attribute with such name was found. + * + * NOTE: isNull might be NULL ! + *------------------------------------------------------------------------- + */ +bool +ExecGetJunkAttribute(JunkFilter *junkfilter, + TupleTableSlot *slot, + char *attrName, + Datum *value, + bool *isNull) +{ + List *targetList; + List *t; + Resdom *resdom; + AttrNumber resno; + char *resname; + int resjunk; + TupleDesc tupType; + HeapTuple tuple; + + /* --------------------- + * first look in the junkfilter's target list for + * an attribute with the given name + * --------------------- + */ + resno = InvalidAttrNumber; + targetList = junkfilter->jf_targetList; + + foreach (t, targetList) { + TargetEntry *tle = lfirst(t); + resdom = tle->resdom; + resname = resdom->resname; + resjunk = resdom->resjunk; + if (resjunk != 0 && (strcmp(resname, attrName) == 0)) { + /* We found it ! */ + resno = resdom->resno; + break; + } + } + + if (resno == InvalidAttrNumber) { + /* Ooops! We couldn't find this attribute... */ + return(false); + } + + /* --------------------- + * Now extract the attribute value from the tuple. + * --------------------- + */ + tuple = slot->val; + tupType = (TupleDesc) junkfilter->jf_tupType; + + *value = (Datum) + heap_getattr(tuple, InvalidBuffer, resno, tupType, isNull); + + return true; +} + +/*------------------------------------------------------------------------- + * ExecRemoveJunk + * + * Construct and return a tuple with all the junk attributes removed. + *------------------------------------------------------------------------- + */ +HeapTuple +ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot) +{ + HeapTuple tuple; + HeapTuple cleanTuple; + AttrNumber *cleanMap; + TupleDesc cleanTupType; + TupleDesc tupType; + int cleanLength; + bool isNull; + int i; + Size size; + Datum *values; + char *nulls; + Datum values_array[64]; + char nulls_array[64]; + + /* ---------------- + * get info from the slot and the junk filter + * ---------------- + */ + tuple = slot->val; + + tupType = (TupleDesc) junkfilter->jf_tupType; + cleanTupType = (TupleDesc) junkfilter->jf_cleanTupType; + cleanLength = junkfilter->jf_cleanLength; + cleanMap = junkfilter->jf_cleanMap; + + /* --------------------- + * Handle the trivial case first. + * --------------------- + */ + if (cleanLength == 0) + return (HeapTuple) NULL; + + /* --------------------- + * Create the arrays that will hold the attribute values + * and the null information for the new "clean" tuple. + * + * Note: we use memory on the stack to optimize things when + * we are dealing with a small number of tuples. + * for large tuples we just use palloc. + * --------------------- + */ + if (cleanLength > 64) { + size = cleanLength * sizeof(Datum); + values = (Datum *) palloc(size); + + size = cleanLength * sizeof(char); + nulls = (char *) palloc(size); + } else { + values = values_array; + nulls = nulls_array; + } + + /* --------------------- + * Exctract one by one all the values of the "clean" tuple. + * --------------------- + */ + for (i=0; i<cleanLength; i++) { + Datum d = (Datum) + heap_getattr(tuple, InvalidBuffer, cleanMap[i], tupType, &isNull); + + values[i] = d; + + if (isNull) + nulls[i] = 'n'; + else + nulls[i] = ' '; + } + + /* --------------------- + * Now form the new tuple. + * --------------------- + */ + cleanTuple = heap_formtuple(cleanTupType, + values, + nulls); + + /* --------------------- + * We are done. Free any space allocated for 'values' and 'nulls' + * and return the new tuple. + * --------------------- + */ + if (cleanLength > 64) { + pfree(values); + pfree(nulls); + } + + return(cleanTuple); +} + diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c new file mode 100644 index 00000000000..07aac34accb --- /dev/null +++ b/src/backend/executor/execMain.c @@ -0,0 +1,1023 @@ +/*------------------------------------------------------------------------- + * + * execMain.c-- + * top level executor interface routines + * + * INTERFACE ROUTINES + * ExecutorStart() + * ExecutorRun() + * ExecutorEnd() + * + * The old ExecutorMain() has been replaced by ExecutorStart(), + * ExecutorRun() and ExecutorEnd() + * + * These three procedures are the external interfaces to the executor. + * In each case, the query descriptor and the execution state is required + * as arguments + * + * ExecutorStart() must be called at the beginning of any execution of any + * query plan and ExecutorEnd() should always be called at the end of + * execution of a plan. + * + * ExecutorRun accepts 'feature' and 'count' arguments that specify whether + * the plan is to be executed forwards, backwards, and for how many tuples. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include "executor/executor.h" +#include "utils/builtins.h" +#include "utils/palloc.h" +#include "utils/acl.h" +#include "parser/parsetree.h" /* rt_fetch() */ +#include "storage/bufmgr.h" +#include "commands/async.h" +/* #include "access/localam.h" */ +#include "optimizer/var.h" + + +/* decls for local routines only used within this module */ +static void ExecCheckPerms(CmdType operation, int resultRelation, List *rangeTable, + Query *parseTree); +static TupleDesc InitPlan(CmdType operation, Query *parseTree, + Plan *plan, EState *estate); +static void EndPlan(Plan *plan, EState *estate); +static TupleTableSlot *ExecutePlan(EState *estate, Plan *plan, + Query *parseTree, CmdType operation, + int numberTuples, int direction, + void (*printfunc)()); +static void ExecRetrieve(TupleTableSlot *slot, void (*printfunc)(), + Relation intoRelationDesc); +static void ExecAppend(TupleTableSlot *slot,ItemPointer tupleid, + EState *estate); +static void ExecDelete(TupleTableSlot *slot, ItemPointer tupleid, + EState *estate); +static void ExecReplace(TupleTableSlot *slot, ItemPointer tupleid, + EState *estate, Query *parseTree); + +/* end of local decls */ + +/* ---------------------------------------------------------------- + * ExecutorStart + * + * This routine must be called at the beginning of any execution of any + * query plan + * + * returns (AttrInfo*) which describes the attributes of the tuples to + * be returned by the query. + * + * ---------------------------------------------------------------- + */ +TupleDesc +ExecutorStart(QueryDesc *queryDesc, EState *estate) +{ + TupleDesc result; + + /* sanity checks */ + Assert(queryDesc!=NULL); + + result = InitPlan(queryDesc->operation, + queryDesc->parsetree, + queryDesc->plantree, + estate); + + /* reset buffer refcount. the current refcounts + * are saved and will be restored when ExecutorEnd is called + * + * this makes sure that when ExecutorRun's are + * called recursively as for postquel functions, + * the buffers pinned by one ExecutorRun will not be + * unpinned by another ExecutorRun. + */ + BufferRefCountReset(estate->es_refcount); + + return result; +} + +/* ---------------------------------------------------------------- + * ExecutorRun + * + * This is the main routine of the executor module. It accepts + * the query descriptor from the traffic cop and executes the + * query plan. + * + * ExecutorStart must have been called already. + * + * the different features supported are: + * EXEC_RUN: retrieve all tuples in the forward direction + * EXEC_FOR: retrieve 'count' number of tuples in the forward dir + * EXEC_BACK: retrieve 'count' number of tuples in the backward dir + * EXEC_RETONE: return one tuple but don't 'retrieve' it + * used in postquel function processing + * + * + * ---------------------------------------------------------------- + */ +TupleTableSlot* +ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, int count) +{ + CmdType operation; + Query *parseTree; + Plan *plan; + TupleTableSlot *result; + CommandDest dest; + void (*destination)(); + + /* ---------------- + * sanity checks + * ---------------- + */ + Assert(queryDesc!=NULL); + + /* ---------------- + * extract information from the query descriptor + * and the query feature. + * ---------------- + */ + operation = queryDesc->operation; + parseTree = queryDesc->parsetree; + plan = queryDesc->plantree; + dest = queryDesc->dest; + destination = (void (*)()) DestToFunction(dest); + + switch(feature) { + + case EXEC_RUN: + result = ExecutePlan(estate, + plan, + parseTree, + operation, + ALL_TUPLES, + EXEC_FRWD, + destination); + break; + case EXEC_FOR: + result = ExecutePlan(estate, + plan, + parseTree, + operation, + count, + EXEC_FRWD, + destination); + break; + + /* ---------------- + * retrieve next n "backward" tuples + * ---------------- + */ + case EXEC_BACK: + result = ExecutePlan(estate, + plan, + parseTree, + operation, + count, + EXEC_BKWD, + destination); + break; + + /* ---------------- + * return one tuple but don't "retrieve" it. + * (this is used by the rule manager..) -cim 9/14/89 + * ---------------- + */ + case EXEC_RETONE: + result = ExecutePlan(estate, + plan, + parseTree, + operation, + ONE_TUPLE, + EXEC_FRWD, + destination); + break; + default: + elog(DEBUG, "ExecutorRun: Unknown feature %d", feature); + break; + } + + return result; +} + +/* ---------------------------------------------------------------- + * ExecutorEnd + * + * This routine must be called at the end of any execution of any + * query plan + * + * returns (AttrInfo*) which describes the attributes of the tuples to + * be returned by the query. + * + * ---------------------------------------------------------------- + */ +void +ExecutorEnd(QueryDesc *queryDesc, EState *estate) +{ + /* sanity checks */ + Assert(queryDesc!=NULL); + + EndPlan(queryDesc->plantree, estate); + + /* restore saved refcounts. */ + BufferRefCountRestore(estate->es_refcount); +} + +/* =============================================================== + * =============================================================== + static routines follow + * =============================================================== + * =============================================================== + */ + +static void +ExecCheckPerms(CmdType operation, + int resultRelation, + List *rangeTable, + Query *parseTree) +{ + int i = 1; + Oid relid; + HeapTuple htp; + List *lp; + List *qvars, *tvars; + int32 ok = 1; + char *opstr; + NameData rname; + char *userName; + +#define CHECK(MODE) pg_aclcheck(rname.data, userName, MODE) + + userName = GetPgUserName(); + + foreach (lp, rangeTable) { + RangeTblEntry *rte = lfirst(lp); + + relid = rte->relid; + htp = SearchSysCacheTuple(RELOID, + ObjectIdGetDatum(relid), + 0,0,0); + if (!HeapTupleIsValid(htp)) + elog(WARN, "ExecCheckPerms: bogus RT relid: %d", + relid); + strncpy(rname.data, + ((Form_pg_class) GETSTRUCT(htp))->relname.data, + NAMEDATALEN); + if (i == resultRelation) { /* this is the result relation */ + qvars = pull_varnos(parseTree->qual); + tvars = pull_varnos((Node*)parseTree->targetList); + if (intMember(resultRelation, qvars) || + intMember(resultRelation, tvars)) { + /* result relation is scanned */ + ok = CHECK(ACL_RD); + opstr = "read"; + if (!ok) + break; + } + switch (operation) { + case CMD_INSERT: + ok = CHECK(ACL_AP) || + CHECK(ACL_WR); + opstr = "append"; + break; + case CMD_NOTIFY: /* what does this mean?? -- jw, 1/6/94 */ + case CMD_DELETE: + case CMD_UPDATE: + ok = CHECK(ACL_WR); + opstr = "write"; + break; + default: + elog(WARN, "ExecCheckPerms: bogus operation %d", + operation); + } + } else { + /* XXX NOTIFY?? */ + ok = CHECK(ACL_RD); + opstr = "read"; + } + if (!ok) + break; + ++i; + } + if (!ok) { +/* + elog(WARN, "%s on \"%-.*s\": permission denied", opstr, + NAMEDATALEN, rname.data); +*/ + elog(WARN, "%s %s", rname.data, ACL_NO_PRIV_WARNING); + } +} + + +/* ---------------------------------------------------------------- + * InitPlan + * + * Initializes the query plan: open files, allocate storage + * and start up the rule manager + * ---------------------------------------------------------------- + */ +static TupleDesc +InitPlan(CmdType operation, Query *parseTree, Plan *plan, EState *estate) +{ + List *rangeTable; + int resultRelation; + Relation intoRelationDesc; + + TupleDesc tupType; + List *targetList; + int len; + + /* ---------------- + * get information from query descriptor + * ---------------- + */ + rangeTable = parseTree->rtable; + resultRelation = parseTree->resultRelation; + + /* ---------------- + * initialize the node's execution state + * ---------------- + */ + estate->es_range_table = rangeTable; + + /* ---------------- + * initialize the BaseId counter so node base_id's + * are assigned correctly. Someday baseid's will have to + * be stored someplace other than estate because they + * should be unique per query planned. + * ---------------- + */ + estate->es_BaseId = 1; + + /* ---------------- + * initialize result relation stuff + * ---------------- + */ + + if (resultRelation != 0 && operation != CMD_SELECT) { + /* ---------------- + * if we have a result relation, open it and + * initialize the result relation info stuff. + * ---------------- + */ + RelationInfo *resultRelationInfo; + Index resultRelationIndex; + RangeTblEntry *rtentry; + Oid resultRelationOid; + Relation resultRelationDesc; + + resultRelationIndex = resultRelation; + rtentry = rt_fetch(resultRelationIndex, rangeTable); + resultRelationOid = rtentry->relid; + resultRelationDesc = heap_open(resultRelationOid); + + /* Write-lock the result relation right away: if the relation + is used in a subsequent scan, we won't have to elevate the + read-lock set by heap_beginscan to a write-lock (needed by + heap_insert, heap_delete and heap_replace). + This will hopefully prevent some deadlocks. - 01/24/94 */ + RelationSetLockForWrite(resultRelationDesc); + + resultRelationInfo = makeNode(RelationInfo); + resultRelationInfo->ri_RangeTableIndex = resultRelationIndex; + resultRelationInfo->ri_RelationDesc = resultRelationDesc; + resultRelationInfo->ri_NumIndices = 0; + resultRelationInfo->ri_IndexRelationDescs = NULL; + resultRelationInfo->ri_IndexRelationInfo = NULL; + + /* ---------------- + * open indices on result relation and save descriptors + * in the result relation information.. + * ---------------- + */ + ExecOpenIndices(resultRelationOid, resultRelationInfo); + + estate->es_result_relation_info = resultRelationInfo; + } else { + /* ---------------- + * if no result relation, then set state appropriately + * ---------------- + */ + estate->es_result_relation_info = NULL; + } + +#ifndef NO_SECURITY + ExecCheckPerms(operation, resultRelation, rangeTable, parseTree); +#endif + + /* ---------------- + * initialize the executor "tuple" table. + * ---------------- + */ + { + int nSlots = ExecCountSlotsNode(plan); + TupleTable tupleTable = ExecCreateTupleTable(nSlots+10); /* why add ten? - jolly */ + + estate->es_tupleTable = tupleTable; + } + + /* ---------------- + * initialize the private state information for + * all the nodes in the query tree. This opens + * files, allocates storage and leaves us ready + * to start processing tuples.. + * ---------------- + */ + ExecInitNode(plan, estate, NULL); + + /* ---------------- + * get the tuple descriptor describing the type + * of tuples to return.. (this is especially important + * if we are creating a relation with "retrieve into") + * ---------------- + */ + tupType = ExecGetTupType(plan); /* tuple descriptor */ + targetList = plan->targetlist; + len = ExecTargetListLength(targetList); /* number of attributes */ + + /* ---------------- + * now that we have the target list, initialize the junk filter + * if this is a REPLACE or a DELETE query. + * We also init the junk filter if this is an append query + * (there might be some rule lock info there...) + * NOTE: in the future we might want to initialize the junk + * filter for all queries. + * ---------------- + */ + if (operation == CMD_UPDATE || operation == CMD_DELETE || + operation == CMD_INSERT) { + + JunkFilter *j = (JunkFilter*) ExecInitJunkFilter(targetList); + estate->es_junkFilter = j; + } else + estate->es_junkFilter = NULL; + + /* ---------------- + * initialize the "into" relation + * ---------------- + */ + intoRelationDesc = (Relation) NULL; + + if (operation == CMD_SELECT) { + char *intoName; + char archiveMode; + Oid intoRelationId; + + if (!parseTree->isPortal) { + /* + * a select into table + */ + if (parseTree->into != NULL) { + /* ---------------- + * create the "into" relation + * + * note: there is currently no way for the user to + * specify the desired archive mode of the + * "into" relation... + * ---------------- + */ + intoName = parseTree->into; + archiveMode = 'n'; + + intoRelationId = heap_create(intoName, + intoName, /* not used */ + archiveMode, + DEFAULT_SMGR, + tupType); + + /* ---------------- + * XXX rather than having to call setheapoverride(true) + * and then back to false, we should change the + * arguments to heap_open() instead.. + * ---------------- + */ + setheapoverride(true); + + intoRelationDesc = heap_open(intoRelationId); + + setheapoverride(false); + } + } + } + + estate->es_into_relation_descriptor = intoRelationDesc; + + /* ---------------- + * return the type information.. + * ---------------- + */ +/* + attinfo = (AttrInfo *)palloc(sizeof(AttrInfo)); + attinfo->numAttr = len; + attinfo->attrs = tupType->attrs; +*/ + + return tupType; +} + +/* ---------------------------------------------------------------- + * EndPlan + * + * Cleans up the query plan -- closes files and free up storages + * ---------------------------------------------------------------- + */ +static void +EndPlan(Plan *plan, EState *estate) +{ + RelationInfo *resultRelationInfo; + Relation intoRelationDesc; + + /* ---------------- + * get information from state + * ---------------- + */ + resultRelationInfo = estate->es_result_relation_info; + intoRelationDesc = estate->es_into_relation_descriptor; + + /* ---------------- + * shut down the query + * ---------------- + */ + ExecEndNode(plan, plan); + + /* ---------------- + * destroy the executor "tuple" table. + * ---------------- + */ + { + TupleTable tupleTable = (TupleTable) estate->es_tupleTable; + ExecDestroyTupleTable(tupleTable,true); /* was missing last arg */ + estate->es_tupleTable = NULL; + } + + /* ---------------- + * close the result relations if necessary + * ---------------- + */ + if (resultRelationInfo != NULL) { + Relation resultRelationDesc; + + resultRelationDesc = resultRelationInfo->ri_RelationDesc; + heap_close(resultRelationDesc); + + /* ---------------- + * close indices on the result relation + * ---------------- + */ + ExecCloseIndices(resultRelationInfo); + } + + /* ---------------- + * close the "into" relation if necessary + * ---------------- + */ + if (intoRelationDesc != NULL) { + heap_close(intoRelationDesc); + } +} + +/* ---------------------------------------------------------------- + * ExecutePlan + * + * processes the query plan to retrieve 'tupleCount' tuples in the + * direction specified. + * Retrieves all tuples if tupleCount is 0 + * + * result is either a slot containing a tuple in the case + * of a RETRIEVE or NULL otherwise. + * + * ---------------------------------------------------------------- + */ + +/* the ctid attribute is a 'junk' attribute that is removed before the + user can see it*/ + +static TupleTableSlot * +ExecutePlan(EState *estate, + Plan *plan, + Query *parseTree, + CmdType operation, + int numberTuples, + int direction, + void (*printfunc)()) +{ + Relation intoRelationDesc; + JunkFilter *junkfilter; + + TupleTableSlot *slot; + ItemPointer tupleid = NULL; + ItemPointerData tuple_ctid; + int current_tuple_count; + TupleTableSlot *result; + + /* ---------------- + * get information + * ---------------- + */ + intoRelationDesc = estate->es_into_relation_descriptor; + + /* ---------------- + * initialize local variables + * ---------------- + */ + slot = NULL; + current_tuple_count = 0; + result = NULL; + + /* ---------------- + * Set the direction. + * ---------------- + */ + estate->es_direction = direction; + + /* ---------------- + * Loop until we've processed the proper number + * of tuples from the plan.. + * ---------------- + */ + + for(;;) { + if (operation != CMD_NOTIFY) { + /* ---------------- + * Execute the plan and obtain a tuple + * ---------------- + */ + /* at the top level, the parent of a plan (2nd arg) is itself */ + slot = ExecProcNode(plan,plan); + + /* ---------------- + * if the tuple is null, then we assume + * there is nothing more to process so + * we just return null... + * ---------------- + */ + if (TupIsNull(slot)) { + result = NULL; + break; + } + } + + /* ---------------- + * if we have a junk filter, then project a new + * tuple with the junk removed. + * + * Store this new "clean" tuple in the place of the + * original tuple. + * + * Also, extract all the junk ifnormation we need. + * ---------------- + */ + if ((junkfilter = estate->es_junkFilter) != (JunkFilter*)NULL) { + Datum datum; +/* NameData attrName; */ + HeapTuple newTuple; + bool isNull; + + /* --------------- + * extract the 'ctid' junk attribute. + * --------------- + */ + if (operation == CMD_UPDATE || operation == CMD_DELETE) { + if (! ExecGetJunkAttribute(junkfilter, + slot, + "ctid", + &datum, + &isNull)) + elog(WARN,"ExecutePlan: NO (junk) `ctid' was found!"); + + if (isNull) + elog(WARN,"ExecutePlan: (junk) `ctid' is NULL!"); + + tupleid = (ItemPointer) DatumGetPointer(datum); + tuple_ctid = *tupleid; /* make sure we don't free the ctid!! */ + tupleid = &tuple_ctid; + } + + /* --------------- + * Finally create a new "clean" tuple with all junk attributes + * removed + * --------------- + */ + newTuple = ExecRemoveJunk(junkfilter, slot); + + slot = ExecStoreTuple(newTuple, /* tuple to store */ + slot, /* destination slot */ + InvalidBuffer,/* this tuple has no buffer */ + true); /* tuple should be pfreed */ + } /* if (junkfilter... */ + + /* ---------------- + * now that we have a tuple, do the appropriate thing + * with it.. either return it to the user, add + * it to a relation someplace, delete it from a + * relation, or modify some of it's attributes. + * ---------------- + */ + + switch(operation) { + case CMD_SELECT: + ExecRetrieve(slot, /* slot containing tuple */ + printfunc, /* print function */ + intoRelationDesc); /* "into" relation */ + result = slot; + break; + + case CMD_INSERT: + ExecAppend(slot, tupleid, estate); + result = NULL; + break; + + case CMD_DELETE: + ExecDelete(slot, tupleid, estate); + result = NULL; + break; + + case CMD_UPDATE: + ExecReplace(slot, tupleid, estate, parseTree); + result = NULL; + break; + + /* Total hack. I'm ignoring any accessor functions for + Relation, RelationTupleForm, NameData. + Assuming that NameData.data has offset 0. + */ + case CMD_NOTIFY: { + RelationInfo *rInfo = estate->es_result_relation_info; + Relation rDesc = rInfo->ri_RelationDesc; + Async_Notify(rDesc->rd_rel->relname.data); + result = NULL; + current_tuple_count = 0; + numberTuples = 1; + elog(DEBUG, "ExecNotify %s",&rDesc->rd_rel->relname); + } + break; + + default: + elog(DEBUG, "ExecutePlan: unknown operation in queryDesc"); + result = NULL; + break; + } + /* ---------------- + * check our tuple count.. if we've returned the + * proper number then return, else loop again and + * process more tuples.. + * ---------------- + */ + current_tuple_count += 1; + if (numberTuples == current_tuple_count) + break; + } + + /* ---------------- + * here, result is either a slot containing a tuple in the case + * of a RETRIEVE or NULL otherwise. + * ---------------- + */ + return result; +} + +/* ---------------------------------------------------------------- + * ExecRetrieve + * + * RETRIEVEs are easy.. we just pass the tuple to the appropriate + * print function. The only complexity is when we do a + * "retrieve into", in which case we insert the tuple into + * the appropriate relation (note: this is a newly created relation + * so we don't need to worry about indices or locks.) + * ---------------------------------------------------------------- + */ +static void +ExecRetrieve(TupleTableSlot *slot, + void (*printfunc)(), + Relation intoRelationDesc) +{ + HeapTuple tuple; + TupleDesc attrtype; + + /* ---------------- + * get the heap tuple out of the tuple table slot + * ---------------- + */ + tuple = slot->val; + attrtype = slot->ttc_tupleDescriptor; + + /* ---------------- + * insert the tuple into the "into relation" + * ---------------- + */ + if (intoRelationDesc != NULL) { + heap_insert (intoRelationDesc, tuple); + IncrAppended(); + } + + /* ---------------- + * send the tuple to the front end (or the screen) + * ---------------- + */ + (*printfunc)(tuple, attrtype); + IncrRetrieved(); +} + +/* ---------------------------------------------------------------- + * ExecAppend + * + * APPENDs are trickier.. we have to insert the tuple into + * the base relation and insert appropriate tuples into the + * index relations. + * ---------------------------------------------------------------- + */ + +static void +ExecAppend(TupleTableSlot *slot, + ItemPointer tupleid, + EState *estate) +{ + HeapTuple tuple; + RelationInfo *resultRelationInfo; + Relation resultRelationDesc; + int numIndices; + Oid newId; + + /* ---------------- + * get the heap tuple out of the tuple table slot + * ---------------- + */ + tuple = slot->val; + + /* ---------------- + * get information on the result relation + * ---------------- + */ + resultRelationInfo = estate->es_result_relation_info; + resultRelationDesc = resultRelationInfo->ri_RelationDesc; + + /* ---------------- + * have to add code to preform unique checking here. + * cim -12/1/89 + * ---------------- + */ + + /* ---------------- + * insert the tuple + * ---------------- + */ + newId = heap_insert(resultRelationDesc, /* relation desc */ + tuple); /* heap tuple */ + IncrAppended(); + UpdateAppendOid(newId); + + /* ---------------- + * process indices + * + * Note: heap_insert adds a new tuple to a relation. As a side + * effect, the tupleid of the new tuple is placed in the new + * tuple's t_ctid field. + * ---------------- + */ + numIndices = resultRelationInfo->ri_NumIndices; + if (numIndices > 0) { + ExecInsertIndexTuples(slot, &(tuple->t_ctid), estate); + } +} + +/* ---------------------------------------------------------------- + * ExecDelete + * + * DELETE is like append, we delete the tuple and its + * index tuples. + * ---------------------------------------------------------------- + */ +static void +ExecDelete(TupleTableSlot *slot, + ItemPointer tupleid, + EState *estate) +{ + RelationInfo *resultRelationInfo; + Relation resultRelationDesc; + + /* ---------------- + * get the result relation information + * ---------------- + */ + resultRelationInfo = estate->es_result_relation_info; + resultRelationDesc = resultRelationInfo->ri_RelationDesc; + + /* ---------------- + * delete the tuple + * ---------------- + */ + (void) heap_delete(resultRelationDesc, /* relation desc */ + tupleid); /* item pointer to tuple */ + + IncrDeleted(); + + /* ---------------- + * Note: Normally one would think that we have to + * delete index tuples associated with the + * heap tuple now.. + * + * ... but in POSTGRES, we have no need to do this + * because the vacuum daemon automatically + * opens an index scan and deletes index tuples + * when it finds deleted heap tuples. -cim 9/27/89 + * ---------------- + */ + +} + +/* ---------------------------------------------------------------- + * ExecReplace + * + * note: we can't run replace queries with transactions + * off because replaces are actually appends and our + * scan will mistakenly loop forever, replacing the tuple + * it just appended.. This should be fixed but until it + * is, we don't want to get stuck in an infinite loop + * which corrupts your database.. + * ---------------------------------------------------------------- + */ +static void +ExecReplace(TupleTableSlot *slot, + ItemPointer tupleid, + EState *estate, + Query *parseTree) +{ + HeapTuple tuple; + RelationInfo *resultRelationInfo; + Relation resultRelationDesc; + int numIndices; + + /* ---------------- + * abort the operation if not running transactions + * ---------------- + */ + if (IsBootstrapProcessingMode()) { + elog(DEBUG, "ExecReplace: replace can't run without transactions"); + return; + } + + /* ---------------- + * get the heap tuple out of the tuple table slot + * ---------------- + */ + tuple = slot->val; + + /* ---------------- + * get the result relation information + * ---------------- + */ + resultRelationInfo = estate->es_result_relation_info; + resultRelationDesc = resultRelationInfo->ri_RelationDesc; + + /* ---------------- + * have to add code to preform unique checking here. + * in the event of unique tuples, this becomes a deletion + * of the original tuple affected by the replace. + * cim -12/1/89 + * ---------------- + */ + + /* ---------------- + * replace the heap tuple + * + * Don't want to continue if our heap_replace didn't actually + * do a replace. This would be the case if heap_replace + * detected a non-functional update. -kw 12/30/93 + * ---------------- + */ + if (heap_replace(resultRelationDesc, /* relation desc */ + tupleid, /* item ptr of tuple to replace */ + tuple)) { /* replacement heap tuple */ + return; + } + + IncrReplaced(); + + /* ---------------- + * Note: instead of having to update the old index tuples + * associated with the heap tuple, all we do is form + * and insert new index tuples.. This is because + * replaces are actually deletes and inserts and + * index tuple deletion is done automagically by + * the vaccuum deamon.. All we do is insert new + * index tuples. -cim 9/27/89 + * ---------------- + */ + + /* ---------------- + * process indices + * + * heap_replace updates a tuple in the base relation by invalidating + * it and then appending a new tuple to the relation. As a side + * effect, the tupleid of the new tuple is placed in the new + * tuple's t_ctid field. So we now insert index tuples using + * the new tupleid stored there. + * ---------------- + */ + numIndices = resultRelationInfo->ri_NumIndices; + if (numIndices > 0) { + ExecInsertIndexTuples(slot, &(tuple->t_ctid), estate); + } +} diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c new file mode 100644 index 00000000000..11a6f63a778 --- /dev/null +++ b/src/backend/executor/execProcnode.c @@ -0,0 +1,477 @@ +/*------------------------------------------------------------------------- + * + * execProcnode.c-- + * contains dispatch functions which call the appropriate "initialize", + * "get a tuple", and "cleanup" routines for the given node type. + * If the node has children, then it will presumably call ExecInitNode, + * ExecProcNode, or ExecEndNode on it's subnodes and do the appropriate + * processing.. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/execProcnode.c,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecInitNode - initialize a plan node and it's subplans + * ExecProcNode - get a tuple by executing the plan node + * ExecEndNode - shut down a plan node and it's subplans + * + * NOTES + * This used to be three files. It is now all combined into + * one file so that it is easier to keep ExecInitNode, ExecProcNode, + * and ExecEndNode in sync when new nodes are added. + * + * EXAMPLE + * suppose we want the age of the manager of the shoe department and + * the number of employees in that department. so we have the query: + * + * retrieve (DEPT.no_emps, EMP.age) + * where EMP.name = DEPT.mgr and + * DEPT.name = "shoe" + * + * Suppose the planner gives us the following plan: + * + * Nest Loop (DEPT.mgr = EMP.name) + * / \ + * / \ + * Seq Scan Seq Scan + * DEPT EMP + * (name = "shoe") + * + * ExecStart() is called first. + * It calls InitPlan() which calls ExecInitNode() on + * the root of the plan -- the nest loop node. + * + * * ExecInitNode() notices that it is looking at a nest loop and + * as the code below demonstrates, it calls ExecInitNestLoop(). + * Eventually this calls ExecInitNode() on the right and left subplans + * and so forth until the entire plan is initialized. + * + * * Then when ExecRun() is called, it calls ExecutePlan() which + * calls ExecProcNode() repeatedly on the top node of the plan. + * Each time this happens, ExecProcNode() will end up calling + * ExecNestLoop(), which calls ExecProcNode() on its subplans. + * Each of these subplans is a sequential scan so ExecSeqScan() is + * called. The slots returned by ExecSeqScan() may contain + * tuples which contain the attributes ExecNestLoop() uses to + * form the tuples it returns. + * + * * Eventually ExecSeqScan() stops returning tuples and the nest + * loop join ends. Lastly, ExecEnd() calls ExecEndNode() which + * calls ExecEndNestLoop() which in turn calls ExecEndNode() on + * its subplans which result in ExecEndSeqScan(). + * + * This should show how the executor works by having + * ExecInitNode(), ExecProcNode() and ExecEndNode() dispatch + * their work to the appopriate node support routines which may + * in turn call these routines themselves on their subplans. + * + */ +#include "executor/executor.h" +#include "executor/nodeResult.h" +#include "executor/nodeAppend.h" +#include "executor/nodeSeqscan.h" +#include "executor/nodeIndexscan.h" +#include "executor/nodeNestloop.h" +#include "executor/nodeMergejoin.h" +#include "executor/nodeMaterial.h" +#include "executor/nodeSort.h" +#include "executor/nodeUnique.h" +#include "executor/nodeGroup.h" +#include "executor/nodeAgg.h" +#include "executor/nodeHash.h" +#include "executor/nodeHashjoin.h" +#include "executor/nodeTee.h" + +/* ------------------------------------------------------------------------ + * ExecInitNode + * + * Recursively initializes all the nodes in the plan rooted + * at 'node'. + * + * Initial States: + * 'node' is the plan produced by the query planner + * + * returns TRUE/FALSE on whether the plan was successfully initialized + * ------------------------------------------------------------------------ + */ +bool +ExecInitNode(Plan *node, EState *estate, Plan *parent) +{ + bool result; + + /* ---------------- + * do nothing when we get to the end + * of a leaf on tree. + * ---------------- + */ + if (node == NULL) + return FALSE; + + switch(nodeTag(node)) { + /* ---------------- + * control nodes + * ---------------- + */ + case T_Result: + result = ExecInitResult((Result *)node, estate, parent); + break; + + case T_Append: + result = ExecInitAppend((Append *)node, estate, parent); + break; + + /* ---------------- + * scan nodes + * ---------------- + */ + case T_SeqScan: + result = ExecInitSeqScan((SeqScan *)node, estate, parent); + break; + + case T_IndexScan: + result = ExecInitIndexScan((IndexScan *)node, estate, parent); + break; + + /* ---------------- + * join nodes + * ---------------- + */ + case T_NestLoop: + result = ExecInitNestLoop((NestLoop *)node, estate, parent); + break; + + case T_MergeJoin: + result = ExecInitMergeJoin((MergeJoin *)node, estate, parent); + break; + + /* ---------------- + * materialization nodes + * ---------------- + */ + case T_Material: + result = ExecInitMaterial((Material *)node, estate, parent); + break; + + case T_Sort: + result = ExecInitSort((Sort *)node, estate, parent); + break; + + case T_Unique: + result = ExecInitUnique((Unique *)node, estate, parent); + break; + + case T_Group: + result = ExecInitGroup((Group *)node, estate, parent); + break; + + case T_Agg: + result = ExecInitAgg((Agg *)node, estate, parent); + break; + + case T_Hash: + result = ExecInitHash((Hash *)node, estate, parent); + break; + + case T_HashJoin: + result = ExecInitHashJoin((HashJoin *)node, estate, parent); + break; + + case T_Tee: + result = ExecInitTee((Tee*)node, estate, parent); + break; + + default: + elog(DEBUG, "ExecInitNode: node not yet supported: %d", + nodeTag(node)); + result = FALSE; + } + + return result; +} + + +/* ---------------------------------------------------------------- + * ExecProcNode + * + * Initial States: + * the query tree must be initialized once by calling ExecInit. + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecProcNode(Plan *node, Plan *parent) +{ + TupleTableSlot *result; + + /* ---------------- + * deal with NULL nodes.. + * ---------------- + */ + if (node == NULL) + return NULL; + + switch(nodeTag(node)) { + /* ---------------- + * control nodes + * ---------------- + */ + case T_Result: + result = ExecResult((Result *)node); + break; + + case T_Append: + result = ExecProcAppend((Append *)node); + break; + + /* ---------------- + * scan nodes + * ---------------- + */ + case T_SeqScan: + result = ExecSeqScan((SeqScan *)node); + break; + + case T_IndexScan: + result = ExecIndexScan((IndexScan *)node); + break; + + /* ---------------- + * join nodes + * ---------------- + */ + case T_NestLoop: + result = ExecNestLoop((NestLoop *)node, parent); + break; + + case T_MergeJoin: + result = ExecMergeJoin((MergeJoin *)node); + break; + + /* ---------------- + * materialization nodes + * ---------------- + */ + case T_Material: + result = ExecMaterial((Material *)node); + break; + + case T_Sort: + result = ExecSort((Sort *)node); + break; + + case T_Unique: + result = ExecUnique((Unique *)node); + break; + + case T_Group: + result = ExecGroup((Group *)node); + break; + + case T_Agg: + result = ExecAgg((Agg *)node); + break; + + case T_Hash: + result = ExecHash((Hash *)node); + break; + + case T_HashJoin: + result = ExecHashJoin((HashJoin *)node); + break; + + case T_Tee: + result = ExecTee((Tee*)node, parent); + break; + + default: + elog(DEBUG, "ExecProcNode: node not yet supported: %d", + nodeTag(node)); + result = FALSE; + } + + return result; +} + +int +ExecCountSlotsNode(Plan *node) +{ + if (node == (Plan *)NULL) + return 0; + + switch(nodeTag(node)) { + /* ---------------- + * control nodes + * ---------------- + */ + case T_Result: + return ExecCountSlotsResult((Result *)node); + + case T_Append: + return ExecCountSlotsAppend((Append *)node); + + /* ---------------- + * scan nodes + * ---------------- + */ + case T_SeqScan: + return ExecCountSlotsSeqScan((SeqScan *)node); + + case T_IndexScan: + return ExecCountSlotsIndexScan((IndexScan *)node); + + /* ---------------- + * join nodes + * ---------------- + */ + case T_NestLoop: + return ExecCountSlotsNestLoop((NestLoop *)node); + + case T_MergeJoin: + return ExecCountSlotsMergeJoin((MergeJoin *)node); + + /* ---------------- + * materialization nodes + * ---------------- + */ + case T_Material: + return ExecCountSlotsMaterial((Material *)node); + + case T_Sort: + return ExecCountSlotsSort((Sort *)node); + + case T_Unique: + return ExecCountSlotsUnique((Unique *)node); + + case T_Group: + return ExecCountSlotsGroup((Group *)node); + + case T_Agg: + return ExecCountSlotsAgg((Agg *)node); + + case T_Hash: + return ExecCountSlotsHash((Hash *)node); + + case T_HashJoin: + return ExecCountSlotsHashJoin((HashJoin *)node); + + case T_Tee: + return ExecCountSlotsTee((Tee*)node); + + default: + elog(WARN, "ExecCountSlotsNode: node not yet supported: %d", + nodeTag(node)); + break; + } + return 0; +} + +/* ---------------------------------------------------------------- + * ExecEndNode + * + * Recursively cleans up all the nodes in the plan rooted + * at 'node'. + * + * After this operation, the query plan will not be able to + * processed any further. This should be called only after + * the query plan has been fully executed. + * ---------------------------------------------------------------- + */ +void +ExecEndNode(Plan *node, Plan *parent) +{ + /* ---------------- + * do nothing when we get to the end + * of a leaf on tree. + * ---------------- + */ + if (node == NULL) + return; + + switch(nodeTag(node)) { + /* ---------------- + * control nodes + * ---------------- + */ + case T_Result: + ExecEndResult((Result *)node); + break; + + case T_Append: + ExecEndAppend((Append *)node); + break; + + /* ---------------- + * scan nodes + * ---------------- + */ + case T_SeqScan: + ExecEndSeqScan((SeqScan *)node); + break; + + case T_IndexScan: + ExecEndIndexScan((IndexScan *)node); + break; + + /* ---------------- + * join nodes + * ---------------- + */ + case T_NestLoop: + ExecEndNestLoop((NestLoop *)node); + break; + + case T_MergeJoin: + ExecEndMergeJoin((MergeJoin *)node); + break; + + /* ---------------- + * materialization nodes + * ---------------- + */ + case T_Material: + ExecEndMaterial((Material *)node); + break; + + case T_Sort: + ExecEndSort((Sort *)node); + break; + + case T_Unique: + ExecEndUnique((Unique *)node); + break; + + case T_Group: + ExecEndGroup((Group *)node); + break; + + case T_Agg: + ExecEndAgg((Agg *)node); + break; + + /* ---------------- + * XXX add hooks to these + * ---------------- + */ + case T_Hash: + ExecEndHash((Hash *) node); + break; + + case T_HashJoin: + ExecEndHashJoin((HashJoin *) node); + break; + + case T_Tee: + ExecEndTee((Tee*) node, parent); + break; + + default: + elog(DEBUG, "ExecEndNode: node not yet supported", + nodeTag(node)); + break; + } +} diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c new file mode 100644 index 00000000000..104c1f2f506 --- /dev/null +++ b/src/backend/executor/execQual.c @@ -0,0 +1,1504 @@ +/*------------------------------------------------------------------------- + * + * execQual.c-- + * Routines to evaluate qualification and targetlist expressions + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.1.1.1 1996/07/09 06:21:25 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecEvalExpr - evaluate an expression and return a datum + * ExecQual - return true/false if qualification is satisified + * ExecTargetList - form a new tuple by projecting the given tuple + * + * NOTES + * ExecEvalExpr() and ExecEvalVar() are hotspots. making these faster + * will speed up the entire system. Unfortunately they are currently + * implemented recursively.. Eliminating the recursion is bound to + * improve the speed of the executor. + * + * ExecTargetList() is used to make tuple projections. Rather then + * trying to speed it up, the execution plan should be pre-processed + * to facilitate attribute sharing between nodes wherever possible, + * instead of doing needless copying. -cim 5/31/91 + * + */ +#include "nodes/primnodes.h" +#include "nodes/relation.h" + +#include "optimizer/clauses.h" + +#include "nodes/memnodes.h" +#include "catalog/pg_language.h" +#include "executor/executor.h" +#include "executor/execFlatten.h" +#include "executor/functions.h" +#include "access/heapam.h" +#include "utils/memutils.h" +#include "utils/builtins.h" +#include "utils/palloc.h" +#include "utils/fcache.h" +#include "utils/fcache2.h" +#include "utils/array.h" + +/* ---------------- + * externs and constants + * ---------------- + */ + +/* + * XXX Used so we can get rid of use of Const nodes in the executor. + * Currently only used by ExecHashGetBucket and set only by ExecMakeVarConst + * and by ExecEvalArrayRef. + */ +bool execConstByVal; +int execConstLen; + +/* static functions decls */ +static Datum ExecEvalAggreg(Aggreg *agg, ExprContext *econtext, bool *isNull); +static Datum ExecEvalArrayRef(ArrayRef *arrayRef, ExprContext *econtext, + bool *isNull, bool *isDone); + +/* -------------------------------- + * ExecEvalArrayRef + * + * This function takes an ArrayRef and returns a Const Node if it + * is an array reference or returns the changed Array Node if it is + * an array assignment. + * + * -------------------------------- + */ +static Datum +ExecEvalArrayRef(ArrayRef *arrayRef, + ExprContext *econtext, + bool *isNull, + bool *isDone) +{ + bool dummy; + int i = 0, j = 0; + ArrayType *array_scanner; + List *upperIndexpr, *lowerIndexpr; + Node *assgnexpr; + List *elt; + IntArray upper, lower; + int *lIndex; + char *dataPtr; + + execConstByVal = arrayRef->refelembyval; + *isNull = false; + array_scanner = (ArrayType*)ExecEvalExpr(arrayRef->refexpr, + econtext, + isNull, + isDone); + if (*isNull) return (Datum)NULL; + + upperIndexpr = arrayRef->refupperindexpr; + + foreach (elt, upperIndexpr) { + upper.indx[i++] = (int32)ExecEvalExpr((Node*)lfirst(elt), + econtext, + isNull, + &dummy); + if (*isNull) return (Datum)NULL; + } + + lowerIndexpr = arrayRef->reflowerindexpr; + lIndex = NULL; + if (lowerIndexpr != NIL) { + foreach (elt, lowerIndexpr) { + lower.indx[j++] = (int32)ExecEvalExpr((Node*)lfirst(elt), + econtext, + isNull, + &dummy); + if (*isNull) return (Datum)NULL; + } + if (i != j) + elog(WARN, + "ExecEvalArrayRef: upper and lower indices mismatch"); + lIndex = lower.indx; + } + + assgnexpr = arrayRef->refassgnexpr; + if (assgnexpr != NULL) { + dataPtr = (char*)ExecEvalExpr((Node *) + assgnexpr, econtext, + isNull, &dummy); + if (*isNull) return (Datum)NULL; + if (lIndex == NULL) + return (Datum) array_set(array_scanner, i, upper.indx, dataPtr, + arrayRef->refelembyval, + arrayRef->refelemlength, + arrayRef->refattrlength, isNull); + return (Datum) array_assgn(array_scanner, i, upper.indx, + lower.indx, + (ArrayType*)dataPtr, + arrayRef->refelembyval, + arrayRef->refelemlength, isNull); + } + if (lIndex == NULL) + return (Datum) array_ref(array_scanner, i, upper.indx, + arrayRef->refelembyval, + arrayRef->refelemlength, + arrayRef->refattrlength, isNull); + return (Datum) array_clip(array_scanner, i, upper.indx, lower.indx, + arrayRef->refelembyval, + arrayRef->refelemlength, isNull); +} + + +/* ---------------------------------------------------------------- + * ExecEvalAggreg + * + * Returns a Datum whose value is the value of the precomputed + * aggregate found in the given expression context. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalAggreg(Aggreg *agg, ExprContext *econtext, bool *isNull) +{ + + *isNull = econtext->ecxt_nulls[agg->aggno]; + return econtext->ecxt_values[agg->aggno]; +} + +/* ---------------------------------------------------------------- + * ExecEvalVar + * + * Returns a Datum whose value is the value of a range + * variable with respect to given expression context. + * ---------------------------------------------------------------- + */ +Datum +ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull) +{ + Datum result; + TupleTableSlot *slot; + AttrNumber attnum; + HeapTuple heapTuple; + TupleDesc tuple_type; + Buffer buffer; + bool byval; + int16 len; + + /* ---------------- + * get the slot we want + * ---------------- + */ + switch(variable->varno) { + case INNER: /* get the tuple from the inner node */ + slot = econtext->ecxt_innertuple; + break; + + case OUTER: /* get the tuple from the outer node */ + slot = econtext->ecxt_outertuple; + break; + + default: /* get the tuple from the relation being scanned */ + slot = econtext->ecxt_scantuple; + break; + } + + /* ---------------- + * extract tuple information from the slot + * ---------------- + */ + heapTuple = slot->val; + tuple_type = slot->ttc_tupleDescriptor; + buffer = slot->ttc_buffer; + + attnum = variable->varattno; + + /* + * If the attribute number is invalid, then we are supposed to + * return the entire tuple, we give back a whole slot so that + * callers know what the tuple looks like. + */ + if (attnum == InvalidAttrNumber) + { + TupleTableSlot *tempSlot; + TupleDesc td; + HeapTuple tup; + + tempSlot = makeNode(TupleTableSlot); + tempSlot->ttc_shouldFree = false; + tempSlot->ttc_descIsNew = true; + tempSlot->ttc_tupleDescriptor = (TupleDesc)NULL, + tempSlot->ttc_buffer = InvalidBuffer; + tempSlot->ttc_whichplan = -1; + + tup = heap_copytuple(slot->val); + td = CreateTupleDescCopy(slot->ttc_tupleDescriptor); + + ExecSetSlotDescriptor(tempSlot, td); + + ExecStoreTuple(tup, tempSlot, InvalidBuffer, true); + return (Datum) tempSlot; + } + + result = (Datum) + heap_getattr(heapTuple, /* tuple containing attribute */ + buffer, /* buffer associated with tuple */ + attnum, /* attribute number of desired attribute */ + tuple_type, /* tuple descriptor of tuple */ + isNull); /* return: is attribute null? */ + + /* ---------------- + * return null if att is null + * ---------------- + */ + if (*isNull) + return (Datum) NULL; + + /* ---------------- + * get length and type information.. + * ??? what should we do about variable length attributes + * - variable length attributes have their length stored + * in the first 4 bytes of the memory pointed to by the + * returned value.. If we can determine that the type + * is a variable length type, we can do the right thing. + * -cim 9/15/89 + * ---------------- + */ + if (attnum < 0) { + /* ---------------- + * If this is a pseudo-att, we get the type and fake the length. + * There ought to be a routine to return the real lengths, so + * we'll mark this one ... XXX -mao + * ---------------- + */ + len = heap_sysattrlen(attnum); /* XXX see -mao above */ + byval = heap_sysattrbyval(attnum); /* XXX see -mao above */ + } else { + len = tuple_type->attrs[ attnum-1 ]->attlen; + byval = tuple_type->attrs[ attnum-1 ]->attbyval ? true : false ; + } + + execConstByVal = byval; + execConstLen = len; + + return result; +} + +/* ---------------------------------------------------------------- + * ExecEvalParam + * + * Returns the value of a parameter. A param node contains + * something like ($.name) and the expression context contains + * the current parameter bindings (name = "sam") (age = 34)... + * so our job is to replace the param node with the datum + * containing the appropriate information ("sam"). + * + * Q: if we have a parameter ($.foo) without a binding, i.e. + * there is no (foo = xxx) in the parameter list info, + * is this a fatal error or should this be a "not available" + * (in which case we shoud return a Const node with the + * isnull flag) ? -cim 10/13/89 + * + * Minor modification: Param nodes now have an extra field, + * `paramkind' which specifies the type of parameter + * (see params.h). So while searching the paramList for + * a paramname/value pair, we have also to check for `kind'. + * + * NOTE: The last entry in `paramList' is always an + * entry with kind == PARAM_INVALID. + * ---------------------------------------------------------------- + */ +Datum +ExecEvalParam(Param *expression, ExprContext *econtext, bool *isNull) +{ + + char *thisParameterName; + int thisParameterKind; + AttrNumber thisParameterId; + int matchFound; + ParamListInfo paramList; + + thisParameterName = expression->paramname; + thisParameterKind = expression->paramkind; + thisParameterId = expression->paramid; + paramList = econtext->ecxt_param_list_info; + + *isNull = false; + /* + * search the list with the parameter info to find a matching name. + * An entry with an InvalidName denotes the last element in the array. + */ + matchFound = 0; + if (paramList != NULL) { + /* + * search for an entry in 'paramList' that matches + * the `expression'. + */ + while(paramList->kind != PARAM_INVALID && !matchFound) { + switch (thisParameterKind) { + case PARAM_NAMED: + if (thisParameterKind == paramList->kind && + strcmp(paramList->name, thisParameterName) == 0){ + matchFound = 1; + } + break; + case PARAM_NUM: + if (thisParameterKind == paramList->kind && + paramList->id == thisParameterId) { + matchFound = 1; + } + break; + case PARAM_OLD: + case PARAM_NEW: + if (thisParameterKind == paramList->kind && + paramList->id == thisParameterId) + { + matchFound = 1; + /* + * sanity check + */ + if (strcmp(paramList->name, thisParameterName) != 0){ + elog(WARN, + "ExecEvalParam: new/old params with same id & diff names"); + } + } + break; + default: + /* + * oops! this is not supposed to happen! + */ + elog(WARN, "ExecEvalParam: invalid paramkind %d", + thisParameterKind); + } + if (! matchFound) { + paramList++; + } + } /*while*/ + } /*if*/ + + if (!matchFound) { + /* + * ooops! we couldn't find this parameter + * in the parameter list. Signal an error + */ + elog(WARN, "ExecEvalParam: Unknown value for parameter %s", + thisParameterName); + } + + /* + * return the value. + */ + if (paramList->isnull) + { + *isNull = true; + return (Datum)NULL; + } + + if (expression->param_tlist != NIL) + { + HeapTuple tup; + Datum value; + List *tlist = expression->param_tlist; + TargetEntry *tle = (TargetEntry *)lfirst(tlist); + TupleTableSlot *slot = (TupleTableSlot *)paramList->value; + + tup = slot->val; + value = ProjectAttribute(slot->ttc_tupleDescriptor, + tle, tup, isNull); + return value; + } + return(paramList->value); +} + + +/* ---------------------------------------------------------------- + * ExecEvalOper / ExecEvalFunc support routines + * ---------------------------------------------------------------- + */ + +/* ---------------- + * GetAttributeByName + * GetAttributeByNum + * + * These are functions which return the value of the + * named attribute out of the tuple from the arg slot. User defined + * C functions which take a tuple as an argument are expected + * to use this. Ex: overpaid(EMP) might call GetAttributeByNum(). + * ---------------- + */ +char * +GetAttributeByNum(TupleTableSlot *slot, + AttrNumber attrno, + bool *isNull) +{ + Datum retval; + + if (!AttributeNumberIsValid(attrno)) + elog(WARN, "GetAttributeByNum: Invalid attribute number"); + + if (!AttrNumberIsForUserDefinedAttr(attrno)) + elog(WARN, "GetAttributeByNum: cannot access system attributes here"); + + if (isNull == (bool *)NULL) + elog(WARN, "GetAttributeByNum: a NULL isNull flag was passed"); + + if (TupIsNull(slot)) + { + *isNull = true; + return (char *) NULL; + } + + retval = (Datum) + heap_getattr(slot->val, + slot->ttc_buffer, + attrno, + slot->ttc_tupleDescriptor, + isNull); + if (*isNull) + return (char *) NULL; + return (char *) retval; +} + +/* XXX char16 name for catalogs */ +char * +att_by_num(TupleTableSlot *slot, + AttrNumber attrno, + bool *isNull) +{ + return(GetAttributeByNum(slot, attrno, isNull)); +} + +char * +GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull) +{ + AttrNumber attrno; + TupleDesc tupdesc; + HeapTuple tuple; + Datum retval; + int natts; + int i; + + if (attname == NULL) + elog(WARN, "GetAttributeByName: Invalid attribute name"); + + if (isNull == (bool *)NULL) + elog(WARN, "GetAttributeByName: a NULL isNull flag was passed"); + + if (TupIsNull(slot)) + { + *isNull = true; + return (char *) NULL; + } + + tupdesc = slot->ttc_tupleDescriptor; + tuple = slot->val; + + natts = tuple->t_natts; + + attrno = InvalidAttrNumber; + for (i=0;i<tupdesc->natts;i++) { + if (namestrcmp(&(tupdesc->attrs[i]->attname), attname) == 0) { + attrno = tupdesc->attrs[i]->attnum; + break; + } + } + + if (attrno == InvalidAttrNumber) + elog(WARN, "GetAttributeByName: attribute %s not found", attname); + + retval = (Datum) + heap_getattr(slot->val, + slot->ttc_buffer, + attrno, + tupdesc, + isNull); + if (*isNull) + return (char *) NULL; + return (char *) retval; +} + +/* XXX char16 name for catalogs */ +char * +att_by_name(TupleTableSlot *slot, char *attname, bool *isNull) +{ + return(GetAttributeByName(slot, attname, isNull)); +} + +void +ExecEvalFuncArgs(FunctionCachePtr fcache, + ExprContext *econtext, + List *argList, + Datum argV[], + bool *argIsDone) +{ + int i; + bool argIsNull, *nullVect; + List *arg; + + nullVect = fcache->nullVect; + + i = 0; + foreach (arg, argList) { + /* ---------------- + * evaluate the expression, in general functions cannot take + * sets as arguments but we make an exception in the case of + * nested dot expressions. We have to watch out for this case + * here. + * ---------------- + */ + argV[i] = (Datum) + ExecEvalExpr((Node *) lfirst(arg), + econtext, + &argIsNull, + argIsDone); + if (! (*argIsDone)) + { + Assert(i == 0); + fcache->setArg = (char *)argV[0]; + fcache->hasSetArg = true; + } + if (argIsNull) + nullVect[i] = true; + else + nullVect[i] = false; + i++; + } +} + +/* ---------------- + * ExecMakeFunctionResult + * ---------------- + */ +Datum +ExecMakeFunctionResult(Node *node, + List *arguments, + ExprContext *econtext, + bool *isNull, + bool *isDone) +{ + Datum argv[MAXFMGRARGS]; + FunctionCachePtr fcache; + Func *funcNode = NULL; + Oper *operNode = NULL; + bool funcisset = false; + + /* + * This is kind of ugly, Func nodes now have targetlists so that + * we know when and what to project out from postquel function results. + * This means we have to pass the func node all the way down instead + * of using only the fcache struct as before. ExecMakeFunctionResult + * becomes a little bit more of a dual personality as a result. + */ + if (IsA(node,Func)) + { + funcNode = (Func *)node; + fcache = funcNode->func_fcache; + } + else + { + operNode = (Oper *)node; + fcache = operNode->op_fcache; + } + + /* ---------------- + * arguments is a list of expressions to evaluate + * before passing to the function manager. + * We collect the results of evaluating the expressions + * into a datum array (argv) and pass this array to arrayFmgr() + * ---------------- + */ + if (fcache->nargs != 0) { + bool argDone; + + if (fcache->nargs > MAXFMGRARGS) + elog(WARN, "ExecMakeFunctionResult: too many arguments"); + + /* + * If the setArg in the fcache is set we have an argument + * returning a set of tuples (i.e. a nested dot expression). We + * don't want to evaluate the arguments again until the function + * is done. hasSetArg will always be false until we eval the args + * for the first time. We should set this in the parser. + */ + if ((fcache->hasSetArg) && fcache->setArg != NULL) + { + argv[0] = (Datum)fcache->setArg; + argDone = false; + } + else + ExecEvalFuncArgs(fcache, econtext, arguments, argv, &argDone); + + if ((fcache->hasSetArg) && (argDone)) { + if (isDone) *isDone = true; + return (Datum)NULL; + } + } + + /* If this function is really a set, we have to diddle with things. + * If the function has already been called at least once, then the + * setArg field of the fcache holds + * the OID of this set in pg_proc. (This is not quite legit, since + * the setArg field is really for functions which take sets of tuples + * as input - set functions take no inputs at all. But it's a nice + * place to stash this value, for now.) + * + * If this is the first call of the set's function, then + * the call to ExecEvalFuncArgs above just returned the OID of + * the pg_proc tuple which defines this set. So replace the existing + * funcid in the funcnode with the set's OID. Also, we want a new + * fcache which points to the right function, so get that, now that + * we have the right OID. Also zero out the argv, since the real + * set doesn't take any arguments. + */ + if (((Func *)node)->funcid == SetEvalRegProcedure) { + funcisset = true; + if (fcache->setArg) { + argv[0] = 0; + + ((Func *)node)->funcid = (Oid) PointerGetDatum(fcache->setArg); + + } else { + ((Func *)node)->funcid = (Oid) argv[0]; + setFcache(node, argv[0], NIL,econtext); + fcache = ((Func *)node)->func_fcache; + fcache->setArg = (char*)argv[0]; + argv[0] = (Datum)0; + } + } + + /* ---------------- + * now return the value gotten by calling the function manager, + * passing the function the evaluated parameter values. + * ---------------- + */ + if (fcache->language == SQLlanguageId) { + Datum result; + + Assert(funcNode); + result = postquel_function (funcNode, (char **) argv, isNull, isDone); + /* + * finagle the situation where we are iterating through all results + * in a nested dot function (whose argument function returns a set + * of tuples) and the current function finally finishes. We need to + * get the next argument in the set and run the function all over + * again. This is getting unclean. + */ + if ((*isDone) && (fcache->hasSetArg)) { + bool argDone; + + ExecEvalFuncArgs(fcache, econtext, arguments, argv, &argDone); + + if (argDone) { + fcache->setArg = (char *)NULL; + *isDone = true; + result = (Datum)NULL; + } + else + result = postquel_function(funcNode, + (char **) argv, + isNull, + isDone); + } + if (funcisset) { + /* reset the funcid so that next call to this routine will + * still recognize this func as a set. + * Note that for now we assume that the set function in + * pg_proc must be a Postquel function - the funcid is + * not reset below for C functions. + */ + ((Func *)node)->funcid = SetEvalRegProcedure; + /* If we're done with the results of this function, get rid + * of its func cache. + */ + if (*isDone) { + ((Func *)node)->func_fcache = NULL; + } + } + return result; + } + else + { + int i; + + if (isDone) *isDone = true; + for (i = 0; i < fcache->nargs; i++) + if (fcache->nullVect[i] == true) *isNull = true; + + return((Datum) fmgr_c(fcache->func, fcache->foid, fcache->nargs, + (FmgrValues *) argv, isNull)); + } +} + + +/* ---------------------------------------------------------------- + * ExecEvalOper + * ExecEvalFunc + * + * Evaluate the functional result of a list of arguments by calling the + * function manager. Note that in the case of operator expressions, the + * optimizer had better have already replaced the operator OID with the + * appropriate function OID or we're hosed. + * + * old comments + * Presumably the function manager will not take null arguments, so we + * check for null arguments before sending the arguments to (fmgr). + * + * Returns the value of the functional expression. + * ---------------------------------------------------------------- + */ + +/* ---------------------------------------------------------------- + * ExecEvalOper + * ---------------------------------------------------------------- + */ +Datum +ExecEvalOper(Expr *opClause, ExprContext *econtext, bool *isNull) +{ + Oper *op; + List *argList; + FunctionCachePtr fcache; + bool isDone; + + /* ---------------- + * an opclause is a list (op args). (I think) + * + * we extract the oid of the function associated with + * the op and then pass the work onto ExecMakeFunctionResult + * which evaluates the arguments and returns the result of + * calling the function on the evaluated arguments. + * ---------------- + */ + op = (Oper *) opClause->oper; + argList = opClause->args; + + /* + * get the fcache from the Oper node. + * If it is NULL, then initialize it + */ + fcache = op->op_fcache; + if (fcache == NULL) { + setFcache((Node*)op, op->opid, argList, econtext); + fcache = op->op_fcache; + } + + /* ----------- + * call ExecMakeFunctionResult() with a dummy isDone that we ignore. + * We don't have operator whose arguments are sets. + * ----------- + */ + return + ExecMakeFunctionResult((Node *)op, argList, econtext, isNull, &isDone); +} + +/* ---------------------------------------------------------------- + * ExecEvalFunc + * ---------------------------------------------------------------- + */ + +Datum +ExecEvalFunc(Expr *funcClause, + ExprContext *econtext, + bool *isNull, + bool *isDone) +{ + Func *func; + List *argList; + FunctionCachePtr fcache; + + /* ---------------- + * an funcclause is a list (func args). (I think) + * + * we extract the oid of the function associated with + * the func node and then pass the work onto ExecMakeFunctionResult + * which evaluates the arguments and returns the result of + * calling the function on the evaluated arguments. + * + * this is nearly identical to the ExecEvalOper code. + * ---------------- + */ + func = (Func *)funcClause->oper; + argList = funcClause->args; + + /* + * get the fcache from the Func node. + * If it is NULL, then initialize it + */ + fcache = func->func_fcache; + if (fcache == NULL) { + setFcache((Node*)func, func->funcid, argList, econtext); + fcache = func->func_fcache; + } + + return + ExecMakeFunctionResult((Node*)func, argList, econtext, isNull, isDone); +} + +/* ---------------------------------------------------------------- + * ExecEvalNot + * ExecEvalOr + * ExecEvalAnd + * + * Evaluate boolean expressions. Evaluation of 'or' is + * short-circuited when the first true (or null) value is found. + * + * The query planner reformulates clause expressions in the + * qualification to conjunctive normal form. If we ever get + * an AND to evaluate, we can be sure that it's not a top-level + * clause in the qualification, but appears lower (as a function + * argument, for example), or in the target list. Not that you + * need to know this, mind you... + * ---------------------------------------------------------------- + */ +Datum +ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull) +{ + Datum expr_value; + Node *clause; + bool isDone; + + clause = lfirst(notclause->args); + + /* ---------------- + * We don't iterate over sets in the quals, so pass in an isDone + * flag, but ignore it. + * ---------------- + */ + expr_value = ExecEvalExpr(clause, econtext, isNull, &isDone); + + /* ---------------- + * if the expression evaluates to null, then we just + * cascade the null back to whoever called us. + * ---------------- + */ + if (*isNull) + return expr_value; + + /* ---------------- + * evaluation of 'not' is simple.. expr is false, then + * return 'true' and vice versa. + * ---------------- + */ + if (DatumGetInt32(expr_value) == 0) + return (Datum) true; + + return (Datum) false; +} + +/* ---------------------------------------------------------------- + * ExecEvalOr + * ---------------------------------------------------------------- + */ +Datum +ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull) +{ + List *clauses; + List *clause; + bool isDone; + bool IsNull; + Datum const_value; + + IsNull = false; + clauses = orExpr->args; + + /* ---------------- + * we use three valued logic functions here... + * we evaluate each of the clauses in turn, + * as soon as one is true we return that + * value. If none is true and none of the + * clauses evaluate to NULL we return + * the value of the last clause evaluated (which + * should be false) with *isNull set to false else + * if none is true and at least one clause evaluated + * to NULL we set *isNull flag to true - + * ---------------- + */ + foreach (clause, clauses) { + + /* ---------------- + * We don't iterate over sets in the quals, so pass in an isDone + * flag, but ignore it. + * ---------------- + */ + const_value = ExecEvalExpr((Node *) lfirst(clause), + econtext, |