diff options
Diffstat (limited to 'src/backend/commands')
29 files changed, 8103 insertions, 0 deletions
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 */ |