mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
Implement SQL-compliant treatment of row comparisons for < <= > >= cases
(previously we only did = and <> correctly). Also, allow row comparisons with any operators that are in btree opclasses, not only those with these specific names. This gets rid of a whole lot of indefensible assumptions about the behavior of particular operators based on their names ... though it's still true that IN and NOT IN expand to "= ANY". The patch adds a RowCompareExpr expression node type, and makes some changes in the representation of ANY/ALL/ROWCOMPARE SubLinks so that they can share code with RowCompareExpr. I have not yet done anything about making RowCompareExpr an indexable operator, but will look at that soon. initdb forced due to changes in stored rules.
This commit is contained in:
@ -3,7 +3,7 @@
|
||||
* back to source text
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.210 2005/12/10 19:21:03 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.211 2005/12/28 01:30:00 tgl Exp $
|
||||
*
|
||||
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||
*
|
||||
@ -215,7 +215,6 @@ static void printSubscripts(ArrayRef *aref, deparse_context *context);
|
||||
static char *generate_relation_name(Oid relid);
|
||||
static char *generate_function_name(Oid funcid, int nargs, Oid *argtypes);
|
||||
static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
|
||||
static void print_operator_name(StringInfo buf, List *opname);
|
||||
static text *string_to_text(char *str);
|
||||
|
||||
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
|
||||
@ -3106,6 +3105,7 @@ get_rule_expr(Node *node, deparse_context *context,
|
||||
break;
|
||||
case PARAM_NUM:
|
||||
case PARAM_EXEC:
|
||||
case PARAM_SUBLINK:
|
||||
appendStringInfo(buf, "$%d", param->paramid);
|
||||
break;
|
||||
default:
|
||||
@ -3514,6 +3514,50 @@ get_rule_expr(Node *node, deparse_context *context,
|
||||
}
|
||||
break;
|
||||
|
||||
case T_RowCompareExpr:
|
||||
{
|
||||
RowCompareExpr *rcexpr = (RowCompareExpr *) node;
|
||||
ListCell *arg;
|
||||
char *sep;
|
||||
|
||||
/*
|
||||
* SQL99 allows "ROW" to be omitted when there is more than
|
||||
* one column, but for simplicity we always print it.
|
||||
*/
|
||||
appendStringInfo(buf, "(ROW(");
|
||||
sep = "";
|
||||
foreach(arg, rcexpr->largs)
|
||||
{
|
||||
Node *e = (Node *) lfirst(arg);
|
||||
|
||||
appendStringInfoString(buf, sep);
|
||||
get_rule_expr(e, context, true);
|
||||
sep = ", ";
|
||||
}
|
||||
/*
|
||||
* We assume that the name of the first-column operator
|
||||
* will do for all the rest too. This is definitely
|
||||
* open to failure, eg if some but not all operators
|
||||
* were renamed since the construct was parsed, but there
|
||||
* seems no way to be perfect.
|
||||
*/
|
||||
appendStringInfo(buf, ") %s ROW(",
|
||||
generate_operator_name(linitial_oid(rcexpr->opnos),
|
||||
exprType(linitial(rcexpr->largs)),
|
||||
exprType(linitial(rcexpr->rargs))));
|
||||
sep = "";
|
||||
foreach(arg, rcexpr->rargs)
|
||||
{
|
||||
Node *e = (Node *) lfirst(arg);
|
||||
|
||||
appendStringInfoString(buf, sep);
|
||||
get_rule_expr(e, context, true);
|
||||
sep = ", ";
|
||||
}
|
||||
appendStringInfo(buf, "))");
|
||||
}
|
||||
break;
|
||||
|
||||
case T_CoalesceExpr:
|
||||
{
|
||||
CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
|
||||
@ -3967,6 +4011,7 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
Query *query = (Query *) (sublink->subselect);
|
||||
char *opname = NULL;
|
||||
bool need_paren;
|
||||
|
||||
if (sublink->subLinkType == ARRAY_SUBLINK)
|
||||
@ -3974,25 +4019,67 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
|
||||
else
|
||||
appendStringInfoChar(buf, '(');
|
||||
|
||||
if (sublink->lefthand != NIL)
|
||||
/*
|
||||
* Note that we print the name of only the first operator, when there
|
||||
* are multiple combining operators. This is an approximation that
|
||||
* could go wrong in various scenarios (operators in different schemas,
|
||||
* renamed operators, etc) but there is not a whole lot we can do about
|
||||
* it, since the syntax allows only one operator to be shown.
|
||||
*/
|
||||
if (sublink->testexpr)
|
||||
{
|
||||
need_paren = (list_length(sublink->lefthand) > 1);
|
||||
if (need_paren)
|
||||
if (IsA(sublink->testexpr, OpExpr))
|
||||
{
|
||||
/* single combining operator */
|
||||
OpExpr *opexpr = (OpExpr *) sublink->testexpr;
|
||||
|
||||
get_rule_expr(linitial(opexpr->args), context, true);
|
||||
opname = generate_operator_name(opexpr->opno,
|
||||
exprType(linitial(opexpr->args)),
|
||||
exprType(lsecond(opexpr->args)));
|
||||
}
|
||||
else if (IsA(sublink->testexpr, BoolExpr))
|
||||
{
|
||||
/* multiple combining operators, = or <> cases */
|
||||
char *sep;
|
||||
ListCell *l;
|
||||
|
||||
appendStringInfoChar(buf, '(');
|
||||
get_rule_expr((Node *) sublink->lefthand, context, true);
|
||||
if (need_paren)
|
||||
sep = "";
|
||||
foreach(l, ((BoolExpr *) sublink->testexpr)->args)
|
||||
{
|
||||
OpExpr *opexpr = (OpExpr *) lfirst(l);
|
||||
|
||||
Assert(IsA(opexpr, OpExpr));
|
||||
appendStringInfoString(buf, sep);
|
||||
get_rule_expr(linitial(opexpr->args), context, true);
|
||||
if (!opname)
|
||||
opname = generate_operator_name(opexpr->opno,
|
||||
exprType(linitial(opexpr->args)),
|
||||
exprType(lsecond(opexpr->args)));
|
||||
sep = ", ";
|
||||
}
|
||||
appendStringInfoChar(buf, ')');
|
||||
appendStringInfoChar(buf, ' ');
|
||||
}
|
||||
else if (IsA(sublink->testexpr, RowCompareExpr))
|
||||
{
|
||||
/* multiple combining operators, < <= > >= cases */
|
||||
RowCompareExpr *rcexpr = (RowCompareExpr *) sublink->testexpr;
|
||||
|
||||
appendStringInfoChar(buf, '(');
|
||||
get_rule_expr((Node *) rcexpr->largs, context, true);
|
||||
opname = generate_operator_name(linitial_oid(rcexpr->opnos),
|
||||
exprType(linitial(rcexpr->largs)),
|
||||
exprType(linitial(rcexpr->rargs)));
|
||||
appendStringInfoChar(buf, ')');
|
||||
}
|
||||
else
|
||||
elog(ERROR, "unrecognized testexpr type: %d",
|
||||
(int) nodeTag(sublink->testexpr));
|
||||
}
|
||||
|
||||
need_paren = true;
|
||||
|
||||
/*
|
||||
* XXX we regurgitate the originally given operator name, with or without
|
||||
* schema qualification. This is not necessarily 100% right but it's the
|
||||
* best we can do, since the operators actually used might not all be in
|
||||
* the same schema.
|
||||
*/
|
||||
switch (sublink->subLinkType)
|
||||
{
|
||||
case EXISTS_SUBLINK:
|
||||
@ -4000,27 +4087,18 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
|
||||
break;
|
||||
|
||||
case ANY_SUBLINK:
|
||||
if (list_length(sublink->operName) == 1 &&
|
||||
strcmp(strVal(linitial(sublink->operName)), "=") == 0)
|
||||
{
|
||||
/* Represent = ANY as IN */
|
||||
appendStringInfo(buf, "IN ");
|
||||
}
|
||||
if (strcmp(opname, "=") == 0) /* Represent = ANY as IN */
|
||||
appendStringInfo(buf, " IN ");
|
||||
else
|
||||
{
|
||||
print_operator_name(buf, sublink->operName);
|
||||
appendStringInfo(buf, " ANY ");
|
||||
}
|
||||
appendStringInfo(buf, " %s ANY ", opname);
|
||||
break;
|
||||
|
||||
case ALL_SUBLINK:
|
||||
print_operator_name(buf, sublink->operName);
|
||||
appendStringInfo(buf, " ALL ");
|
||||
appendStringInfo(buf, " %s ALL ", opname);
|
||||
break;
|
||||
|
||||
case MULTIEXPR_SUBLINK:
|
||||
print_operator_name(buf, sublink->operName);
|
||||
appendStringInfoChar(buf, ' ');
|
||||
case ROWCOMPARE_SUBLINK:
|
||||
appendStringInfo(buf, " %s ", opname);
|
||||
break;
|
||||
|
||||
case EXPR_SUBLINK:
|
||||
@ -4812,30 +4890,6 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2)
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
/*
|
||||
* Print out a possibly-qualified operator name
|
||||
*/
|
||||
static void
|
||||
print_operator_name(StringInfo buf, List *opname)
|
||||
{
|
||||
ListCell *op = list_head(opname);
|
||||
int nnames = list_length(opname);
|
||||
|
||||
if (nnames == 1)
|
||||
appendStringInfoString(buf, strVal(lfirst(op)));
|
||||
else
|
||||
{
|
||||
appendStringInfo(buf, "OPERATOR(");
|
||||
while (nnames-- > 1)
|
||||
{
|
||||
appendStringInfo(buf, "%s.",
|
||||
quote_identifier(strVal(lfirst(op))));
|
||||
op = lnext(op);
|
||||
}
|
||||
appendStringInfo(buf, "%s)", strVal(lfirst(op)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a C string, produce a TEXT datum.
|
||||
*
|
||||
|
144
src/backend/utils/cache/lsyscache.c
vendored
144
src/backend/utils/cache/lsyscache.c
vendored
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.130 2005/11/17 22:14:53 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.131 2005/12/28 01:30:01 tgl Exp $
|
||||
*
|
||||
* NOTES
|
||||
* Eventually, the index information should go through here, too.
|
||||
@ -183,6 +183,99 @@ get_op_hash_function(Oid opno)
|
||||
return InvalidOid;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_op_btree_interpretation
|
||||
* Given an operator's OID, find out which btree opclasses it belongs to,
|
||||
* and what strategy number it has within each one. The results are
|
||||
* returned as an OID list and a parallel integer list.
|
||||
*
|
||||
* In addition to the normal btree operators, we consider a <> operator to be
|
||||
* a "member" of an opclass if its negator is the opclass' equality operator.
|
||||
* ROWCOMPARE_NE is returned as the strategy number for this case.
|
||||
*/
|
||||
void
|
||||
get_op_btree_interpretation(Oid opno, List **opclasses, List **opstrats)
|
||||
{
|
||||
Oid lefttype,
|
||||
righttype;
|
||||
CatCList *catlist;
|
||||
bool op_negated;
|
||||
int i;
|
||||
|
||||
*opclasses = NIL;
|
||||
*opstrats = NIL;
|
||||
|
||||
/*
|
||||
* Get the nominal left-hand input type of the operator; we will ignore
|
||||
* opclasses that don't have that as the expected input datatype. This
|
||||
* is a kluge to avoid being confused by binary-compatible opclasses
|
||||
* (such as text_ops and varchar_ops, which share the same operators).
|
||||
*/
|
||||
op_input_types(opno, &lefttype, &righttype);
|
||||
Assert(OidIsValid(lefttype));
|
||||
|
||||
/*
|
||||
* Find all the pg_amop entries containing the operator.
|
||||
*/
|
||||
catlist = SearchSysCacheList(AMOPOPID, 1,
|
||||
ObjectIdGetDatum(opno),
|
||||
0, 0, 0);
|
||||
/*
|
||||
* If we can't find any opclass containing the op, perhaps it is a
|
||||
* <> operator. See if it has a negator that is in an opclass.
|
||||
*/
|
||||
op_negated = false;
|
||||
if (catlist->n_members == 0)
|
||||
{
|
||||
Oid op_negator = get_negator(opno);
|
||||
|
||||
if (OidIsValid(op_negator))
|
||||
{
|
||||
op_negated = true;
|
||||
ReleaseSysCacheList(catlist);
|
||||
catlist = SearchSysCacheList(AMOPOPID, 1,
|
||||
ObjectIdGetDatum(op_negator),
|
||||
0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Now search the opclasses */
|
||||
for (i = 0; i < catlist->n_members; i++)
|
||||
{
|
||||
HeapTuple op_tuple = &catlist->members[i]->tuple;
|
||||
Form_pg_amop op_form = (Form_pg_amop) GETSTRUCT(op_tuple);
|
||||
Oid opclass_id;
|
||||
StrategyNumber op_strategy;
|
||||
|
||||
opclass_id = op_form->amopclaid;
|
||||
|
||||
/* must be btree */
|
||||
if (!opclass_is_btree(opclass_id))
|
||||
continue;
|
||||
|
||||
/* must match operator input type exactly */
|
||||
if (get_opclass_input_type(opclass_id) != lefttype)
|
||||
continue;
|
||||
|
||||
/* Get the operator's btree strategy number */
|
||||
op_strategy = (StrategyNumber) op_form->amopstrategy;
|
||||
Assert(op_strategy >= 1 && op_strategy <= 5);
|
||||
|
||||
if (op_negated)
|
||||
{
|
||||
/* Only consider negators that are = */
|
||||
if (op_strategy != BTEqualStrategyNumber)
|
||||
continue;
|
||||
op_strategy = ROWCOMPARE_NE;
|
||||
}
|
||||
|
||||
*opclasses = lappend_oid(*opclasses, opclass_id);
|
||||
*opstrats = lappend_int(*opstrats, op_strategy);
|
||||
}
|
||||
|
||||
ReleaseSysCacheList(catlist);
|
||||
}
|
||||
|
||||
|
||||
/* ---------- AMPROC CACHES ---------- */
|
||||
|
||||
@ -433,6 +526,55 @@ opclass_is_hash(Oid opclass)
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* opclass_is_default
|
||||
*
|
||||
* Returns TRUE iff the specified opclass is the default for its
|
||||
* index access method and input data type.
|
||||
*/
|
||||
bool
|
||||
opclass_is_default(Oid opclass)
|
||||
{
|
||||
HeapTuple tp;
|
||||
Form_pg_opclass cla_tup;
|
||||
bool result;
|
||||
|
||||
tp = SearchSysCache(CLAOID,
|
||||
ObjectIdGetDatum(opclass),
|
||||
0, 0, 0);
|
||||
if (!HeapTupleIsValid(tp))
|
||||
elog(ERROR, "cache lookup failed for opclass %u", opclass);
|
||||
cla_tup = (Form_pg_opclass) GETSTRUCT(tp);
|
||||
|
||||
result = cla_tup->opcdefault;
|
||||
ReleaseSysCache(tp);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_opclass_input_type
|
||||
*
|
||||
* Returns the OID of the datatype the opclass indexes.
|
||||
*/
|
||||
Oid
|
||||
get_opclass_input_type(Oid opclass)
|
||||
{
|
||||
HeapTuple tp;
|
||||
Form_pg_opclass cla_tup;
|
||||
Oid result;
|
||||
|
||||
tp = SearchSysCache(CLAOID,
|
||||
ObjectIdGetDatum(opclass),
|
||||
0, 0, 0);
|
||||
if (!HeapTupleIsValid(tp))
|
||||
elog(ERROR, "cache lookup failed for opclass %u", opclass);
|
||||
cla_tup = (Form_pg_opclass) GETSTRUCT(tp);
|
||||
|
||||
result = cla_tup->opcintype;
|
||||
ReleaseSysCache(tp);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ---------- OPERATOR CACHE ---------- */
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user