1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-13 07:41:39 +03:00

First cut at full support for OUTER JOINs. There are still a few loose

ends to clean up (see my message of same date to pghackers), but mostly
it works.  INITDB REQUIRED!
This commit is contained in:
Tom Lane
2000-09-12 21:07:18 +00:00
parent b5c0ab278b
commit ed5003c584
93 changed files with 6386 additions and 4262 deletions

View File

@ -1,9 +1,9 @@
/**********************************************************************
* get_ruledef.c - Function to get a rules definition text
* out of its tuple
* ruleutils.c - Functions to convert stored expressions/querytrees
* back to source text
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.60 2000/09/12 04:15:58 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.61 2000/09/12 21:07:05 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -43,6 +43,7 @@
#include "catalog/pg_index.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_shadow.h"
#include "commands/view.h"
#include "executor/spi.h"
#include "lib/stringinfo.h"
#include "optimizer/clauses.h"
@ -50,8 +51,8 @@
#include "parser/keywords.h"
#include "parser/parse_expr.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
#include "commands/view.h"
/* ----------
@ -65,12 +66,6 @@ typedef struct
bool varprefix; /* TRUE to print prefixes on Vars */
} deparse_context;
typedef struct
{
Index rt_index;
int levelsup;
} check_if_rte_used_context;
/* ----------
* Global data
@ -108,13 +103,13 @@ static void get_func_expr(Expr *expr, deparse_context *context);
static void get_tle_expr(TargetEntry *tle, deparse_context *context);
static void get_const_expr(Const *constval, deparse_context *context);
static void get_sublink_expr(Node *node, deparse_context *context);
static void get_from_clause(Query *query, deparse_context *context);
static void get_from_clause_item(Node *jtnode, Query *query,
deparse_context *context);
static bool tleIsArrayAssign(TargetEntry *tle);
static char *quote_identifier(char *ident);
static char *get_relation_name(Oid relid);
static char *get_attribute_name(Oid relid, int2 attnum);
static bool check_if_rte_used(Node *node, Index rt_index, int levelsup);
static bool check_if_rte_used_walker(Node *node,
check_if_rte_used_context *context);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@ -230,13 +225,13 @@ pg_get_viewdef(PG_FUNCTION_ARGS)
Name vname = PG_GETARG_NAME(0);
text *ruledef;
Datum args[1];
char nulls[2];
char nulls[1];
int spirc;
HeapTuple ruletup;
TupleDesc rulettc;
StringInfoData buf;
int len;
char *name;
char *name;
/* ----------
* We need the view name somewhere deep down
@ -276,7 +271,6 @@ pg_get_viewdef(PG_FUNCTION_ARGS)
name = MakeRetrieveViewRuleName(rulename);
args[0] = PointerGetDatum(name);
nulls[0] = ' ';
nulls[1] = '\0';
spirc = SPI_execp(plan_getview, args, nulls, 1);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for view %s", rulename);
@ -883,60 +877,8 @@ get_select_query_def(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
char *sep;
TargetEntry *tle;
RangeTblEntry *rte;
bool *rt_used;
int rt_length;
int rt_numused = 0;
bool rt_constonly = TRUE;
int i;
List *l;
/* ----------
* First we need to know which and how many of the
* range table entries in the query are used in the target list
* or queries qualification
* ----------
*/
rt_length = length(query->rtable);
rt_used = palloc(sizeof(bool) * rt_length);
for (i = 0; i < rt_length; i++)
{
if (check_if_rte_used((Node *) (query->targetList), i + 1, 0) ||
check_if_rte_used(query->qual, i + 1, 0) ||
check_if_rte_used(query->havingQual, i + 1, 0))
{
rt_used[i] = TRUE;
rt_numused++;
}
else
rt_used[i] = FALSE;
}
/* ----------
* Now check if any of the used rangetable entries is different
* from *NEW* and *OLD*. If so we must provide the FROM clause
* later.
* ----------
*/
i = 0;
foreach(l, query->rtable)
{
if (!rt_used[i++])
continue;
rte = (RangeTblEntry *) lfirst(l);
if (rte->ref == NULL)
continue;
if (strcmp(rte->ref->relname, "*NEW*") == 0)
continue;
if (strcmp(rte->ref->relname, "*OLD*") == 0)
continue;
rt_constonly = FALSE;
break;
}
/* ----------
* Build up the query string - first we say SELECT
* ----------
@ -947,9 +889,9 @@ get_select_query_def(Query *query, deparse_context *context)
sep = " ";
foreach(l, query->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
bool tell_as = false;
tle = (TargetEntry *) lfirst(l);
appendStringInfo(buf, sep);
sep = ", ";
@ -962,6 +904,7 @@ get_select_query_def(Query *query, deparse_context *context)
else
{
Var *var = (Var *) (tle->expr);
RangeTblEntry *rte;
char *attname;
rte = get_rte_for_var(var, context);
@ -975,60 +918,8 @@ get_select_query_def(Query *query, deparse_context *context)
quote_identifier(tle->resdom->resname));
}
/* If we need other tables than *NEW* or *OLD* add the FROM clause */
if (!rt_constonly && rt_numused > 0)
{
sep = " FROM ";
i = 0;
foreach(l, query->rtable)
{
if (rt_used[i++])
{
rte = (RangeTblEntry *) lfirst(l);
if (rte->ref == NULL)
continue;
if (strcmp(rte->ref->relname, "*NEW*") == 0)
continue;
if (strcmp(rte->ref->relname, "*OLD*") == 0)
continue;
appendStringInfo(buf, sep);
sep = ", ";
appendStringInfo(buf, "%s%s",
only_marker(rte),
quote_identifier(rte->relname));
/*
* NOTE: SQL92 says you can't write column aliases unless
* you write a table alias --- so, if there's an alias
* list, make sure we emit a table alias even if it's the
* same as the table's real name.
*/
if ((rte->ref != NULL)
&& ((strcmp(rte->relname, rte->ref->relname) != 0)
|| (rte->ref->attrs != NIL)))
{
appendStringInfo(buf, " %s",
quote_identifier(rte->ref->relname));
if (rte->ref->attrs != NIL)
{
List *col;
appendStringInfo(buf, " (");
foreach(col, rte->ref->attrs)
{
if (col != rte->ref->attrs)
appendStringInfo(buf, ", ");
appendStringInfo(buf, "%s",
quote_identifier(strVal(lfirst(col))));
}
appendStringInfoChar(buf, ')');
}
}
}
}
}
/* Add the FROM clause if needed */
get_from_clause(query, context);
/* Add the WHERE clause if given */
if (query->qual != NULL)
@ -1066,52 +957,32 @@ get_insert_query_def(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
char *sep;
TargetEntry *tle;
RangeTblEntry *rte;
bool *rt_used;
int rt_length;
int rt_numused = 0;
bool rt_constonly = TRUE;
RangeTblEntry *rte;
int i;
List *l;
/* ----------
* We need to know if other tables than *NEW* or *OLD*
* are used in the query. If not, it's an INSERT ... VALUES,
* otherwise an INSERT ... SELECT.
* otherwise an INSERT ... SELECT. (Pretty klugy ... fix this
* when we redesign querytrees!)
* ----------
*/
rt_length = length(query->rtable);
rt_used = palloc(sizeof(bool) * rt_length);
for (i = 0; i < rt_length; i++)
{
if (check_if_rte_used((Node *) (query->targetList), i + 1, 0) ||
check_if_rte_used(query->qual, i + 1, 0) ||
check_if_rte_used(query->havingQual, i + 1, 0))
{
rt_used[i] = TRUE;
rt_numused++;
}
else
rt_used[i] = FALSE;
}
i = 0;
foreach(l, query->rtable)
{
if (!rt_used[i++])
continue;
rte = (RangeTblEntry *) lfirst(l);
if (rte->ref == NULL)
i++;
if (strcmp(rte->eref->relname, "*NEW*") == 0)
continue;
if (strcmp(rte->ref->relname, "*NEW*") == 0)
if (strcmp(rte->eref->relname, "*OLD*") == 0)
continue;
if (strcmp(rte->ref->relname, "*OLD*") == 0)
continue;
rt_constonly = FALSE;
break;
if (rangeTableEntry_used((Node *) query, i, 0))
{
rt_constonly = FALSE;
break;
}
}
/* ----------
@ -1122,11 +993,11 @@ get_insert_query_def(Query *query, deparse_context *context)
appendStringInfo(buf, "INSERT INTO %s",
quote_identifier(rte->relname));
/* Add the target list */
/* Add the insert-column-names list */
sep = " (";
foreach(l, query->targetList)
{
tle = (TargetEntry *) lfirst(l);
TargetEntry *tle = (TargetEntry *) lfirst(l);
appendStringInfo(buf, sep);
sep = ", ";
@ -1141,7 +1012,7 @@ get_insert_query_def(Query *query, deparse_context *context)
sep = "";
foreach(l, query->targetList)
{
tle = (TargetEntry *) lfirst(l);
TargetEntry *tle = (TargetEntry *) lfirst(l);
appendStringInfo(buf, sep);
sep = ", ";
@ -1195,6 +1066,9 @@ get_update_query_def(Query *query, deparse_context *context)
get_tle_expr(tle, context);
}
/* Add the FROM clause if needed */
get_from_clause(query, context);
/* Finally add a WHERE clause if given */
if (query->qual != NULL)
{
@ -1281,16 +1155,13 @@ get_rule_expr(Node *node, deparse_context *context)
if (context->varprefix)
{
if (rte->ref == NULL)
appendStringInfo(buf, "%s.",
quote_identifier(rte->relname));
else if (strcmp(rte->ref->relname, "*NEW*") == 0)
if (strcmp(rte->eref->relname, "*NEW*") == 0)
appendStringInfo(buf, "new.");
else if (strcmp(rte->ref->relname, "*OLD*") == 0)
else if (strcmp(rte->eref->relname, "*OLD*") == 0)
appendStringInfo(buf, "old.");
else
appendStringInfo(buf, "%s.",
quote_identifier(rte->ref->relname));
quote_identifier(rte->eref->relname));
}
appendStringInfo(buf, "%s",
quote_identifier(get_attribute_name(rte->relid,
@ -1860,6 +1731,165 @@ get_sublink_expr(Node *node, deparse_context *context)
appendStringInfoChar(buf, ')');
}
/* ----------
* get_from_clause - Parse back a FROM clause
* ----------
*/
static void
get_from_clause(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
char *sep;
List *l;
/*
* We use the query's jointree as a guide to what to print. However,
* we must ignore auto-added RTEs that are marked not inFromCl.
* Also ignore the rule pseudo-RTEs for NEW and OLD.
*/
sep = " FROM ";
foreach(l, query->jointree)
{
Node *jtnode = (Node *) lfirst(l);
if (IsA(jtnode, RangeTblRef))
{
int varno = ((RangeTblRef *) jtnode)->rtindex;
RangeTblEntry *rte = rt_fetch(varno, query->rtable);
if (!rte->inFromCl)
continue;
if (strcmp(rte->eref->relname, "*NEW*") == 0)
continue;
if (strcmp(rte->eref->relname, "*OLD*") == 0)
continue;
}
appendStringInfo(buf, sep);
get_from_clause_item(jtnode, query, context);
sep = ", ";
}
}
static void
get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
if (IsA(jtnode, RangeTblRef))
{
int varno = ((RangeTblRef *) jtnode)->rtindex;
RangeTblEntry *rte = rt_fetch(varno, query->rtable);
appendStringInfo(buf, "%s%s",
only_marker(rte),
quote_identifier(rte->relname));
if (rte->alias != NULL)
{
appendStringInfo(buf, " %s",
quote_identifier(rte->alias->relname));
if (rte->alias->attrs != NIL)
{
List *col;
appendStringInfo(buf, " (");
foreach(col, rte->alias->attrs)
{
if (col != rte->alias->attrs)
appendStringInfo(buf, ", ");
appendStringInfo(buf, "%s",
quote_identifier(strVal(lfirst(col))));
}
appendStringInfoChar(buf, ')');
}
}
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
appendStringInfoChar(buf, '(');
get_from_clause_item(j->larg, query, context);
if (j->isNatural)
appendStringInfo(buf, " NATURAL");
switch (j->jointype)
{
case JOIN_INNER:
if (j->quals)
appendStringInfo(buf, " JOIN ");
else
appendStringInfo(buf, " CROSS JOIN ");
break;
case JOIN_LEFT:
appendStringInfo(buf, " LEFT JOIN ");
break;
case JOIN_FULL:
appendStringInfo(buf, " FULL JOIN ");
break;
case JOIN_RIGHT:
appendStringInfo(buf, " RIGHT JOIN ");
break;
case JOIN_UNION:
appendStringInfo(buf, " UNION JOIN ");
break;
default:
elog(ERROR, "get_from_clause_item: unknown join type %d",
(int) j->jointype);
}
get_from_clause_item(j->rarg, query, context);
if (! j->isNatural)
{
if (j->using)
{
List *col;
appendStringInfo(buf, " USING (");
foreach(col, j->using)
{
if (col != j->using)
appendStringInfo(buf, ", ");
appendStringInfo(buf, "%s",
quote_identifier(strVal(lfirst(col))));
}
appendStringInfoChar(buf, ')');
}
else if (j->quals)
{
appendStringInfo(buf, " ON (");
get_rule_expr(j->quals, context);
appendStringInfoChar(buf, ')');
}
}
appendStringInfoChar(buf, ')');
/* Yes, it's correct to put alias after the right paren ... */
if (j->alias != NULL)
{
appendStringInfo(buf, " %s",
quote_identifier(j->alias->relname));
if (j->alias->attrs != NIL)
{
List *col;
appendStringInfo(buf, " (");
foreach(col, j->alias->attrs)
{
if (col != j->alias->attrs)
appendStringInfo(buf, ", ");
appendStringInfo(buf, "%s",
quote_identifier(strVal(lfirst(col))));
}
appendStringInfoChar(buf, ')');
}
}
}
else
elog(ERROR, "get_from_clause_item: unexpected node type %d",
nodeTag(jtnode));
}
/* ----------
* tleIsArrayAssign - check for array assignment
* ----------
@ -1990,56 +2020,3 @@ get_attribute_name(Oid relid, int2 attnum)
attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
return pstrdup(NameStr(attStruct->attname));
}
/* ----------
* check_if_rte_used
* Check a targetlist or qual to see if a given rangetable entry
* is used in it
* ----------
*/
static bool
check_if_rte_used(Node *node, Index rt_index, int levelsup)
{
check_if_rte_used_context context;
context.rt_index = rt_index;
context.levelsup = levelsup;
return check_if_rte_used_walker(node, &context);
}
static bool
check_if_rte_used_walker(Node *node,
check_if_rte_used_context *context)
{
if (node == NULL)
return false;
if (IsA(node, Var))
{
Var *var = (Var *) node;
return var->varno == context->rt_index &&
var->varlevelsup == context->levelsup;
}
if (IsA(node, SubLink))
{
SubLink *sublink = (SubLink *) node;
Query *query = (Query *) sublink->subselect;
/* Recurse into subquery; expression_tree_walker will not */
if (check_if_rte_used((Node *) (query->targetList),
context->rt_index, context->levelsup + 1) ||
check_if_rte_used(query->qual,
context->rt_index, context->levelsup + 1) ||
check_if_rte_used(query->havingQual,
context->rt_index, context->levelsup + 1))
return true;
/*
* fall through to let expression_tree_walker examine lefthand
* args
*/
}
return expression_tree_walker(node, check_if_rte_used_walker,
(void *) context);
}