mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
737 lines
16 KiB
C
737 lines
16 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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.26 1997/10/25 05:34:07 thomas Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
#include "parser/dbcommands.h"
|
|
#include "access/xact.h"
|
|
#include "access/heapam.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/sequence.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 "commands/trigger.h"
|
|
|
|
#include "nodes/parsenodes.h"
|
|
#include "../backend/parser/parse.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"
|
|
#include "tcop/variable.h"
|
|
#include "tcop/utility.h"
|
|
#include "fmgr.h" /* For load_file() */
|
|
#include "storage/fd.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 = (stmt->ismove) ? "MOVE" : "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,
|
|
(stmt->ismove) ? None : dest); /* /dev/null for MOVE */
|
|
}
|
|
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;
|
|
Relation rel;
|
|
|
|
commandTag = "DROP";
|
|
CHECK_IF_ABORTED();
|
|
|
|
foreach(arg, args)
|
|
{
|
|
relname = strVal(lfirst(arg));
|
|
if (IsSystemRelationName(relname))
|
|
elog(WARN, "class \"%s\" is a system catalog",
|
|
relname);
|
|
rel = heap_openr(relname);
|
|
if (RelationIsValid(rel))
|
|
{
|
|
if (stmt->sequence &&
|
|
rel->rd_rel->relkind != RELKIND_SEQUENCE)
|
|
elog(WARN, "Use DROP TABLE to drop table '%s'",
|
|
relname);
|
|
if (!(stmt->sequence) &&
|
|
rel->rd_rel->relkind == RELKIND_SEQUENCE)
|
|
elog(WARN, "Use DROP SEQUENCE to drop sequence '%s'",
|
|
relname);
|
|
heap_close(rel);
|
|
}
|
|
#ifndef NO_SECURITY
|
|
if (!pg_ownercheck(userName, relname, RELNAME))
|
|
elog(WARN, "you do not own class \"%s\"",
|
|
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;
|
|
|
|
commandTag = "COPY";
|
|
CHECK_IF_ABORTED();
|
|
|
|
DoCopy(stmt->relname,
|
|
stmt->binary,
|
|
stmt->oids,
|
|
(bool) (stmt->direction == FROM),
|
|
(bool) (stmt->filename == NULL),
|
|
|
|
/*
|
|
* null filename means copy to/from stdout/stdin, rather
|
|
* than to/from a file.
|
|
*/
|
|
stmt->filename,
|
|
stmt->delimiter);
|
|
}
|
|
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\"",
|
|
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 TYPE_P:
|
|
{
|
|
DefineType(stmt->defname, stmt->definition);
|
|
}
|
|
break;
|
|
case AGGREGATE:
|
|
DefineAggregate(stmt->defname, /* aggregate name */
|
|
stmt->definition); /* rest */
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case T_ViewStmt: /* CREATE VIEW */
|
|
{
|
|
ViewStmt *stmt = (ViewStmt *) parsetree;
|
|
|
|
commandTag = "CREATE";
|
|
CHECK_IF_ABORTED();
|
|
DefineView(stmt->viewname, stmt->query); /* retrieve parsetree */
|
|
}
|
|
break;
|
|
|
|
case T_ProcedureStmt: /* CREATE FUNCTION */
|
|
commandTag = "CREATE";
|
|
CHECK_IF_ABORTED();
|
|
CreateFunction((ProcedureStmt *) parsetree, dest); /* everything */
|
|
break;
|
|
|
|
case T_IndexStmt: /* CREATE INDEX */
|
|
{
|
|
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,
|
|
stmt->unique,
|
|
(Expr *) stmt->whereClause,
|
|
stmt->rangetable);
|
|
}
|
|
break;
|
|
|
|
case T_RuleStmt: /* CREATE RULE */
|
|
{
|
|
RuleStmt *stmt = (RuleStmt *) parsetree;
|
|
int aclcheck_result;
|
|
|
|
#ifndef NO_SECURITY
|
|
relname = stmt->object->relname;
|
|
aclcheck_result = pg_aclcheck(relname, userName, ACL_RU);
|
|
if (aclcheck_result != ACLCHECK_OK)
|
|
elog(WARN, "%s: %s", relname, aclcheck_error_strings[aclcheck_result]);
|
|
#endif
|
|
commandTag = "CREATE";
|
|
CHECK_IF_ABORTED();
|
|
DefineQueryRewrite(stmt);
|
|
}
|
|
break;
|
|
|
|
case T_CreateSeqStmt:
|
|
commandTag = "CREATE";
|
|
CHECK_IF_ABORTED();
|
|
|
|
DefineSequence((CreateSeqStmt *) parsetree);
|
|
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 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, "%s: %s", relname, aclcheck_error_strings[ACLCHECK_NOT_OWNER]);
|
|
#endif
|
|
RemoveIndex(relname);
|
|
break;
|
|
case RULE:
|
|
{
|
|
char *rulename = stmt->name;
|
|
int aclcheck_result;
|
|
|
|
#ifndef NO_SECURITY
|
|
|
|
relationName = RewriteGetRuleEventRel(rulename);
|
|
aclcheck_result = pg_aclcheck(relationName, userName, ACL_RU);
|
|
if (aclcheck_result != ACLCHECK_OK)
|
|
{
|
|
elog(WARN, "%s: %s", relationName, aclcheck_error_strings[aclcheck_result]);
|
|
}
|
|
#endif
|
|
RemoveRewriteRule(rulename);
|
|
}
|
|
break;
|
|
case TYPE_P:
|
|
#ifndef NO_SECURITY
|
|
/* XXX moved to remove.c */
|
|
#endif
|
|
RemoveType(stmt->name);
|
|
break;
|
|
case VIEW:
|
|
{
|
|
char *viewName = stmt->name;
|
|
char *ruleName;
|
|
|
|
#ifndef NO_SECURITY
|
|
|
|
ruleName = MakeRetrieveViewRuleName(viewName);
|
|
relationName = RewriteGetRuleEventRel(ruleName);
|
|
if (!pg_ownercheck(userName, relationName, RELNAME))
|
|
elog(WARN, "%s: %s", relationName, aclcheck_error_strings[ACLCHECK_NOT_OWNER]);
|
|
pfree(ruleName);
|
|
#endif
|
|
RemoveView(viewName);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case T_RemoveAggrStmt:
|
|
{
|
|
RemoveAggrStmt *stmt = (RemoveAggrStmt *) parsetree;
|
|
|
|
commandTag = "DROP";
|
|
CHECK_IF_ABORTED();
|
|
RemoveAggregate(stmt->aggname, stmt->aggtype);
|
|
}
|
|
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;
|
|
char *filename;
|
|
|
|
commandTag = "LOAD";
|
|
CHECK_IF_ABORTED();
|
|
|
|
filename = stmt->filename;
|
|
closeAllVfds();
|
|
if ((fp = AllocateFile(filename, "r")) == NULL)
|
|
elog(WARN, "LOAD: could not open file %s", filename);
|
|
FreeFile(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,
|
|
((VacuumStmt *) parsetree)->verbose,
|
|
((VacuumStmt *) parsetree)->analyze,
|
|
((VacuumStmt *) parsetree)->va_spec);
|
|
break;
|
|
|
|
case T_ExplainStmt:
|
|
{
|
|
ExplainStmt *stmt = (ExplainStmt *) parsetree;
|
|
|
|
commandTag = "EXPLAIN";
|
|
CHECK_IF_ABORTED();
|
|
|
|
ExplainQuery(stmt->query, stmt->verbose, dest);
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* ******************************** Tioga-related statements *******************************
|
|
*/
|
|
case T_RecipeStmt:
|
|
{
|
|
RecipeStmt *stmt = (RecipeStmt *) parsetree;
|
|
|
|
commandTag = "EXECUTE RECIPE";
|
|
CHECK_IF_ABORTED();
|
|
beginRecipe(stmt);
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* ******************************** set variable statements *******************************
|
|
*/
|
|
case T_VariableSetStmt:
|
|
{
|
|
VariableSetStmt *n = (VariableSetStmt *) parsetree;
|
|
|
|
SetPGVariable(n->name, n->value);
|
|
commandTag = "SET VARIABLE";
|
|
}
|
|
break;
|
|
|
|
case T_VariableShowStmt:
|
|
{
|
|
VariableShowStmt *n = (VariableShowStmt *) parsetree;
|
|
|
|
GetPGVariable(n->name);
|
|
commandTag = "SHOW VARIABLE";
|
|
}
|
|
break;
|
|
|
|
case T_VariableResetStmt:
|
|
{
|
|
VariableResetStmt *n = (VariableResetStmt *) parsetree;
|
|
|
|
ResetPGVariable(n->name);
|
|
commandTag = "RESET VARIABLE";
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* ******************************** TRIGGER statements *******************************
|
|
*/
|
|
case T_CreateTrigStmt:
|
|
commandTag = "CREATE";
|
|
CHECK_IF_ABORTED();
|
|
|
|
CreateTrigger((CreateTrigStmt *) parsetree);
|
|
break;
|
|
|
|
case T_DropTrigStmt:
|
|
commandTag = "DROP";
|
|
CHECK_IF_ABORTED();
|
|
|
|
DropTrigger((DropTrigStmt *) parsetree);
|
|
break;
|
|
|
|
/*
|
|
* ******************************** default ********************************
|
|
*
|
|
*/
|
|
default:
|
|
elog(WARN, "ProcessUtility: command #%d unsupported",
|
|
nodeTag(parsetree));
|
|
break;
|
|
}
|
|
|
|
/* ----------------
|
|
* tell fe/be or whatever that we're done.
|
|
* ----------------
|
|
*/
|
|
EndCommand(commandTag, dest);
|
|
}
|