mirror of
https://github.com/postgres/postgres.git
synced 2025-11-12 05:01:15 +03:00
Postgres95 1.01 Distribution - Virgin Sources
This commit is contained in:
18
src/backend/tcop/Makefile.inc
Normal file
18
src/backend/tcop/Makefile.inc
Normal file
@@ -0,0 +1,18 @@
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Makefile.inc--
|
||||
# Makefile for the traffic cop module
|
||||
#
|
||||
# Copyright (c) 1994, Regents of the University of California
|
||||
#
|
||||
#
|
||||
# IDENTIFICATION
|
||||
# $Header: /cvsroot/pgsql/src/backend/tcop/Attic/Makefile.inc,v 1.1.1.1 1996/07/09 06:21:59 scrappy Exp $
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
VPATH:= $(VPATH):$(CURDIR)/tcop
|
||||
|
||||
SRCS_TCOP= aclchk.c dest.c fastpath.c postgres.c pquery.c utility.c
|
||||
|
||||
HEADERS+= dest.h fastpath.h pquery.h tcopdebug.h tcopprot.h utility.h
|
||||
555
src/backend/tcop/aclchk.c
Normal file
555
src/backend/tcop/aclchk.c
Normal file
@@ -0,0 +1,555 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* aclchk.c--
|
||||
* Routines to check access control permissions.
|
||||
*
|
||||
* Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/tcop/Attic/aclchk.c,v 1.1.1.1 1996/07/09 06:21:59 scrappy Exp $
|
||||
*
|
||||
* NOTES
|
||||
* See acl.h.
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "utils/acl.h" /* where declarations for this file goes */
|
||||
#include "access/heapam.h"
|
||||
#include "access/htup.h"
|
||||
#include "access/tupmacs.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/elog.h"
|
||||
#include "utils/palloc.h"
|
||||
#include "catalog/indexing.h"
|
||||
#include "catalog/catalog.h"
|
||||
#include "catalog/catname.h"
|
||||
#include "catalog/pg_group.h"
|
||||
#include "catalog/pg_user.h"
|
||||
#include "utils/syscache.h"
|
||||
#include "parser/catalog_utils.h"
|
||||
#include "fmgr.h"
|
||||
|
||||
/*
|
||||
* Enable use of user relations in place of real system catalogs.
|
||||
*/
|
||||
/*#define ACLDEBUG*/
|
||||
|
||||
#ifdef ACLDEBUG
|
||||
/*
|
||||
* Fool the code below into thinking that "pgacls" is pg_class.
|
||||
* relname and relowner are in the same place, happily.
|
||||
*/
|
||||
#undef Anum_pg_class_relacl
|
||||
#define Anum_pg_class_relacl 3
|
||||
#undef Natts_pg_class
|
||||
#define Natts_pg_class 3
|
||||
#undef Name_pg_class
|
||||
#define Name_pg_class "pgacls"
|
||||
#undef Name_pg_group
|
||||
#define Name_pg_group "pggroup"
|
||||
#endif
|
||||
|
||||
#ifdef ACLDEBUG_TRACE
|
||||
static
|
||||
dumpacl(Acl *acl)
|
||||
{
|
||||
register unsigned i;
|
||||
AclItem *aip;
|
||||
|
||||
elog(DEBUG, "acl size = %d, # acls = %d",
|
||||
ACL_SIZE(acl), ACL_NUM(acl));
|
||||
aip = (AclItem *) ACL_DAT(acl);
|
||||
for (i = 0; i < ACL_NUM(acl); ++i)
|
||||
elog(DEBUG, " acl[%d]: %s", i, aclitemout(aip + i));
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
void
|
||||
ChangeAcl(char *relname,
|
||||
AclItem *mod_aip,
|
||||
unsigned modechg)
|
||||
{
|
||||
register unsigned i;
|
||||
Acl *old_acl = (Acl *) NULL, *new_acl;
|
||||
Relation relation;
|
||||
static ScanKeyData relkey[1] = {
|
||||
{ 0, Anum_pg_class_relname, NameEqualRegProcedure }
|
||||
};
|
||||
HeapScanDesc hsdp;
|
||||
HeapTuple htp;
|
||||
Buffer buffer;
|
||||
Datum values[Natts_pg_class];
|
||||
char nulls[Natts_pg_class];
|
||||
char replaces[Natts_pg_class];
|
||||
ItemPointerData tmp_ipd;
|
||||
Relation idescs[Num_pg_class_indices];
|
||||
int free_old_acl = 0;
|
||||
|
||||
/*
|
||||
* Find the pg_class tuple matching 'relname' and extract the ACL.
|
||||
* If there's no ACL, create a default using the pg_class.relowner
|
||||
* field.
|
||||
*
|
||||
* We can't use the syscache here, since we need to do a heap_replace
|
||||
* on the tuple we find. Feh.
|
||||
*/
|
||||
relation = heap_openr(RelationRelationName);
|
||||
if (!RelationIsValid(relation))
|
||||
elog(WARN, "ChangeAcl: could not open '%s'??",
|
||||
RelationRelationName);
|
||||
fmgr_info(NameEqualRegProcedure, &relkey[0].sk_func, &relkey[0].sk_nargs);
|
||||
relkey[0].sk_argument = NameGetDatum(relname);
|
||||
hsdp = heap_beginscan(relation,
|
||||
0,
|
||||
NowTimeQual,
|
||||
(unsigned) 1,
|
||||
relkey);
|
||||
htp = heap_getnext(hsdp, 0, &buffer);
|
||||
if (!HeapTupleIsValid(htp)) {
|
||||
heap_endscan(hsdp);
|
||||
heap_close(relation);
|
||||
elog(WARN, "ChangeAcl: class \"%s\" not found",
|
||||
relname);
|
||||
return;
|
||||
}
|
||||
if (!heap_attisnull(htp, Anum_pg_class_relacl))
|
||||
old_acl = (Acl *) heap_getattr(htp, buffer,
|
||||
Anum_pg_class_relacl,
|
||||
RelationGetTupleDescriptor(relation),
|
||||
(bool *) NULL);
|
||||
if (!old_acl || ACL_NUM(old_acl) < 1) {
|
||||
#ifdef ACLDEBUG_TRACE
|
||||
elog(DEBUG, "ChangeAcl: using default ACL");
|
||||
#endif
|
||||
/* old_acl = acldefault(((Form_pg_class) GETSTRUCT(htp))->relowner); */
|
||||
old_acl = acldefault();
|
||||
free_old_acl = 1;
|
||||
}
|
||||
|
||||
#ifdef ACLDEBUG_TRACE
|
||||
dumpacl(old_acl);
|
||||
#endif
|
||||
new_acl = aclinsert3(old_acl, mod_aip, modechg);
|
||||
#ifdef ACLDEBUG_TRACE
|
||||
dumpacl(new_acl);
|
||||
#endif
|
||||
|
||||
for (i = 0; i < Natts_pg_class; ++i) {
|
||||
replaces[i] = ' ';
|
||||
nulls[i] = ' '; /* ignored if replaces[i] == ' ' anyway */
|
||||
values[i] = (Datum)NULL;/* ignored if replaces[i] == ' ' anyway */
|
||||
}
|
||||
replaces[Anum_pg_class_relacl - 1] = 'r';
|
||||
values[Anum_pg_class_relacl - 1] = (Datum)new_acl;
|
||||
htp = heap_modifytuple(htp, buffer, relation, values, nulls, replaces);
|
||||
/* XXX is this necessary? */
|
||||
ItemPointerCopy(&htp->t_ctid, &tmp_ipd);
|
||||
/* XXX handle index on pg_class? */
|
||||
setheapoverride(true);
|
||||
(void) heap_replace(relation, &tmp_ipd, htp);
|
||||
setheapoverride(false);
|
||||
heap_endscan(hsdp);
|
||||
|
||||
/* keep the catalog indices up to date */
|
||||
CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices,
|
||||
idescs);
|
||||
CatalogIndexInsert(idescs, Num_pg_class_indices, relation, htp);
|
||||
CatalogCloseIndices(Num_pg_class_indices, idescs);
|
||||
|
||||
heap_close(relation);
|
||||
if (free_old_acl)
|
||||
pfree(old_acl);
|
||||
pfree(new_acl);
|
||||
}
|
||||
|
||||
AclId
|
||||
get_grosysid(char *groname)
|
||||
{
|
||||
HeapTuple htp;
|
||||
AclId id = 0;
|
||||
|
||||
htp = SearchSysCacheTuple(GRONAME, PointerGetDatum(groname),
|
||||
0,0,0);
|
||||
if (HeapTupleIsValid(htp)) {
|
||||
id = ((Form_pg_group) GETSTRUCT(htp))->grosysid;
|
||||
} else {
|
||||
elog(WARN, "non-existent group \"%s\"", groname);
|
||||
}
|
||||
return(id);
|
||||
}
|
||||
|
||||
char*
|
||||
get_groname(AclId grosysid)
|
||||
{
|
||||
HeapTuple htp;
|
||||
char *name;
|
||||
|
||||
htp = SearchSysCacheTuple(GROSYSID, PointerGetDatum(grosysid),
|
||||
0,0,0);
|
||||
if (HeapTupleIsValid(htp)) {
|
||||
name = (((Form_pg_group) GETSTRUCT(htp))->groname).data;
|
||||
} else {
|
||||
elog(NOTICE, "get_groname: group %d not found", grosysid);
|
||||
}
|
||||
return(name);
|
||||
}
|
||||
|
||||
static int32
|
||||
in_group(AclId uid, AclId gid)
|
||||
{
|
||||
Relation relation;
|
||||
HeapTuple htp;
|
||||
Acl *tmp;
|
||||
unsigned i, num;
|
||||
AclId *aidp;
|
||||
int32 found = 0;
|
||||
|
||||
relation = heap_openr(GroupRelationName);
|
||||
if (!RelationIsValid(relation)) {
|
||||
elog(NOTICE, "in_group: could not open \"%s\"??",
|
||||
GroupRelationName);
|
||||
return(0);
|
||||
}
|
||||
htp = SearchSysCacheTuple(GROSYSID, ObjectIdGetDatum(gid),
|
||||
0,0,0);
|
||||
if (HeapTupleIsValid(htp) &&
|
||||
!heap_attisnull(htp, Anum_pg_group_grolist)) {
|
||||
tmp = (IdList *) heap_getattr(htp, InvalidBuffer,
|
||||
Anum_pg_group_grolist,
|
||||
RelationGetTupleDescriptor(relation),
|
||||
(bool *) NULL);
|
||||
/* XXX make me a function */
|
||||
num = IDLIST_NUM(tmp);
|
||||
aidp = IDLIST_DAT(tmp);
|
||||
for (i = 0; i < num; ++i)
|
||||
if (aidp[i] == uid) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
elog(NOTICE, "in_group: group %d not found", gid);
|
||||
}
|
||||
heap_close(relation);
|
||||
return(found);
|
||||
}
|
||||
|
||||
/*
|
||||
* aclcheck
|
||||
* Returns 1 if the 'id' of type 'idtype' has ACL entries in 'acl' to satisfy
|
||||
* any one of the requirements of 'mode'. Returns 0 otherwise.
|
||||
*/
|
||||
int32
|
||||
aclcheck(Acl *acl, AclId id, AclIdType idtype, AclMode mode)
|
||||
{
|
||||
register unsigned i;
|
||||
register AclItem *aip, *aidat;
|
||||
unsigned num, found_group;
|
||||
|
||||
/* if no acl is found, use world default */
|
||||
if (!acl) {
|
||||
acl = acldefault();
|
||||
}
|
||||
|
||||
num = ACL_NUM(acl);
|
||||
aidat = ACL_DAT(acl);
|
||||
|
||||
/*
|
||||
* We'll treat the empty ACL like that, too, although this is more
|
||||
* like an error (i.e., you manually blew away your ACL array) --
|
||||
* the system never creates an empty ACL.
|
||||
*/
|
||||
if (num < 1) {
|
||||
#ifdef ACLDEBUG_TRACE
|
||||
elog(DEBUG, "aclcheck: zero-length ACL, returning 1");
|
||||
#endif
|
||||
return(1);
|
||||
}
|
||||
|
||||
switch (idtype) {
|
||||
case ACL_IDTYPE_UID:
|
||||
for (i = 1, aip = aidat + 1; /* skip world entry */
|
||||
i < num && aip->ai_idtype == ACL_IDTYPE_UID;
|
||||
++i, ++aip) {
|
||||
if (aip->ai_id == id) {
|
||||
#ifdef ACLDEBUG_TRACE
|
||||
elog(DEBUG, "aclcheck: found %d/%d",
|
||||
aip->ai_id, aip->ai_mode);
|
||||
#endif
|
||||
return((aip->ai_mode & mode) ? 1 : 0);
|
||||
}
|
||||
}
|
||||
for (found_group = 0;
|
||||
i < num && aip->ai_idtype == ACL_IDTYPE_GID;
|
||||
++i, ++aip) {
|
||||
if (in_group(id, aip->ai_id)) {
|
||||
if (aip->ai_mode & mode)
|
||||
++found_group;
|
||||
else {
|
||||
#ifdef ACLDEBUG_TRACE
|
||||
elog(DEBUG, "aclcheck: found %d/%d",
|
||||
aip->ai_id, aip->ai_mode);
|
||||
#endif
|
||||
return(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found_group) {
|
||||
#ifdef ACLDEBUG_TRACE
|
||||
elog(DEBUG,"aclcheck: all groups ok");
|
||||
#endif
|
||||
return(1);
|
||||
}
|
||||
break;
|
||||
case ACL_IDTYPE_GID:
|
||||
for (i = 1, aip = aidat + 1; /* skip world entry and UIDs */
|
||||
i < num && aip->ai_idtype == ACL_IDTYPE_UID;
|
||||
++i, ++aip)
|
||||
;
|
||||
for (;
|
||||
i < num && aip->ai_idtype == ACL_IDTYPE_GID;
|
||||
++i, ++aip) {
|
||||
if (aip->ai_id == id) {
|
||||
#ifdef ACLDEBUG_TRACE
|
||||
elog(DEBUG, "aclcheck: found %d/%d",
|
||||
aip->ai_id, aip->ai_mode);
|
||||
#endif
|
||||
return((aip->ai_mode & mode) ? 1 : 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ACL_IDTYPE_WORLD:
|
||||
break;
|
||||
default:
|
||||
elog(WARN, "aclcheck: bogus ACL id type: %d", idtype);
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef ACLDEBUG_TRACE
|
||||
elog(DEBUG, "aclcheck: using world=%d", aidat->ai_mode);
|
||||
#endif
|
||||
return((aidat->ai_mode & mode) ? 1 : 0);
|
||||
}
|
||||
|
||||
int32
|
||||
pg_aclcheck(char *relname, char *usename, AclMode mode)
|
||||
{
|
||||
HeapTuple htp;
|
||||
AclId id;
|
||||
Acl *acl = (Acl *) NULL, *tmp;
|
||||
int32 result;
|
||||
Relation relation;
|
||||
|
||||
htp = SearchSysCacheTuple(USENAME, PointerGetDatum(usename),
|
||||
0,0,0);
|
||||
if (!HeapTupleIsValid(htp))
|
||||
elog(WARN, "pg_aclcheck: user \"%-.*s\" not found",
|
||||
NAMEDATALEN, usename);
|
||||
id = (AclId) ((Form_pg_user) GETSTRUCT(htp))->usesysid;
|
||||
|
||||
/* for the 'pg_database' relation, check the usecreatedb
|
||||
field before checking normal permissions */
|
||||
if ( strcmp(DatabaseRelationName, relname) == 0 &&
|
||||
(((Form_pg_user) GETSTRUCT(htp))->usecreatedb)) {
|
||||
/* note that even though the user can now append to the
|
||||
pg_database table, there is still additional permissions checking
|
||||
in dbcommands.c */
|
||||
if (mode & ACL_AP)
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Deny anyone permission to update a system catalog unless
|
||||
* pg_user.usecatupd is set. (This is to let superusers protect
|
||||
* themselves from themselves.)
|
||||
*/
|
||||
if (((mode & ACL_WR) || (mode & ACL_AP)) &&
|
||||
IsSystemRelationName(relname) &&
|
||||
!((Form_pg_user) GETSTRUCT(htp))->usecatupd) {
|
||||
elog(DEBUG, "pg_aclcheck: catalog update to \"%-.*s\": permission denied",
|
||||
NAMEDATALEN, relname);
|
||||
return(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise, superusers bypass all permission-checking.
|
||||
*/
|
||||
if (((Form_pg_user) GETSTRUCT(htp))->usesuper) {
|
||||
#ifdef ACLDEBUG_TRACE
|
||||
elog(DEBUG, "pg_aclcheck: \"%-.*s\" is superuser",
|
||||
NAMEDATALEN, usename);
|
||||
#endif
|
||||
return(1);
|
||||
}
|
||||
|
||||
#ifndef ACLDEBUG
|
||||
htp = SearchSysCacheTuple(RELNAME, PointerGetDatum(relname),
|
||||
0,0,0);
|
||||
if (!HeapTupleIsValid(htp)) {
|
||||
elog(WARN, "pg_aclcheck: class \"%-.*s\" not found",
|
||||
NAMEDATALEN, relname);
|
||||
return(1);
|
||||
}
|
||||
if (!heap_attisnull(htp, Anum_pg_class_relacl)) {
|
||||
relation = heap_openr(RelationRelationName);
|
||||
tmp = (Acl *) heap_getattr(htp, InvalidBuffer,
|
||||
Anum_pg_class_relacl,
|
||||
RelationGetTupleDescriptor(relation),
|
||||
(bool *) NULL);
|
||||
acl = makeacl(ACL_NUM(tmp));
|
||||
memmove((char *) acl, (char *) tmp, ACL_SIZE(tmp));
|
||||
heap_close(relation);
|
||||
} else {
|
||||
/* if the acl is null, by default the owner can do whatever
|
||||
he wants to with it */
|
||||
Oid ownerId;
|
||||
relation = heap_openr(RelationRelationName);
|
||||
ownerId = (Oid)heap_getattr(htp, InvalidBuffer,
|
||||
Anum_pg_class_relowner,
|
||||
RelationGetTupleDescriptor(relation),
|
||||
(bool*) NULL);
|
||||
acl = aclownerdefault(ownerId);
|
||||
}
|
||||
#else
|
||||
{ /* This is why the syscache is great... */
|
||||
static ScanKeyData relkey[1] = {
|
||||
{ 0, Anum_pg_class_relname, NameEqualRegProcedure }
|
||||
};
|
||||
HeapScanDesc hsdp;
|
||||
|
||||
relation = heap_openr(RelationRelationName);
|
||||
if (!RelationIsValid(relation)) {
|
||||
elog(NOTICE, "pg_checkacl: could not open \"%-.*s\"??",
|
||||
RelationRelationName);
|
||||
return(1);
|
||||
}
|
||||
fmgr_info(NameEqualRegProcedure,
|
||||
&relkey[0].sk_func,
|
||||
&relkey[0].sk_nargs);
|
||||
relkey[0].sk_argument = NameGetDatum(relname);
|
||||
hsdp = heap_beginscan(relation, 0, NowTimeQual, 1, relkey);
|
||||
htp = heap_getnext(hsdp, 0, (Buffer *) 0);
|
||||
if (HeapTupleIsValid(htp) &&
|
||||
!heap_attisnull(htp, Anum_pg_class_relacl)) {
|
||||
tmp = (Acl *) heap_getattr(htp, InvalidBuffer,
|
||||
Anum_pg_class_relacl,
|
||||
RelationGetTupleDescriptor(relation),
|
||||
(bool *) NULL);
|
||||
acl = makeacl(ACL_NUM(tmp));
|
||||
memmove((char *) acl, (char *) tmp, ACL_SIZE(tmp));
|
||||
}
|
||||
heap_endscan(hsdp);
|
||||
heap_close(relation);
|
||||
}
|
||||
#endif
|
||||
result = aclcheck(acl, id, (AclIdType) ACL_IDTYPE_UID, mode);
|
||||
if (acl)
|
||||
pfree(acl);
|
||||
return(result);
|
||||
}
|
||||
|
||||
int32
|
||||
pg_ownercheck(char *usename,
|
||||
char *value,
|
||||
int cacheid)
|
||||
{
|
||||
HeapTuple htp;
|
||||
AclId user_id, owner_id;
|
||||
|
||||
htp = SearchSysCacheTuple(USENAME, PointerGetDatum(usename),
|
||||
0,0,0);
|
||||
if (!HeapTupleIsValid(htp))
|
||||
elog(WARN, "pg_ownercheck: user \"%-.*s\" not found",
|
||||
NAMEDATALEN, usename);
|
||||
user_id = (AclId) ((Form_pg_user) GETSTRUCT(htp))->usesysid;
|
||||
|
||||
/*
|
||||
* Superusers bypass all permission-checking.
|
||||
*/
|
||||
if (((Form_pg_user) GETSTRUCT(htp))->usesuper) {
|
||||
#ifdef ACLDEBUG_TRACE
|
||||
elog(DEBUG, "pg_ownercheck: user \"%-.*s\" is superuser",
|
||||
NAMEDATALEN, usename);
|
||||
#endif
|
||||
return(1);
|
||||
}
|
||||
|
||||
htp = SearchSysCacheTuple(cacheid, PointerGetDatum(value),
|
||||
0,0,0);
|
||||
switch (cacheid) {
|
||||
case OPROID:
|
||||
if (!HeapTupleIsValid(htp))
|
||||
elog(WARN, "pg_ownercheck: operator %d not found",
|
||||
(int) value);
|
||||
owner_id = ((OperatorTupleForm) GETSTRUCT(htp))->oprowner;
|
||||
break;
|
||||
case PRONAME:
|
||||
if (!HeapTupleIsValid(htp))
|
||||
elog(WARN, "pg_ownercheck: function \"%-.*s\" not found",
|
||||
NAMEDATALEN, value);
|
||||
owner_id = ((Form_pg_proc) GETSTRUCT(htp))->proowner;
|
||||
break;
|
||||
case RELNAME:
|
||||
if (!HeapTupleIsValid(htp))
|
||||
elog(WARN, "pg_ownercheck: class \"%-.*s\" not found",
|
||||
NAMEDATALEN, value);
|
||||
owner_id = ((Form_pg_class) GETSTRUCT(htp))->relowner;
|
||||
break;
|
||||
case TYPNAME:
|
||||
if (!HeapTupleIsValid(htp))
|
||||
elog(WARN, "pg_ownercheck: type \"%-.*s\" not found",
|
||||
NAMEDATALEN, value);
|
||||
owner_id = ((TypeTupleForm) GETSTRUCT(htp))->typowner;
|
||||
break;
|
||||
default:
|
||||
elog(WARN, "pg_ownercheck: invalid cache id: %d",
|
||||
cacheid);
|
||||
break;
|
||||
}
|
||||
|
||||
return(user_id == owner_id);
|
||||
}
|
||||
|
||||
int32
|
||||
pg_func_ownercheck(char *usename,
|
||||
char *funcname,
|
||||
int nargs,
|
||||
Oid *arglist)
|
||||
{
|
||||
HeapTuple htp;
|
||||
AclId user_id, owner_id;
|
||||
|
||||
htp = SearchSysCacheTuple(USENAME, PointerGetDatum(usename),
|
||||
0,0,0);
|
||||
if (!HeapTupleIsValid(htp))
|
||||
elog(WARN, "pg_func_ownercheck: user \"%-.*s\" not found",
|
||||
NAMEDATALEN, usename);
|
||||
user_id = (AclId) ((Form_pg_user) GETSTRUCT(htp))->usesysid;
|
||||
|
||||
/*
|
||||
* Superusers bypass all permission-checking.
|
||||
*/
|
||||
if (((Form_pg_user) GETSTRUCT(htp))->usesuper) {
|
||||
#ifdef ACLDEBUG_TRACE
|
||||
elog(DEBUG, "pg_ownercheck: user \"%-.*s\" is superuser",
|
||||
NAMEDATALEN, usename);
|
||||
#endif
|
||||
return(1);
|
||||
}
|
||||
|
||||
htp = SearchSysCacheTuple(PRONAME,
|
||||
PointerGetDatum(funcname),
|
||||
PointerGetDatum(nargs),
|
||||
PointerGetDatum(arglist),
|
||||
0);
|
||||
if (!HeapTupleIsValid(htp))
|
||||
func_error("pg_func_ownercheck", funcname, nargs, (int*)arglist);
|
||||
|
||||
owner_id = ((Form_pg_proc) GETSTRUCT(htp))->proowner;
|
||||
|
||||
return(user_id == owner_id);
|
||||
}
|
||||
354
src/backend/tcop/dest.c
Normal file
354
src/backend/tcop/dest.c
Normal file
@@ -0,0 +1,354 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* dest.c--
|
||||
* support for various communication destinations - see lib/H/tcop/dest.h
|
||||
*
|
||||
* Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.1.1.1 1996/07/09 06:21:59 scrappy Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
/*
|
||||
* INTERFACE ROUTINES
|
||||
* BeginCommand - prepare destination for tuples of the given type
|
||||
* EndCommand - tell destination that no more tuples will arrive
|
||||
* NullCommand - tell dest that the last of a query sequence was processed
|
||||
*
|
||||
* NOTES
|
||||
* These routines do the appropriate work before and after
|
||||
* tuples are returned by a query to keep the backend and the
|
||||
* "destination" portals synchronized.
|
||||
*
|
||||
*/
|
||||
#include <stdio.h> /* for sprintf() */
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/htup.h"
|
||||
#include "libpq/libpq-be.h"
|
||||
#include "access/printtup.h"
|
||||
#include "utils/portal.h"
|
||||
#include "utils/elog.h"
|
||||
#include "utils/palloc.h"
|
||||
|
||||
#include "executor/executor.h"
|
||||
|
||||
#include "tcop/dest.h"
|
||||
|
||||
#include "catalog/pg_type.h"
|
||||
#include "utils/mcxt.h"
|
||||
|
||||
#include "commands/async.h"
|
||||
|
||||
/* ----------------
|
||||
* output functions
|
||||
* ----------------
|
||||
*/
|
||||
void
|
||||
donothing(List *tuple, List *attrdesc)
|
||||
{
|
||||
}
|
||||
|
||||
void (*DestToFunction(CommandDest dest))()
|
||||
{
|
||||
switch (dest) {
|
||||
case RemoteInternal:
|
||||
return printtup_internal;
|
||||
break;
|
||||
|
||||
case Remote:
|
||||
return printtup;
|
||||
break;
|
||||
|
||||
case Local:
|
||||
return be_printtup;
|
||||
break;
|
||||
|
||||
case Debug:
|
||||
return debugtup;
|
||||
break;
|
||||
|
||||
case None:
|
||||
default:
|
||||
return donothing;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* never gets here, but DECstation lint appears to be stupid...
|
||||
*/
|
||||
|
||||
return donothing;
|
||||
}
|
||||
|
||||
#define IS_INSERT_TAG(tag) (*tag == 'I' && *(tag+1) == 'N')
|
||||
|
||||
/* ----------------
|
||||
* EndCommand - tell destination that no more tuples will arrive
|
||||
* ----------------
|
||||
*/
|
||||
void
|
||||
EndCommand(char *commandTag, CommandDest dest)
|
||||
{
|
||||
char buf[64];
|
||||
|
||||
switch (dest) {
|
||||
case RemoteInternal:
|
||||
case Remote:
|
||||
/* ----------------
|
||||
* tell the fe that the query is over
|
||||
* ----------------
|
||||
*/
|
||||
pq_putnchar("C", 1);
|
||||
/* pq_putint(0, 4); */
|
||||
if (IS_INSERT_TAG(commandTag))
|
||||
{
|
||||
sprintf(buf, "%s %d", commandTag, GetAppendOid());
|
||||
pq_putstr(buf);
|
||||
}
|
||||
else
|
||||
pq_putstr(commandTag);
|
||||
pq_flush();
|
||||
break;
|
||||
|
||||
case Local:
|
||||
case Debug:
|
||||
break;
|
||||
case CopyEnd:
|
||||
pq_putnchar("Z", 1);
|
||||
pq_flush();
|
||||
break;
|
||||
case None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* These are necessary to sync communications between fe/be processes doing
|
||||
* COPY rel TO stdout
|
||||
*
|
||||
* or
|
||||
*
|
||||
* COPY rel FROM stdin
|
||||
*
|
||||
*/
|
||||
void
|
||||
SendCopyBegin()
|
||||
{
|
||||
pq_putnchar("B", 1);
|
||||
/* pq_putint(0, 4); */
|
||||
pq_flush();
|
||||
}
|
||||
|
||||
void
|
||||
ReceiveCopyBegin()
|
||||
{
|
||||
pq_putnchar("D", 1);
|
||||
/* pq_putint(0, 4); */
|
||||
pq_flush();
|
||||
}
|
||||
|
||||
/* ----------------
|
||||
* NullCommand - tell dest that the last of a query sequence was processed
|
||||
*
|
||||
* Necessary to implement the hacky FE/BE interface to handle
|
||||
* multiple-return queries.
|
||||
* ----------------
|
||||
*/
|
||||
void
|
||||
NullCommand(CommandDest dest)
|
||||
{
|
||||
switch (dest) {
|
||||
case RemoteInternal:
|
||||
case Remote: {
|
||||
#if 0
|
||||
/* Do any asynchronous notification. If front end wants to poll,
|
||||
it can send null queries to call this function.
|
||||
*/
|
||||
PQNotifyList *nPtr;
|
||||
MemoryContext orig;
|
||||
|
||||
if (notifyContext == NULL) {
|
||||
notifyContext = CreateGlobalMemory("notify");
|
||||
}
|
||||
orig = MemoryContextSwitchTo((MemoryContext)notifyContext);
|
||||
|
||||
for (nPtr = PQnotifies() ;
|
||||
nPtr != NULL;
|
||||
nPtr = (PQNotifyList *)SLGetSucc(&nPtr->Node)) {
|
||||
pq_putnchar("A",1);
|
||||
pq_putint(0, 4);
|
||||
pq_putstr(nPtr->relname);
|
||||
pq_putint(nPtr->be_pid,4);
|
||||
PQremoveNotify(nPtr);
|
||||
}
|
||||
pq_flush();
|
||||
PQcleanNotify(); /* garbage collect */
|
||||
(void) MemoryContextSwitchTo(orig);
|
||||
#endif
|
||||
/* ----------------
|
||||
* tell the fe that the last of the queries has finished
|
||||
* ----------------
|
||||
*/
|
||||
/* pq_putnchar("I", 1); */
|
||||
pq_putstr("I");
|
||||
/* pq_putint(0, 4);*/
|
||||
pq_flush();
|
||||
}
|
||||
break;
|
||||
|
||||
case Local:
|
||||
case Debug:
|
||||
case None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------
|
||||
* BeginCommand - prepare destination for tuples of the given type
|
||||
* ----------------
|
||||
*/
|
||||
void
|
||||
BeginCommand(char *pname,
|
||||
int operation,
|
||||
TupleDesc tupdesc,
|
||||
bool isIntoRel,
|
||||
bool isIntoPortal,
|
||||
char *tag,
|
||||
CommandDest dest)
|
||||
{
|
||||
PortalEntry *entry;
|
||||
AttributeTupleForm *attrs = tupdesc->attrs;
|
||||
int natts = tupdesc->natts;
|
||||
int i;
|
||||
char *p;
|
||||
|
||||
switch (dest) {
|
||||
case RemoteInternal:
|
||||
case Remote:
|
||||
/* ----------------
|
||||
* if this is a "retrieve portal" query, just return
|
||||
* because nothing needs to be sent to the fe.
|
||||
* ----------------
|
||||
*/
|
||||
ResetAppendOid();
|
||||
if (isIntoPortal)
|
||||
return;
|
||||
|
||||
/* ----------------
|
||||
* if portal name not specified for remote query,
|
||||
* use the "blank" portal.
|
||||
* ----------------
|
||||
*/
|
||||
if (pname == NULL)
|
||||
pname = "blank";
|
||||
|
||||
/* ----------------
|
||||
* send fe info on tuples we're about to send
|
||||
* ----------------
|
||||
*/
|
||||
pq_flush();
|
||||
pq_putnchar("P", 1); /* new portal.. */
|
||||
pq_putstr(pname); /* portal name */
|
||||
|
||||
/* ----------------
|
||||
* if this is a retrieve, then we send back the tuple
|
||||
* descriptor of the tuples. "retrieve into" is an
|
||||
* exception because no tuples are returned in that case.
|
||||
* ----------------
|
||||
*/
|
||||
if (operation == CMD_SELECT && !isIntoRel) {
|
||||
pq_putnchar("T", 1); /* type info to follow.. */
|
||||
pq_putint(natts, 2); /* number of attributes in tuples */
|
||||
|
||||
for (i = 0; i < natts; ++i) {
|
||||
pq_putstr(attrs[i]->attname.data);/* if 16 char name oops.. */
|
||||
pq_putint((int) attrs[i]->atttypid, 4);
|
||||
pq_putint(attrs[i]->attlen, 2);
|
||||
}
|
||||
}
|
||||
pq_flush();
|
||||
break;
|
||||
|
||||
case Local:
|
||||
/* ----------------
|
||||
* prepare local portal buffer for query results
|
||||
* and setup result for PQexec()
|
||||
* ----------------
|
||||
*/
|
||||
entry = be_currentportal();
|
||||
if (pname != NULL)
|
||||
pbuf_setportalinfo(entry, pname);
|
||||
|
||||
if (operation == CMD_SELECT && !isIntoRel) {
|
||||
be_typeinit(entry, tupdesc, natts);
|
||||
p = (char *) palloc(strlen(entry->name)+2);
|
||||
p[0] = 'P';
|
||||
strcpy(p+1,entry->name);
|
||||
} else {
|
||||
p = (char *) palloc(strlen(tag)+2);
|
||||
p[0] = 'C';
|
||||
strcpy(p+1,tag);
|
||||
}
|
||||
entry->result = p;
|
||||
break;
|
||||
|
||||
case Debug:
|
||||
/* ----------------
|
||||
* show the return type of the tuples
|
||||
* ----------------
|
||||
*/
|
||||
if (pname == NULL)
|
||||
pname = "blank";
|
||||
|
||||
showatts(pname, tupdesc);
|
||||
break;
|
||||
|
||||
case None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static Oid AppendOid;
|
||||
|
||||
void
|
||||
ResetAppendOid()
|
||||
{
|
||||
AppendOid = InvalidOid;
|
||||
}
|
||||
|
||||
#define MULTI_TUPLE_APPEND -1
|
||||
|
||||
void
|
||||
UpdateAppendOid(Oid newoid)
|
||||
{
|
||||
/*
|
||||
* First update after AppendOid was reset (at command beginning).
|
||||
*/
|
||||
if (AppendOid == InvalidOid)
|
||||
AppendOid = newoid;
|
||||
/*
|
||||
* Already detected a multiple tuple append, return a void oid ;)
|
||||
*/
|
||||
else if (AppendOid == MULTI_TUPLE_APPEND)
|
||||
return;
|
||||
/*
|
||||
* Oid has been assigned once before, tag this as a multiple tuple
|
||||
* append.
|
||||
*/
|
||||
else
|
||||
AppendOid = MULTI_TUPLE_APPEND;
|
||||
}
|
||||
|
||||
Oid
|
||||
GetAppendOid()
|
||||
{
|
||||
if (AppendOid == MULTI_TUPLE_APPEND)
|
||||
return InvalidOid;
|
||||
return AppendOid;
|
||||
}
|
||||
78
src/backend/tcop/dest.h
Normal file
78
src/backend/tcop/dest.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* dest.h--
|
||||
* Whenever the backend is submitted a query, the results
|
||||
* have to go someplace - either to the standard output,
|
||||
* to a local portal buffer or to a remote portal buffer.
|
||||
*
|
||||
* - stdout is the destination only when we are running a
|
||||
* backend without a postmaster and are returning results
|
||||
* back to the user.
|
||||
*
|
||||
* - a local portal buffer is the destination when a backend
|
||||
* executes a user-defined function which calls PQexec() or
|
||||
* PQfn(). In this case, the results are collected into a
|
||||
* PortalBuffer which the user's function may diddle with.
|
||||
*
|
||||
* - a remote portal buffer is the destination when we are
|
||||
* running a backend with a frontend and the frontend executes
|
||||
* PQexec() or PQfn(). In this case, the results are sent
|
||||
* to the frontend via the pq_ functions.
|
||||
*
|
||||
* - None is the destination when the system executes
|
||||
* a query internally. This is not used now but it may be
|
||||
* useful for the parallel optimiser/executor.
|
||||
*
|
||||
*
|
||||
* Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $Id: dest.h,v 1.1.1.1 1996/07/09 06:21:59 scrappy Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef DEST_H
|
||||
#define DEST_H
|
||||
|
||||
#include "catalog/pg_attribute.h"
|
||||
#include "access/tupdesc.h"
|
||||
|
||||
/* ----------------
|
||||
* CommandDest is used to allow the results of calling
|
||||
* pg_eval() to go to the right place.
|
||||
* ----------------
|
||||
*/
|
||||
typedef enum {
|
||||
None, /* results are discarded */
|
||||
Debug, /* results go to debugging output */
|
||||
Local, /* results go in local portal buffer */
|
||||
Remote, /* results sent to frontend process */
|
||||
CopyBegin, /* results sent to frontend process but are strings */
|
||||
CopyEnd, /* results sent to frontend process but are strings */
|
||||
RemoteInternal /* results sent to frontend process in internal
|
||||
(binary) form */
|
||||
} CommandDest;
|
||||
|
||||
|
||||
/* AttrInfo* replaced with TupleDesc, now that TupleDesc also has within it
|
||||
the number of attributes
|
||||
|
||||
typedef struct AttrInfo {
|
||||
int numAttr;
|
||||
AttributeTupleForm *attrs;
|
||||
} AttrInfo;
|
||||
*/
|
||||
|
||||
extern void donothing(List *tuple, List *attrdesc);
|
||||
extern void (*DestToFunction(CommandDest dest))();
|
||||
extern void EndCommand(char *commandTag, CommandDest dest);
|
||||
extern void SendCopyBegin();
|
||||
extern void ReceiveCopyBegin();
|
||||
extern void NullCommand(CommandDest dest);
|
||||
extern void BeginCommand(char *pname, int operation, TupleDesc attinfo,
|
||||
bool isIntoRel, bool isIntoPortal, char *tag,
|
||||
CommandDest dest);
|
||||
extern void ResetAppendOid();
|
||||
extern void UpdateAppendOid(Oid newoid);
|
||||
extern Oid GetAppendOid();
|
||||
|
||||
#endif /* DEST_H */
|
||||
353
src/backend/tcop/fastpath.c
Normal file
353
src/backend/tcop/fastpath.c
Normal file
@@ -0,0 +1,353 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* fastpath.c--
|
||||
* routines to handle function requests from the frontend
|
||||
*
|
||||
* Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.1.1.1 1996/07/09 06:21:59 scrappy Exp $
|
||||
*
|
||||
* NOTES
|
||||
* This cruft is the server side of PQfn.
|
||||
*
|
||||
* - jolly 07/11/95:
|
||||
*
|
||||
* no longer rely on return sizes provided by the frontend. Always
|
||||
* use the true lengths for the catalogs. Assume that the frontend
|
||||
* has allocated enough space to handle the result value returned.
|
||||
*
|
||||
* trust that the user knows what he is doing with the args. If the
|
||||
* sys catalog says it is a varlena, assume that the user is only sending
|
||||
* down VARDATA and that the argsize is the VARSIZE. If the arg is
|
||||
* fixed len, assume that the argsize given by the user is correct.
|
||||
*
|
||||
* if the function returns by value, then only send 4 bytes value
|
||||
* back to the frontend. If the return returns by reference,
|
||||
* send down only the data portion and set the return size appropriately.
|
||||
*
|
||||
* OLD COMMENTS FOLLOW
|
||||
*
|
||||
* The VAR_LENGTH_{ARGS,RESULT} stuff is limited to MAX_STRING_LENGTH
|
||||
* (see src/backend/tmp/fastpath.h) for no obvious reason. Since its
|
||||
* primary use (for us) is for Inversion path names, it should probably
|
||||
* be increased to 256 (MAXPATHLEN for Inversion, hidden in pg_type
|
||||
* as well as utils/adt/filename.c).
|
||||
*
|
||||
* Quoth PMA on 08/15/93:
|
||||
*
|
||||
* This code has been almost completely rewritten with an eye to
|
||||
* keeping it as compatible as possible with the previous (broken)
|
||||
* implementation.
|
||||
*
|
||||
* The previous implementation would assume (1) that any value of
|
||||
* length <= 4 bytes was passed-by-value, and that any other value
|
||||
* was a struct varlena (by-reference). There was NO way to pass a
|
||||
* fixed-length by-reference argument (like char16) or a struct
|
||||
* varlena of size <= 4 bytes.
|
||||
*
|
||||
* The new implementation checks the catalogs to determine whether
|
||||
* a value is by-value (type "0" is null-delimited character string,
|
||||
* as it is for, e.g., the parser). The only other item obtained
|
||||
* from the catalogs is whether or not the value should be placed in
|
||||
* a struct varlena or not. Otherwise, the size given by the
|
||||
* frontend is assumed to be correct (probably a bad decision, but
|
||||
* we do strange things in the name of compatibility).
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "tcop/tcopdebug.h"
|
||||
|
||||
#include "utils/palloc.h"
|
||||
#include "fmgr.h"
|
||||
#include "utils/elog.h"
|
||||
#include "utils/builtins.h" /* for oideq */
|
||||
#include "tcop/fastpath.h"
|
||||
#include "libpq/libpq.h"
|
||||
|
||||
#include "access/xact.h" /* for TransactionId/CommandId protos */
|
||||
|
||||
#include "utils/syscache.h"
|
||||
#include "catalog/pg_proc.h"
|
||||
#include "catalog/pg_type.h"
|
||||
|
||||
|
||||
/* ----------------
|
||||
* SendFunctionResult
|
||||
* ----------------
|
||||
*/
|
||||
static void
|
||||
SendFunctionResult(Oid fid, /* function id */
|
||||
char *retval, /* actual return value */
|
||||
bool retbyval,
|
||||
int retlen /* the length according to the catalogs */
|
||||
)
|
||||
{
|
||||
pq_putnchar("V", 1);
|
||||
|
||||
if (retlen != 0) {
|
||||
pq_putnchar("G", 1);
|
||||
if (retbyval) { /* by-value */
|
||||
pq_putint(retlen, 4);
|
||||
pq_putint((int)retval, retlen);
|
||||
} else { /* by-reference ... */
|
||||
if (retlen < 0) { /* ... varlena */
|
||||
pq_putint(VARSIZE(retval) - VARHDRSZ, 4);
|
||||
pq_putnchar(VARDATA(retval), VARSIZE(retval) - VARHDRSZ);
|
||||
} else { /* ... fixed */
|
||||
pq_putint(retlen, 4);
|
||||
pq_putnchar(retval, retlen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pq_putnchar("0", 1);
|
||||
pq_flush();
|
||||
}
|
||||
|
||||
/*
|
||||
* This structure saves enough state so that one can avoid having to
|
||||
* do catalog lookups over and over again. (Each RPC can require up
|
||||
* to MAXFMGRARGS+2 lookups, which is quite tedious.)
|
||||
*
|
||||
* The previous incarnation of this code just assumed that any argument
|
||||
* of size <= 4 was by value; this is not correct. There is no cheap
|
||||
* way to determine function argument length etc.; one must simply pay
|
||||
* the price of catalog lookups.
|
||||
*/
|
||||
struct fp_info {
|
||||
Oid funcid;
|
||||
int nargs;
|
||||
bool argbyval[MAXFMGRARGS];
|
||||
int32 arglen[MAXFMGRARGS]; /* signed (for varlena) */
|
||||
bool retbyval;
|
||||
int32 retlen; /* signed (for varlena) */
|
||||
TransactionId xid;
|
||||
CommandId cid;
|
||||
};
|
||||
|
||||
/*
|
||||
* We implement one-back caching here. If we need to do more, we can.
|
||||
* Most routines in tight loops (like PQfswrite -> F_LOWRITE) will do
|
||||
* the same thing repeatedly.
|
||||
*/
|
||||
static struct fp_info last_fp = { InvalidOid };
|
||||
|
||||
/*
|
||||
* valid_fp_info
|
||||
*
|
||||
* RETURNS:
|
||||
* 1 if the state in 'fip' is valid
|
||||
* 0 otherwise
|
||||
*
|
||||
* "valid" means:
|
||||
* The saved state was either uninitialized, for another function,
|
||||
* or from a previous command. (Commands can do updates, which
|
||||
* may invalidate catalog entries for subsequent commands. This
|
||||
* is overly pessimistic but since there is no smarter invalidation
|
||||
* scheme...).
|
||||
*/
|
||||
static int
|
||||
valid_fp_info(Oid func_id, struct fp_info *fip)
|
||||
{
|
||||
Assert(OidIsValid(func_id));
|
||||
Assert(fip != (struct fp_info *) NULL);
|
||||
|
||||
return(OidIsValid(fip->funcid) &&
|
||||
oideq(func_id, fip->funcid) &&
|
||||
TransactionIdIsCurrentTransactionId(fip->xid) &&
|
||||
CommandIdIsCurrentCommandId(fip->cid));
|
||||
}
|
||||
|
||||
/*
|
||||
* update_fp_info
|
||||
*
|
||||
* Performs catalog lookups to load a struct fp_info 'fip' for the
|
||||
* function 'func_id'.
|
||||
*
|
||||
* RETURNS:
|
||||
* The correct information in 'fip'. Sets 'fip->funcid' to
|
||||
* InvalidOid if an exception occurs.
|
||||
*/
|
||||
static void
|
||||
update_fp_info(Oid func_id, struct fp_info *fip)
|
||||
{
|
||||
Oid *argtypes; /* an oid8 */
|
||||
Oid rettype;
|
||||
HeapTuple func_htp, type_htp;
|
||||
TypeTupleForm tp;
|
||||
Form_pg_proc pp;
|
||||
int i;
|
||||
|
||||
Assert(OidIsValid(func_id));
|
||||
Assert(fip != (struct fp_info *) NULL);
|
||||
|
||||
/*
|
||||
* Since the validity of this structure is determined by whether
|
||||
* the funcid is OK, we clear the funcid here. It must not be
|
||||
* set to the correct value until we are about to return with
|
||||
* a good struct fp_info, since we can be interrupted (i.e., with
|
||||
* an elog(WARN, ...)) at any time.
|
||||
*/
|
||||
memset((char *) fip, 0, (int) sizeof(struct fp_info));
|
||||
fip->funcid = InvalidOid;
|
||||
|
||||
func_htp = SearchSysCacheTuple(PROOID, ObjectIdGetDatum(func_id),
|
||||
0,0,0);
|
||||
if (!HeapTupleIsValid(func_htp)) {
|
||||
elog(WARN, "update_fp_info: cache lookup for function %d failed",
|
||||
func_id);
|
||||
}
|
||||
pp = (Form_pg_proc) GETSTRUCT(func_htp);
|
||||
fip->nargs = pp->pronargs;
|
||||
rettype = pp->prorettype;
|
||||
argtypes = pp->proargtypes;
|
||||
|
||||
for (i = 0; i < fip->nargs; ++i) {
|
||||
if (OidIsValid(argtypes[i])) {
|
||||
type_htp = SearchSysCacheTuple(TYPOID,
|
||||
ObjectIdGetDatum(argtypes[i]),
|
||||
0,0,0);
|
||||
if (!HeapTupleIsValid(type_htp)) {
|
||||
elog(WARN, "update_fp_info: bad argument type %d for %d",
|
||||
argtypes[i], func_id);
|
||||
}
|
||||
tp = (TypeTupleForm) GETSTRUCT(type_htp);
|
||||
fip->argbyval[i] = tp->typbyval;
|
||||
fip->arglen[i] = tp->typlen;
|
||||
} /* else it had better be VAR_LENGTH_ARG */
|
||||
}
|
||||
|
||||
if (OidIsValid(rettype)) {
|
||||
type_htp = SearchSysCacheTuple(TYPOID, ObjectIdGetDatum(rettype),
|
||||
0,0,0);
|
||||
if (!HeapTupleIsValid(type_htp)) {
|
||||
elog(WARN, "update_fp_info: bad return type %d for %d",
|
||||
rettype, func_id);
|
||||
}
|
||||
tp = (TypeTupleForm) GETSTRUCT(type_htp);
|
||||
fip->retbyval = tp->typbyval;
|
||||
fip->retlen = tp->typlen;
|
||||
} /* else it had better by VAR_LENGTH_RESULT */
|
||||
|
||||
fip->xid = GetCurrentTransactionId();
|
||||
fip->cid = GetCurrentCommandId();
|
||||
|
||||
/*
|
||||
* This must be last!
|
||||
*/
|
||||
fip->funcid = func_id;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* HandleFunctionRequest
|
||||
*
|
||||
* Server side of PQfn (fastpath function calls from the frontend).
|
||||
* This corresponds to the libpq protocol symbol "F".
|
||||
*
|
||||
* RETURNS:
|
||||
* nothing of significance.
|
||||
* All errors result in elog(WARN,...).
|
||||
*/
|
||||
int
|
||||
HandleFunctionRequest()
|
||||
{
|
||||
Oid fid;
|
||||
int argsize;
|
||||
int nargs;
|
||||
char *arg[8];
|
||||
char *retval;
|
||||
int i;
|
||||
uint32 palloced;
|
||||
char *p;
|
||||
struct fp_info *fip;
|
||||
|
||||
fid = (Oid) pq_getint(4); /* function oid */
|
||||
nargs = pq_getint(4); /* # of arguments */
|
||||
|
||||
/*
|
||||
* This is where the one-back caching is done.
|
||||
* If you want to save more state, make this a loop around an array.
|
||||
*/
|
||||
fip = &last_fp;
|
||||
if (!valid_fp_info(fid, fip)) {
|
||||
update_fp_info(fid, fip);
|
||||
}
|
||||
|
||||
if (fip->nargs != nargs) {
|
||||
elog(WARN, "HandleFunctionRequest: actual arguments (%d) != registered arguments (%d)",
|
||||
nargs, fip->nargs);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy arguments into arg vector. If we palloc() an argument, we need
|
||||
* to remember, so that we pfree() it after the call.
|
||||
*/
|
||||
palloced = 0x0;
|
||||
for (i = 0; i < 8; ++i) {
|
||||
if (i >= nargs) {
|
||||
arg[i] = (char *) NULL;
|
||||
} else {
|
||||
argsize = pq_getint(4);
|
||||
|
||||
Assert(argsize > 0);
|
||||
if (fip->argbyval[i]) { /* by-value */
|
||||
Assert(argsize <= 4);
|
||||
arg[i] = (char *) pq_getint(argsize);
|
||||
} else { /* by-reference ... */
|
||||
if (fip->arglen[i] < 0) { /* ... varlena */
|
||||
if (!(p = palloc(argsize + VARHDRSZ))) {
|
||||
elog(WARN, "HandleFunctionRequest: palloc failed");
|
||||
}
|
||||
VARSIZE(p) = argsize + VARHDRSZ;
|
||||
pq_getnchar(VARDATA(p), 0, argsize);
|
||||
} else { /* ... fixed */
|
||||
/* XXX cross our fingers and trust "argsize" */
|
||||
if (!(p = palloc(argsize))) {
|
||||
elog(WARN, "HandleFunctionRequest: palloc failed");
|
||||
}
|
||||
pq_getnchar(p, 0, argsize);
|
||||
}
|
||||
palloced |= (1 << i);
|
||||
arg[i] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NO_FASTPATH
|
||||
retval = fmgr(fid,
|
||||
arg[0], arg[1], arg[2], arg[3],
|
||||
arg[4], arg[5], arg[6], arg[7]);
|
||||
|
||||
#else
|
||||
retval = NULL;
|
||||
#endif /* NO_FASTPATH */
|
||||
|
||||
/* free palloc'ed arguments */
|
||||
for (i = 0; i < nargs; ++i) {
|
||||
if (palloced & (1 << i))
|
||||
pfree(arg[i]);
|
||||
}
|
||||
|
||||
/*
|
||||
* If this is an ordinary query (not a retrieve portal p ...), then
|
||||
* we return the data to the user. If the return value was palloc'ed,
|
||||
* then it must also be freed.
|
||||
*/
|
||||
#ifndef NO_FASTPATH
|
||||
SendFunctionResult(fid, retval, fip->retbyval, fip->retlen);
|
||||
#else
|
||||
SendFunctionResult(fid, retval, fip->retbyval, 0);
|
||||
#endif /* NO_FASTPATH */
|
||||
|
||||
if (!fip->retbyval)
|
||||
pfree(retval);
|
||||
|
||||
|
||||
|
||||
return(0);
|
||||
}
|
||||
31
src/backend/tcop/fastpath.h
Normal file
31
src/backend/tcop/fastpath.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* fastpath.h--
|
||||
*
|
||||
*
|
||||
*
|
||||
* Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $Id: fastpath.h,v 1.1.1.1 1996/07/09 06:21:59 scrappy Exp $
|
||||
*
|
||||
* NOTES
|
||||
* This information pulled out of tcop/fastpath.c and put
|
||||
* here so that the PQfn() in be-pqexec.c could access it.
|
||||
* -cim 2/26/91
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef FASTPATH_H
|
||||
#define FASTPATH_H
|
||||
|
||||
/* ----------------
|
||||
* fastpath #defines
|
||||
* ----------------
|
||||
*/
|
||||
#define VAR_LENGTH_RESULT (-1)
|
||||
#define VAR_LENGTH_ARG (-5)
|
||||
#define MAX_STRING_LENGTH 256
|
||||
|
||||
extern int HandleFunctionRequest(void);
|
||||
|
||||
#endif /* FASTPATH_H */
|
||||
1500
src/backend/tcop/postgres.c
Normal file
1500
src/backend/tcop/postgres.c
Normal file
File diff suppressed because it is too large
Load Diff
362
src/backend/tcop/pquery.c
Normal file
362
src/backend/tcop/pquery.c
Normal file
@@ -0,0 +1,362 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* pquery.c--
|
||||
* POSTGRES process query command code
|
||||
*
|
||||
* Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/tcop/pquery.c,v 1.1.1.1 1996/07/09 06:22:00 scrappy Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "tcop/tcopdebug.h"
|
||||
|
||||
#include "utils/elog.h"
|
||||
#include "utils/palloc.h"
|
||||
#include "utils/mcxt.h"
|
||||
#include "miscadmin.h"
|
||||
#include "utils/portal.h"
|
||||
|
||||
#include "nodes/pg_list.h"
|
||||
#include "nodes/primnodes.h"
|
||||
#include "nodes/plannodes.h"
|
||||
#include "nodes/execnodes.h"
|
||||
#include "nodes/memnodes.h"
|
||||
|
||||
#include "tcop/dest.h"
|
||||
|
||||
#include "executor/execdefs.h"
|
||||
#include "executor/execdesc.h"
|
||||
#include "executor/executor.h"
|
||||
#include "tcop/pquery.h"
|
||||
|
||||
#include "commands/command.h"
|
||||
|
||||
static char* CreateOperationTag(int operationType);
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* CreateQueryDesc
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
QueryDesc *
|
||||
CreateQueryDesc(Query *parsetree,
|
||||
Plan *plantree,
|
||||
CommandDest dest)
|
||||
{
|
||||
QueryDesc *qd = (QueryDesc *)palloc(sizeof(QueryDesc));
|
||||
|
||||
qd->operation = parsetree->commandType; /* operation */
|
||||
qd->parsetree = parsetree; /* parse tree */
|
||||
qd->plantree = plantree; /* plan */
|
||||
qd->dest = dest; /* output dest */
|
||||
return qd;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* CreateExecutorState
|
||||
*
|
||||
* Note: this may someday take parameters -cim 9/18/89
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
EState *
|
||||
CreateExecutorState()
|
||||
{
|
||||
EState *state;
|
||||
extern int NBuffers;
|
||||
long *refcount;
|
||||
|
||||
/* ----------------
|
||||
* create a new executor state
|
||||
* ----------------
|
||||
*/
|
||||
state = makeNode(EState);
|
||||
|
||||
/* ----------------
|
||||
* initialize the Executor State structure
|
||||
* ----------------
|
||||
*/
|
||||
state->es_direction = EXEC_FRWD;
|
||||
state->es_range_table = NIL;
|
||||
|
||||
state->es_into_relation_descriptor = NULL;
|
||||
state->es_result_relation_info = NULL;
|
||||
|
||||
state->es_param_list_info = NULL;
|
||||
|
||||
state->es_BaseId = 0;
|
||||
state->es_tupleTable = NULL;
|
||||
|
||||
state->es_junkFilter = NULL;
|
||||
|
||||
refcount = (long *) palloc(NBuffers * sizeof(long));
|
||||
memset((char *) refcount, 0, NBuffers * sizeof(long));
|
||||
state->es_refcount = (int *) refcount;
|
||||
|
||||
/* ----------------
|
||||
* return the executor state structure
|
||||
* ----------------
|
||||
*/
|
||||
return state;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* CreateOperationTag
|
||||
*
|
||||
* utility to get a string representation of the
|
||||
* query operation.
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
static char*
|
||||
CreateOperationTag(int operationType)
|
||||
{
|
||||
char* tag;
|
||||
|
||||
switch (operationType) {
|
||||
case CMD_SELECT:
|
||||
tag = "SELECT";
|
||||
break;
|
||||
case CMD_INSERT:
|
||||
tag = "INSERT";
|
||||
break;
|
||||
case CMD_DELETE:
|
||||
tag = "DELETE";
|
||||
break;
|
||||
case CMD_UPDATE:
|
||||
tag = "UPDATE";
|
||||
break;
|
||||
default:
|
||||
elog(DEBUG, "CreateOperationTag: unknown operation type %d",
|
||||
operationType);
|
||||
tag = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
/* ----------------
|
||||
* ProcessPortal
|
||||
* ----------------
|
||||
*/
|
||||
|
||||
void
|
||||
ProcessPortal(char* portalName,
|
||||
Query *parseTree,
|
||||
Plan *plan,
|
||||
EState *state,
|
||||
TupleDesc attinfo,
|
||||
CommandDest dest)
|
||||
{
|
||||
Portal portal;
|
||||
MemoryContext portalContext;
|
||||
|
||||
/* ----------------
|
||||
* convert the current blank portal into the user-specified
|
||||
* portal and initialize the state and query descriptor.
|
||||
* ----------------
|
||||
*/
|
||||
|
||||
if (PortalNameIsSpecial(portalName))
|
||||
elog(WARN,
|
||||
"The portal name %s is reserved for internal use",
|
||||
portalName);
|
||||
|
||||
portal = BlankPortalAssignName(portalName);
|
||||
|
||||
PortalSetQuery(portal,
|
||||
CreateQueryDesc(parseTree, plan, dest),
|
||||
attinfo,
|
||||
state,
|
||||
PortalCleanup);
|
||||
|
||||
/* ----------------
|
||||
* now create a new blank portal and switch to it.
|
||||
* Otherwise, the new 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. -hirohama
|
||||
* ----------------
|
||||
*/
|
||||
portalContext = (MemoryContext)
|
||||
PortalGetHeapMemory(GetPortalByName(NULL));
|
||||
|
||||
MemoryContextSwitchTo(portalContext);
|
||||
|
||||
StartPortalAllocMode(DefaultAllocMode, 0);
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ProcessQueryDesc
|
||||
*
|
||||
* Read the comments for ProcessQuery() below...
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
void
|
||||
ProcessQueryDesc(QueryDesc *queryDesc)
|
||||
{
|
||||
Query *parseTree;
|
||||
Plan *plan;
|
||||
int operation;
|
||||
char* tag;
|
||||
EState *state;
|
||||
TupleDesc attinfo;
|
||||
|
||||
bool isRetrieveIntoPortal;
|
||||
bool isRetrieveIntoRelation;
|
||||
char* intoName;
|
||||
CommandDest dest;
|
||||
|
||||
/* ----------------
|
||||
* get info from the query desc
|
||||
* ----------------
|
||||
*/
|
||||
parseTree = queryDesc->parsetree;
|
||||
plan = queryDesc->plantree;
|
||||
|
||||
operation = queryDesc->operation;
|
||||
tag = CreateOperationTag(operation);
|
||||
dest = queryDesc->dest;
|
||||
|
||||
/* ----------------
|
||||
* initialize portal/into relation status
|
||||
* ----------------
|
||||
*/
|
||||
isRetrieveIntoPortal = false;
|
||||
isRetrieveIntoRelation = false;
|
||||
|
||||
if (operation == CMD_SELECT) {
|
||||
if (parseTree->isPortal) {
|
||||
isRetrieveIntoPortal = true;
|
||||
intoName = parseTree->into;
|
||||
if (parseTree->isBinary) {
|
||||
/*
|
||||
* For internal format portals, we change Remote
|
||||
* (externalized form) to RemoteInternal (internalized
|
||||
* form)
|
||||
*/
|
||||
dest = queryDesc->dest = RemoteInternal;
|
||||
}
|
||||
} else if (parseTree->into != NULL) {
|
||||
/* select into table */
|
||||
isRetrieveIntoRelation = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* ----------------
|
||||
* when performing a retrieve into, we override the normal
|
||||
* communication destination during the processing of the
|
||||
* the query. This only affects the tuple-output function
|
||||
* - the correct destination will still see BeginCommand()
|
||||
* and EndCommand() messages.
|
||||
* ----------------
|
||||
*/
|
||||
if (isRetrieveIntoRelation)
|
||||
queryDesc->dest = (int) None;
|
||||
|
||||
/* ----------------
|
||||
* create a default executor state..
|
||||
* ----------------
|
||||
*/
|
||||
state = CreateExecutorState();
|
||||
|
||||
/* ----------------
|
||||
* call ExecStart to prepare the plan for execution
|
||||
* ----------------
|
||||
*/
|
||||
attinfo = ExecutorStart(queryDesc, state);
|
||||
|
||||
/* ----------------
|
||||
* report the query's result type information
|
||||
* back to the front end or to whatever destination
|
||||
* we're dealing with.
|
||||
* ----------------
|
||||
*/
|
||||
BeginCommand(NULL,
|
||||
operation,
|
||||
attinfo,
|
||||
isRetrieveIntoRelation,
|
||||
isRetrieveIntoPortal,
|
||||
tag,
|
||||
dest);
|
||||
|
||||
/* ----------------
|
||||
* Named portals do not do a "fetch all" initially, so now
|
||||
* we return since ExecMain has been called with EXEC_START
|
||||
* to initialize the query plan.
|
||||
*
|
||||
* Note: ProcessPortal transforms the current "blank" portal
|
||||
* into a named portal and creates a new blank portal so
|
||||
* everything we allocated in the current "blank" memory
|
||||
* context will be preserved across queries. -cim 2/22/91
|
||||
* ----------------
|
||||
*/
|
||||
if (isRetrieveIntoPortal) {
|
||||
PortalExecutorHeapMemory = NULL;
|
||||
|
||||
ProcessPortal(intoName,
|
||||
parseTree,
|
||||
plan,
|
||||
state,
|
||||
attinfo,
|
||||
dest);
|
||||
|
||||
EndCommand(tag, dest);
|
||||
return;
|
||||
}
|
||||
|
||||
/* ----------------
|
||||
* Now we get to the important call to ExecutorRun() where we
|
||||
* actually run the plan..
|
||||
* ----------------
|
||||
*/
|
||||
ExecutorRun(queryDesc, state, EXEC_RUN, 0);
|
||||
|
||||
/* ----------------
|
||||
* now, we close down all the scans and free allocated resources...
|
||||
* with ExecutorEnd()
|
||||
* ----------------
|
||||
*/
|
||||
ExecutorEnd(queryDesc, state);
|
||||
|
||||
/* ----------------
|
||||
* Notify the destination of end of processing.
|
||||
* ----------------
|
||||
*/
|
||||
EndCommand(tag, dest);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ProcessQuery
|
||||
*
|
||||
* Execute a plan, the non-parallel version
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
void
|
||||
ProcessQuery(Query *parsetree,
|
||||
Plan *plan,
|
||||
char *argv[],
|
||||
Oid *typev,
|
||||
int nargs,
|
||||
CommandDest dest)
|
||||
{
|
||||
QueryDesc *queryDesc;
|
||||
extern int dontExecute; /* from postgres.c */
|
||||
extern void print_plan (Plan* p, Query* parsetree); /* from print.c */
|
||||
|
||||
queryDesc = CreateQueryDesc(parsetree, plan, dest);
|
||||
|
||||
if (dontExecute) {
|
||||
/* don't execute it, just show the query plan */
|
||||
print_plan(plan, parsetree);
|
||||
} else
|
||||
ProcessQueryDesc(queryDesc);
|
||||
}
|
||||
|
||||
36
src/backend/tcop/pquery.h
Normal file
36
src/backend/tcop/pquery.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* pquery.h--
|
||||
* prototypes for pquery.c.
|
||||
*
|
||||
*
|
||||
* Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $Id: pquery.h,v 1.1.1.1 1996/07/09 06:22:00 scrappy Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef PQUERY_H
|
||||
#define PQUERY_H
|
||||
|
||||
#include "executor/execdesc.h"
|
||||
#include "tcop/dest.h"
|
||||
|
||||
/* moved to execdesc.h
|
||||
extern QueryDesc *CreateQueryDesc(Query *parsetree, Plan *plantree,
|
||||
CommandDest dest);
|
||||
|
||||
*/
|
||||
extern EState *CreateExecutorState();
|
||||
|
||||
|
||||
extern void ProcessPortal(char *portalName, Query *parseTree,
|
||||
Plan *plan, EState *state, TupleDesc attinfo,
|
||||
CommandDest dest);
|
||||
|
||||
extern void ProcessQueryDesc(QueryDesc *queryDesc);
|
||||
|
||||
extern void ProcessQuery(Query *parsetree, Plan *plan, char *argv[],
|
||||
Oid *typev, int nargs, CommandDest dest);
|
||||
|
||||
#endif /* pqueryIncluded */
|
||||
43
src/backend/tcop/tcopdebug.h
Normal file
43
src/backend/tcop/tcopdebug.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* tcopdebug.h--
|
||||
* #defines governing debugging behaviour in the traffic cop
|
||||
*
|
||||
*
|
||||
* Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $Id: tcopdebug.h,v 1.1.1.1 1996/07/09 06:22:00 scrappy Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef TCOPDEBUG_H
|
||||
#define TCOPDEBUG_H
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* debugging defines.
|
||||
*
|
||||
* If you want certain debugging behaviour, then #define
|
||||
* the variable to 1, else #undef it. -cim 10/26/89
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/* ----------------
|
||||
* TCOP_SHOWSTATS controls whether or not buffer and
|
||||
* access method statistics are shown for each query. -cim 2/9/89
|
||||
* ----------------
|
||||
*/
|
||||
#undef TCOP_SHOWSTATS
|
||||
|
||||
/* ----------------
|
||||
* TCOP_DONTUSENEWLINE controls the default setting of
|
||||
* the UseNewLine variable in postgres.c
|
||||
* ----------------
|
||||
*/
|
||||
#undef TCOP_DONTUSENEWLINE
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* #defines controlled by above definitions
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#endif /* TCOPDEBUG_H */
|
||||
40
src/backend/tcop/tcopprot.h
Normal file
40
src/backend/tcop/tcopprot.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* tcopprot.h--
|
||||
* prototypes for postgres.c.
|
||||
*
|
||||
*
|
||||
* Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $Id: tcopprot.h,v 1.1.1.1 1996/07/09 06:22:00 scrappy Exp $
|
||||
*
|
||||
* OLD COMMENTS
|
||||
* This file was created so that other c files could get the two
|
||||
* function prototypes without having to include tcop.h which single
|
||||
* handedly includes the whole f*cking tree -- mer 5 Nov. 1991
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef TCOPPROT_H
|
||||
#define TCOPPROT_H
|
||||
|
||||
#include "tcop/dest.h"
|
||||
#include "nodes/pg_list.h"
|
||||
#include "parser/parse_query.h"
|
||||
|
||||
#ifndef BOOTSTRAP_INCLUDE
|
||||
extern List *pg_plan(char *query_string, Oid *typev, int nargs,
|
||||
QueryTreeList **queryListP, CommandDest dest);
|
||||
extern void pg_eval(char *query_string, char *argv[], Oid *typev, int nargs);
|
||||
extern void pg_eval_dest(char *query_string, char *argv[], Oid *typev,
|
||||
int nargs, CommandDest dest);
|
||||
#endif /* BOOTSTRAP_HEADER */
|
||||
|
||||
extern void handle_warn();
|
||||
extern void quickdie();
|
||||
extern void die();
|
||||
extern int PostgresMain(int argc, char *argv[]);
|
||||
extern void ResetUsage();
|
||||
extern void ShowUsage();
|
||||
|
||||
#endif /* tcopprotIncluded */
|
||||
646
src/backend/tcop/utility.c
Normal file
646
src/backend/tcop/utility.c
Normal file
@@ -0,0 +1,646 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* utility.c--
|
||||
* Contains functions which control the execution of the POSTGRES utility
|
||||
* commands. At one time acted as an interface between the Lisp and C
|
||||
* systems.
|
||||
*
|
||||
* Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.1.1.1 1996/07/09 06:22:00 scrappy Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
#include "parser/dbcommands.h"
|
||||
#include "access/xact.h"
|
||||
#include "catalog/catalog.h"
|
||||
#include "catalog/pg_type.h"
|
||||
|
||||
#include "commands/async.h"
|
||||
#include "commands/cluster.h"
|
||||
#include "commands/command.h"
|
||||
#include "commands/copy.h"
|
||||
#include "commands/creatinh.h"
|
||||
#include "commands/defrem.h"
|
||||
#include "commands/purge.h"
|
||||
#include "commands/rename.h"
|
||||
#include "commands/view.h"
|
||||
#include "commands/version.h"
|
||||
#include "commands/vacuum.h"
|
||||
#include "commands/recipe.h"
|
||||
#include "commands/explain.h"
|
||||
|
||||
#include "nodes/parsenodes.h"
|
||||
#include "parse.h"
|
||||
#include "utils/elog.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/acl.h"
|
||||
#include "utils/palloc.h"
|
||||
#include "rewrite/rewriteRemove.h"
|
||||
#include "rewrite/rewriteDefine.h"
|
||||
#include "tcop/tcopdebug.h"
|
||||
#include "tcop/dest.h"
|
||||
|
||||
#ifndef NO_SECURITY
|
||||
#include "miscadmin.h"
|
||||
#include "utils/acl.h"
|
||||
#include "utils/syscache.h"
|
||||
#endif
|
||||
|
||||
|
||||
/* ----------------
|
||||
* CHECK_IF_ABORTED() is used to avoid doing unnecessary
|
||||
* processing within an aborted transaction block.
|
||||
* ----------------
|
||||
*/
|
||||
#define CHECK_IF_ABORTED() \
|
||||
if (IsAbortedTransactionBlockState()) { \
|
||||
elog(NOTICE, "(transaction aborted): %s", \
|
||||
"queries ignored until END"); \
|
||||
commandTag = "*ABORT STATE*"; \
|
||||
break; \
|
||||
} \
|
||||
|
||||
/* ----------------
|
||||
* general utility function invoker
|
||||
* ----------------
|
||||
*/
|
||||
void
|
||||
ProcessUtility(Node *parsetree,
|
||||
CommandDest dest)
|
||||
{
|
||||
char *commandTag = NULL;
|
||||
char *relname;
|
||||
char *relationName;
|
||||
char *userName;
|
||||
|
||||
userName = GetPgUserName();
|
||||
|
||||
switch (nodeTag(parsetree)) {
|
||||
/* ********************************
|
||||
* transactions
|
||||
* ********************************
|
||||
*/
|
||||
case T_TransactionStmt:
|
||||
{
|
||||
TransactionStmt *stmt = (TransactionStmt *)parsetree;
|
||||
switch (stmt->command) {
|
||||
case BEGIN_TRANS:
|
||||
commandTag = "BEGIN";
|
||||
CHECK_IF_ABORTED();
|
||||
BeginTransactionBlock();
|
||||
break;
|
||||
|
||||
case END_TRANS:
|
||||
commandTag = "END";
|
||||
EndTransactionBlock();
|
||||
break;
|
||||
|
||||
case ABORT_TRANS:
|
||||
commandTag = "ABORT";
|
||||
UserAbortTransactionBlock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/* ********************************
|
||||
* portal manipulation
|
||||
* ********************************
|
||||
*/
|
||||
case T_ClosePortalStmt:
|
||||
{
|
||||
ClosePortalStmt *stmt = (ClosePortalStmt *)parsetree;
|
||||
|
||||
commandTag = "CLOSE";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
PerformPortalClose(stmt->portalname, dest);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_FetchStmt:
|
||||
{
|
||||
FetchStmt *stmt = (FetchStmt *)parsetree;
|
||||
char *portalName = stmt->portalname;
|
||||
bool forward;
|
||||
int count;
|
||||
|
||||
commandTag = "FETCH";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
forward = (bool)(stmt->direction == FORWARD);
|
||||
|
||||
/* parser ensures that count is >= 0 and
|
||||
'fetch ALL' -> 0 */
|
||||
|
||||
count = stmt->howMany;
|
||||
PerformPortalFetch(portalName, forward, count, commandTag, dest);
|
||||
}
|
||||
break;
|
||||
|
||||
/* ********************************
|
||||
* relation and attribute manipulation
|
||||
* ********************************
|
||||
*/
|
||||
case T_CreateStmt:
|
||||
commandTag = "CREATE";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
DefineRelation((CreateStmt *)parsetree);
|
||||
break;
|
||||
|
||||
case T_DestroyStmt:
|
||||
{
|
||||
DestroyStmt *stmt = (DestroyStmt *)parsetree;
|
||||
List *arg;
|
||||
List *args = stmt->relNames;
|
||||
|
||||
commandTag = "DROP";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
foreach (arg, args) {
|
||||
relname = strVal(lfirst(arg));
|
||||
if (IsSystemRelationName(relname))
|
||||
elog(WARN, "class \"%-.*s\" is a system catalog",
|
||||
NAMEDATALEN, relname);
|
||||
#ifndef NO_SECURITY
|
||||
if (!pg_ownercheck(userName, relname, RELNAME))
|
||||
elog(WARN, "you do not own class \"%-.*s\"",
|
||||
NAMEDATALEN, relname);
|
||||
#endif
|
||||
}
|
||||
foreach (arg, args) {
|
||||
relname = strVal(lfirst(arg));
|
||||
RemoveRelation(relname);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case T_PurgeStmt:
|
||||
{
|
||||
PurgeStmt *stmt = (PurgeStmt *)parsetree;
|
||||
|
||||
commandTag = "PURGE";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
RelationPurge(stmt->relname,
|
||||
stmt->beforeDate, /* absolute time string */
|
||||
stmt->afterDate); /* relative time string */
|
||||
}
|
||||
break;
|
||||
|
||||
case T_CopyStmt:
|
||||
{
|
||||
CopyStmt *stmt = (CopyStmt *)parsetree;
|
||||
char *filename;
|
||||
char *delim;
|
||||
bool isBinary;
|
||||
bool isFrom;
|
||||
bool pipe = false;
|
||||
|
||||
commandTag = "COPY";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
relname = stmt->relname;
|
||||
isBinary = stmt->binary;
|
||||
|
||||
isFrom = (bool)(stmt->direction == FROM);
|
||||
filename = stmt->filename;
|
||||
delim = stmt->delimiter;
|
||||
|
||||
#ifndef NO_SECURITY
|
||||
if (isFrom) {
|
||||
if (!pg_aclcheck(relname, userName, ACL_RD))
|
||||
elog(WARN, "%s %s", relname, ACL_NO_PRIV_WARNING);
|
||||
} else {
|
||||
if (!pg_aclcheck(relname, userName, ACL_WR))
|
||||
elog(WARN, "%s %s", relname, ACL_NO_PRIV_WARNING);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Free up file descriptors - going to do a read... */
|
||||
|
||||
closeOneVfd();
|
||||
|
||||
/*
|
||||
* use stdin/stdout if filename is null.
|
||||
*/
|
||||
if (filename == NULL)
|
||||
pipe = true;
|
||||
|
||||
if (pipe && IsUnderPostmaster) dest = CopyEnd;
|
||||
|
||||
DoCopy(relname, isBinary, isFrom, pipe, filename, delim);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_AddAttrStmt:
|
||||
{
|
||||
AddAttrStmt *stmt = (AddAttrStmt *)parsetree;
|
||||
|
||||
commandTag = "ADD";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
/* owner checking done in PerformAddAttribute (now recursive) */
|
||||
PerformAddAttribute(stmt->relname,
|
||||
userName,
|
||||
stmt->inh,
|
||||
stmt->colDef);
|
||||
}
|
||||
break;
|
||||
|
||||
/*
|
||||
* schema
|
||||
*/
|
||||
case T_RenameStmt:
|
||||
{
|
||||
RenameStmt *stmt = (RenameStmt *)parsetree;
|
||||
|
||||
commandTag = "RENAME";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
relname = stmt->relname;
|
||||
if (IsSystemRelationName(relname))
|
||||
elog(WARN, "class \"%s\" is a system catalog",
|
||||
relname);
|
||||
#ifndef NO_SECURITY
|
||||
if (!pg_ownercheck(userName, relname, RELNAME))
|
||||
elog(WARN, "you do not own class \"%s\"",
|
||||
relname);
|
||||
#endif
|
||||
|
||||
/* ----------------
|
||||
* XXX using len == 3 to tell the difference
|
||||
* between "rename rel to newrel" and
|
||||
* "rename att in rel to newatt" will not
|
||||
* work soon because "rename type/operator/rule"
|
||||
* stuff is being added. - cim 10/24/90
|
||||
* ----------------
|
||||
* [another piece of amuzing but useless anecdote -- ay]
|
||||
*/
|
||||
if (stmt->column == NULL) {
|
||||
/* ----------------
|
||||
* rename relation
|
||||
*
|
||||
* Note: we also rename the "type" tuple
|
||||
* corresponding to the relation.
|
||||
* ----------------
|
||||
*/
|
||||
renamerel(relname, /* old name */
|
||||
stmt->newname); /* new name */
|
||||
TypeRename(relname, /* old name */
|
||||
stmt->newname); /* new name */
|
||||
} else {
|
||||
/* ----------------
|
||||
* rename attribute
|
||||
* ----------------
|
||||
*/
|
||||
renameatt(relname, /* relname */
|
||||
stmt->column, /* old att name */
|
||||
stmt->newname, /* new att name */
|
||||
userName,
|
||||
stmt->inh); /* recursive? */
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case T_ChangeACLStmt:
|
||||
{
|
||||
ChangeACLStmt *stmt = (ChangeACLStmt *)parsetree;
|
||||
List *i;
|
||||
AclItem *aip;
|
||||
unsigned modechg;
|
||||
|
||||
commandTag = "CHANGE";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
aip = stmt->aclitem;
|
||||
modechg = stmt->modechg;
|
||||
#ifndef NO_SECURITY
|
||||
foreach (i, stmt->relNames) {
|
||||
relname = strVal(lfirst(i));
|
||||
if (!pg_ownercheck(userName, relname, RELNAME))
|
||||
elog(WARN, "you do not own class \"%-.*s\"",
|
||||
NAMEDATALEN, relname);
|
||||
}
|
||||
#endif
|
||||
foreach (i, stmt->relNames) {
|
||||
relname = strVal(lfirst(i));
|
||||
ChangeAcl(relname, aip, modechg);
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
/* ********************************
|
||||
* object creation / destruction
|
||||
* ********************************
|
||||
*/
|
||||
case T_DefineStmt:
|
||||
{
|
||||
DefineStmt *stmt = (DefineStmt *)parsetree;
|
||||
|
||||
commandTag = "CREATE";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
switch(stmt->defType) {
|
||||
case OPERATOR:
|
||||
DefineOperator(stmt->defname, /* operator name */
|
||||
stmt->definition); /* rest */
|
||||
break;
|
||||
case P_TYPE:
|
||||
{
|
||||
DefineType (stmt->defname, stmt->definition);
|
||||
}
|
||||
break;
|
||||
case AGGREGATE:
|
||||
DefineAggregate(stmt->defname, /*aggregate name */
|
||||
stmt->definition); /* rest */
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case T_ViewStmt: /* VIEW */
|
||||
{
|
||||
ViewStmt *stmt = (ViewStmt *)parsetree;
|
||||
|
||||
commandTag = "CREATE";
|
||||
CHECK_IF_ABORTED();
|
||||
DefineView (stmt->viewname, stmt->query); /* retrieve parsetree */
|
||||
}
|
||||
break;
|
||||
|
||||
case T_ProcedureStmt: /* FUNCTION */
|
||||
commandTag = "CREATE";
|
||||
CHECK_IF_ABORTED();
|
||||
DefineFunction((ProcedureStmt *)parsetree, dest); /* everything */
|
||||
break;
|
||||
|
||||
case T_IndexStmt:
|
||||
{
|
||||
IndexStmt *stmt = (IndexStmt *)parsetree;
|
||||
|
||||
commandTag = "CREATE";
|
||||
CHECK_IF_ABORTED();
|
||||
/* XXX no support for ARCHIVE indices, yet */
|
||||
DefineIndex(stmt->relname, /* relation name */
|
||||
stmt->idxname, /* index name */
|
||||
stmt->accessMethod, /* am name */
|
||||
stmt->indexParams, /* parameters */
|
||||
stmt->withClause,
|
||||
(Expr*)stmt->whereClause,
|
||||
stmt->rangetable);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_RuleStmt:
|
||||
{
|
||||
RuleStmt *stmt = (RuleStmt *)parsetree;
|
||||
#ifndef NO_SECURITY
|
||||
relname = stmt->object->relname;
|
||||
if (!pg_aclcheck(relname, userName, ACL_RU))
|
||||
elog(WARN, "%s %s", relname, ACL_NO_PRIV_WARNING);
|
||||
#endif
|
||||
commandTag = "CREATE";
|
||||
CHECK_IF_ABORTED();
|
||||
DefineQueryRewrite(stmt);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_ExtendStmt:
|
||||
{
|
||||
ExtendStmt *stmt = (ExtendStmt *)parsetree;
|
||||
|
||||
commandTag = "EXTEND";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
ExtendIndex(stmt->idxname, /* index name */
|
||||
(Expr*)stmt->whereClause, /* where */
|
||||
stmt->rangetable);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_RemoveStmt:
|
||||
{
|
||||
RemoveStmt *stmt = (RemoveStmt *)parsetree;
|
||||
|
||||
commandTag = "DROP";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
switch(stmt->removeType) {
|
||||
case AGGREGATE:
|
||||
RemoveAggregate(stmt->name);
|
||||
break;
|
||||
case INDEX:
|
||||
relname = stmt->name;
|
||||
if (IsSystemRelationName(relname))
|
||||
elog(WARN, "class \"%s\" is a system catalog index",
|
||||
relname);
|
||||
#ifndef NO_SECURITY
|
||||
if (!pg_ownercheck(userName, relname, RELNAME))
|
||||
elog(WARN, "you do not own class \"%s\"",
|
||||
relname);
|
||||
#endif
|
||||
RemoveIndex(relname);
|
||||
break;
|
||||
case RULE:
|
||||
{
|
||||
char *rulename = stmt->name;
|
||||
#ifndef NO_SECURITY
|
||||
|
||||
relationName = RewriteGetRuleEventRel(rulename);
|
||||
if (!pg_aclcheck(relationName, userName, ACL_RU))
|
||||
elog(WARN, "%s %s", relationName, ACL_NO_PRIV_WARNING);
|
||||
#endif
|
||||
RemoveRewriteRule(rulename);
|
||||
}
|
||||
break;
|
||||
case P_TYPE:
|
||||
#ifndef NO_SECURITY
|
||||
/* XXX moved to remove.c */
|
||||
#endif
|
||||
RemoveType(stmt->name);
|
||||
break;
|
||||
case VIEW:
|
||||
{
|
||||
char *viewName = stmt->name;
|
||||
char *ruleName;
|
||||
extern char *RewriteGetRuleEventRel();
|
||||
|
||||
#ifndef NO_SECURITY
|
||||
|
||||
ruleName = MakeRetrieveViewRuleName(viewName);
|
||||
relationName = RewriteGetRuleEventRel(ruleName);
|
||||
if (!pg_ownercheck(userName, relationName, RELNAME))
|
||||
elog(WARN, "%s %s", relationName, ACL_NO_PRIV_WARNING);
|
||||
pfree(ruleName);
|
||||
#endif
|
||||
RemoveView(viewName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case T_RemoveFuncStmt:
|
||||
{
|
||||
RemoveFuncStmt *stmt = (RemoveFuncStmt *)parsetree;
|
||||
commandTag = "DROP";
|
||||
CHECK_IF_ABORTED();
|
||||
RemoveFunction(stmt->funcname,
|
||||
length(stmt->args),
|
||||
stmt->args);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_RemoveOperStmt:
|
||||
{
|
||||
RemoveOperStmt *stmt = (RemoveOperStmt *)parsetree;
|
||||
char* type1 = (char*) NULL;
|
||||
char *type2 = (char*) NULL;
|
||||
|
||||
commandTag = "DROP";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
if (lfirst(stmt->args)!=NULL)
|
||||
type1 = strVal(lfirst(stmt->args));
|
||||
if (lsecond(stmt->args)!=NULL)
|
||||
type2 = strVal(lsecond(stmt->args));
|
||||
RemoveOperator(stmt->opname, type1, type2);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_VersionStmt:
|
||||
{
|
||||
elog(WARN, "CREATE VERSION is not currently implemented");
|
||||
}
|
||||
break;
|
||||
|
||||
case T_CreatedbStmt:
|
||||
{
|
||||
CreatedbStmt *stmt = (CreatedbStmt *)parsetree;
|
||||
|
||||
commandTag = "CREATEDB";
|
||||
CHECK_IF_ABORTED();
|
||||
createdb(stmt->dbname);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_DestroydbStmt:
|
||||
{
|
||||
DestroydbStmt *stmt = (DestroydbStmt *)parsetree;
|
||||
|
||||
commandTag = "DESTROYDB";
|
||||
CHECK_IF_ABORTED();
|
||||
destroydb(stmt->dbname);
|
||||
}
|
||||
break;
|
||||
|
||||
/* Query-level asynchronous notification */
|
||||
case T_NotifyStmt:
|
||||
{
|
||||
NotifyStmt *stmt = (NotifyStmt *)parsetree;
|
||||
|
||||
commandTag = "NOTIFY";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
Async_Notify(stmt->relname);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_ListenStmt:
|
||||
{
|
||||
ListenStmt *stmt = (ListenStmt *)parsetree;
|
||||
|
||||
commandTag = "LISTEN";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
Async_Listen(stmt->relname,MasterPid);
|
||||
}
|
||||
break;
|
||||
|
||||
/* ********************************
|
||||
* dynamic loader
|
||||
* ********************************
|
||||
*/
|
||||
case T_LoadStmt:
|
||||
{
|
||||
LoadStmt *stmt = (LoadStmt *)parsetree;
|
||||
FILE *fp, *fopen();
|
||||
char *filename;
|
||||
|
||||
commandTag = "LOAD";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
filename = stmt->filename;
|
||||
closeAllVfds();
|
||||
if ((fp = fopen(filename, "r")) == NULL)
|
||||
elog(WARN, "LOAD: could not open file %s", filename);
|
||||
fclose(fp);
|
||||
load_file(filename);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_ClusterStmt:
|
||||
{
|
||||
ClusterStmt *stmt = (ClusterStmt *)parsetree;
|
||||
|
||||
commandTag = "CLUSTER";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
cluster(stmt->relname, stmt->indexname);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_VacuumStmt:
|
||||
commandTag = "VACUUM";
|
||||
CHECK_IF_ABORTED();
|
||||
vacuum(((VacuumStmt *) parsetree)->vacrel);
|
||||
break;
|
||||
|
||||
case T_ExplainStmt:
|
||||
{
|
||||
ExplainStmt *stmt = (ExplainStmt *)parsetree;
|
||||
|
||||
commandTag = "EXPLAIN";
|
||||
CHECK_IF_ABORTED();
|
||||
|
||||
ExplainQuery(stmt->query, stmt->options, dest);
|
||||
}
|
||||
break;
|
||||
|
||||
/* ********************************
|
||||
Tioga-related statements
|
||||
*********************************/
|
||||
case T_RecipeStmt:
|
||||
{
|
||||
RecipeStmt* stmt = (RecipeStmt*)parsetree;
|
||||
commandTag="EXECUTE RECIPE";
|
||||
CHECK_IF_ABORTED();
|
||||
beginRecipe(stmt);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
/* ********************************
|
||||
* default
|
||||
* ********************************
|
||||
*/
|
||||
default:
|
||||
elog(WARN, "ProcessUtility: command #%d unsupported",
|
||||
nodeTag(parsetree));
|
||||
break;
|
||||
}
|
||||
|
||||
/* ----------------
|
||||
* tell fe/be or whatever that we're done.
|
||||
* ----------------
|
||||
*/
|
||||
EndCommand(commandTag, dest);
|
||||
}
|
||||
|
||||
18
src/backend/tcop/utility.h
Normal file
18
src/backend/tcop/utility.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* utility.h--
|
||||
* prototypes for utility.c.
|
||||
*
|
||||
*
|
||||
* Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $Id: utility.h,v 1.1.1.1 1996/07/09 06:22:00 scrappy Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef UTILITY_H
|
||||
#define UTILITY_H
|
||||
|
||||
extern void ProcessUtility(Node *parsetree, CommandDest dest);
|
||||
|
||||
#endif /* UTILITY_H */
|
||||
Reference in New Issue
Block a user