aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/optimizer/path/indxpath.c559
-rw-r--r--src/backend/optimizer/util/pathnode.c9
-rw-r--r--src/backend/parser/parse_coerce.c150
-rw-r--r--src/backend/parser/parse_func.c213
-rw-r--r--src/backend/parser/parse_oper.c421
-rw-r--r--src/backend/utils/adt/selfuncs.c144
-rw-r--r--src/backend/utils/adt/varchar.c193
-rw-r--r--src/backend/utils/cache/lsyscache.c29
8 files changed, 622 insertions, 1096 deletions
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index b4da4666f1f..4529c30e626 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.139 2003/05/15 19:34:46 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.140 2003/05/26 00:11:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -20,6 +20,7 @@
#include "access/nbtree.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
@@ -30,6 +31,7 @@
#include "optimizer/paths.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/var.h"
+#include "parser/parse_expr.h"
#include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
@@ -80,17 +82,18 @@ static bool pred_test_simple_clause(Expr *predicate, Node *clause);
static Relids indexable_outerrelids(RelOptInfo *rel, IndexOptInfo *index);
static Path *make_innerjoin_index_path(Query *root,
RelOptInfo *rel, IndexOptInfo *index,
- List *clausegroup);
+ List *clausegroups);
static bool match_index_to_operand(int indexkey, Node *operand,
RelOptInfo *rel, IndexOptInfo *index);
static bool function_index_operand(Expr *funcOpnd, RelOptInfo *rel,
IndexOptInfo *index);
static bool match_special_index_operator(Expr *clause, Oid opclass,
bool indexkey_on_left);
-static List *prefix_quals(Node *leftop, Oid expr_op,
+static List *expand_indexqual_condition(Expr *clause, Oid opclass);
+static List *prefix_quals(Node *leftop, Oid opclass,
Const *prefix, Pattern_Prefix_Status pstatus);
-static List *network_prefix_quals(Node *leftop, Oid expr_op, Datum rightop);
-static Oid find_operator(const char *opname, Oid datatype);
+static List *network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass,
+ Datum rightop);
static Datum string_to_datum(const char *str, Oid datatype);
static Const *string_to_const(const char *str, Oid datatype);
@@ -411,7 +414,7 @@ match_or_subclause_to_indexkey(RelOptInfo *rel,
* Currently we'll end up rechecking both the OR clause and the transferred
* restriction clause as qpquals. FIXME someday.)
*
- * Also, we apply expand_indexqual_conditions() to convert any special
+ * Also, we apply expand_indexqual_condition() to convert any special
* matching opclauses to indexable operators.
*
* The passed-in clause is not changed.
@@ -430,7 +433,7 @@ extract_or_indexqual_conditions(RelOptInfo *rel,
* Extract relevant indexclauses in indexkey order. This is
* essentially just like group_clauses_by_indexkey() except that the
* input and output are lists of bare clauses, not of RestrictInfo
- * nodes.
+ * nodes, and that we expand special operators immediately.
*/
do
{
@@ -448,13 +451,15 @@ extract_or_indexqual_conditions(RelOptInfo *rel,
if (match_clause_to_indexkey(rel, index,
curIndxKey, curClass,
subsubclause))
- clausegroup = lappend(clausegroup, subsubclause);
+ clausegroup = nconc(clausegroup,
+ expand_indexqual_condition(subsubclause,
+ curClass));
}
}
else if (match_clause_to_indexkey(rel, index,
curIndxKey, curClass,
orsubclause))
- clausegroup = makeList1(orsubclause);
+ clausegroup = expand_indexqual_condition(orsubclause, curClass);
/*
* If we found no clauses for this indexkey in the OR subclause
@@ -469,7 +474,9 @@ extract_or_indexqual_conditions(RelOptInfo *rel,
if (match_clause_to_indexkey(rel, index,
curIndxKey, curClass,
rinfo->clause))
- clausegroup = lappend(clausegroup, rinfo->clause);
+ clausegroup = nconc(clausegroup,
+ expand_indexqual_condition(rinfo->clause,
+ curClass));
}
}
@@ -490,7 +497,7 @@ extract_or_indexqual_conditions(RelOptInfo *rel,
if (quals == NIL)
elog(ERROR, "extract_or_indexqual_conditions: no matching clause");
- return expand_indexqual_conditions(quals);
+ return quals;
}
@@ -501,26 +508,23 @@ extract_or_indexqual_conditions(RelOptInfo *rel,
/*
* group_clauses_by_indexkey
- * Generates a list of restriction clauses that can be used with an index.
+ * Find restriction clauses that can be used with an index.
*
* 'rel' is the node of the relation itself.
* 'index' is a index on 'rel'.
*
- * Returns a list of all the RestrictInfo nodes for clauses that can be
- * used with this index.
- *
- * The list is ordered by index key. (This is not depended on by any part
- * of the planner, so far as I can tell; but some parts of the executor
- * do assume that the indxqual list ultimately delivered to the executor
- * is so ordered. One such place is _bt_orderkeys() in the btree support.
- * Perhaps that ought to be fixed someday --- tgl 7/00)
+ * Returns a list of sublists of RestrictInfo nodes for clauses that can be
+ * used with this index. Each sublist contains clauses that can be used
+ * with one index key (in no particular order); the top list is ordered by
+ * index key. (This is depended on by expand_indexqual_conditions().)
*
* Note that in a multi-key index, we stop if we find a key that cannot be
* used with any clause. For example, given an index on (A,B,C), we might
- * return (C1 C2 C3 C4) if we find that clauses C1 and C2 use column A,
+ * return ((C1 C2) (C3 C4)) if we find that clauses C1 and C2 use column A,
* clauses C3 and C4 use column B, and no clauses use column C. But if
- * no clauses match B we will return (C1 C2), whether or not there are
+ * no clauses match B we will return ((C1 C2)), whether or not there are
* clauses matching column C, because the executor couldn't use them anyway.
+ * Therefore, there are no empty sublists in the result.
*/
static List *
group_clauses_by_indexkey(RelOptInfo *rel, IndexOptInfo *index)
@@ -559,20 +563,19 @@ group_clauses_by_indexkey(RelOptInfo *rel, IndexOptInfo *index)
if (clausegroup == NIL)
break;
- clausegroup_list = nconc(clausegroup_list, clausegroup);
+ clausegroup_list = lappend(clausegroup_list, clausegroup);
indexkeys++;
classes++;
} while (!DoneMatchingIndexKeys(indexkeys, classes));
- /* clausegroup_list holds all matched clauses ordered by indexkeys */
return clausegroup_list;
}
/*
* group_clauses_by_indexkey_for_join
- * Generates a list of clauses that can be used with an index
+ * Generate a list of sublists of clauses that can be used with an index
* to scan the inner side of a nestloop join.
*
* This is much like group_clauses_by_indexkey(), but we consider both
@@ -652,23 +655,20 @@ group_clauses_by_indexkey_for_join(RelOptInfo *rel, IndexOptInfo *index,
if (clausegroup == NIL)
break;
- clausegroup_list = nconc(clausegroup_list, clausegroup);
+ clausegroup_list = lappend(clausegroup_list, clausegroup);
indexkeys++;
classes++;
} while (!DoneMatchingIndexKeys(indexkeys, classes));
- /*
- * if no join clause was matched then forget it, per comments above.
- */
+ /* if no join clause was matched then forget it, per comments above */
if (!jfound)
{
freeList(clausegroup_list);
return NIL;
}
- /* clausegroup_list holds all matched clauses ordered by indexkeys */
return clausegroup_list;
}
@@ -1124,8 +1124,6 @@ pred_test_simple_clause(Expr *predicate, Node *clause)
ExprState *test_exprstate;
Datum test_result;
bool isNull;
- HeapTuple test_tuple;
- Form_pg_amop test_form;
CatCList *catlist;
int i;
EState *estate;
@@ -1241,22 +1239,13 @@ pred_test_simple_clause(Expr *predicate, Node *clause)
/*
* 3. From the same opclass, find the operator for the test strategy
*/
- test_tuple = SearchSysCache(AMOPSTRATEGY,
- ObjectIdGetDatum(opclass_id),
- Int16GetDatum(test_strategy),
- 0, 0);
- if (!HeapTupleIsValid(test_tuple))
+ test_op = get_opclass_member(opclass_id, test_strategy);
+ if (!OidIsValid(test_op))
{
/* This should not fail, else pg_amop entry is missing */
elog(ERROR, "Missing pg_amop entry for opclass %u strategy %d",
opclass_id, test_strategy);
}
- test_form = (Form_pg_amop) GETSTRUCT(test_tuple);
-
- /* Get the test operator */
- test_op = test_form->amopopr;
-
- ReleaseSysCache(test_tuple);
/*
* 4. Evaluate the test. For this we need an EState.
@@ -1488,22 +1477,18 @@ best_inner_indexscan(Query *root, RelOptInfo *rel,
if (jlist == NIL) /* failed to find a match? */
{
- List *clausegroup;
+ List *clausegroups;
/* find useful clauses for this index and outerjoin set */
- clausegroup = group_clauses_by_indexkey_for_join(rel,
- index,
- index_outer_relids,
- isouterjoin);
- if (clausegroup)
+ clausegroups = group_clauses_by_indexkey_for_join(rel,
+ index,
+ index_outer_relids,
+ isouterjoin);
+ if (clausegroups)
{
- /* remove duplicate and redundant clauses */
- clausegroup = remove_redundant_join_clauses(root,
- clausegroup,
- jointype);
/* make the path */
path = make_innerjoin_index_path(root, rel, index,
- clausegroup);
+ clausegroups);
}
/* Cache the result --- whether positive or negative */
@@ -1542,15 +1527,17 @@ best_inner_indexscan(Query *root, RelOptInfo *rel,
* relation in a nestloop join.
*
* 'rel' is the relation for which 'index' is defined
- * 'clausegroup' is a list of restrictinfo nodes that can use 'index'
+ * 'clausegroups' is a list of lists of RestrictInfos that can use 'index'
*/
static Path *
make_innerjoin_index_path(Query *root,
RelOptInfo *rel, IndexOptInfo *index,
- List *clausegroup)
+ List *clausegroups)
{
IndexPath *pathnode = makeNode(IndexPath);
- List *indexquals;
+ List *indexquals,
+ *allclauses,
+ *l;
/* XXX this code ought to be merged with create_index_path? */
@@ -1564,11 +1551,8 @@ make_innerjoin_index_path(Query *root,
*/
pathnode->path.pathkeys = NIL;
- /* Extract bare indexqual clauses from restrictinfos */
- indexquals = get_actual_clauses(clausegroup);
-
- /* expand special operators to indexquals the executor can handle */
- indexquals = expand_indexqual_conditions(indexquals);
+ /* Convert RestrictInfo nodes to indexquals the executor can handle */
+ indexquals = expand_indexqual_conditions(index, clausegroups);
/*
* Note that we are making a pathnode for a single-scan indexscan;
@@ -1583,24 +1567,31 @@ make_innerjoin_index_path(Query *root,
/*
* We must compute the estimated number of output rows for the
* indexscan. This is less than rel->rows because of the
- * additional selectivity of the join clauses. Since clausegroup
+ * additional selectivity of the join clauses. Since clausegroups
* may contain both restriction and join clauses, we have to do a
* set union to get the full set of clauses that must be
- * considered to compute the correct selectivity. (We can't just
- * nconc the two lists; then we might have some restriction
- * clauses appearing twice, which'd mislead
- * restrictlist_selectivity into double-counting their
+ * considered to compute the correct selectivity. (Without the union
+ * operation, we might have some restriction clauses appearing twice,
+ * which'd mislead restrictlist_selectivity into double-counting their
* selectivity. However, since RestrictInfo nodes aren't copied when
* linking them into different lists, it should be sufficient to use
* pointer comparison to remove duplicates.)
*
+ * We assume we can destructively modify the input sublists.
+ *
* Always assume the join type is JOIN_INNER; even if some of the
* join clauses come from other contexts, that's not our problem.
*/
+ allclauses = NIL;
+ foreach(l, clausegroups)
+ {
+ /* nconc okay here since same clause couldn't be in two sublists */
+ allclauses = nconc(allclauses, (List *) lfirst(l));
+ }
+ allclauses = set_ptrUnion(rel->baserestrictinfo, allclauses);
pathnode->rows = rel->tuples *
restrictlist_selectivity(root,
- set_ptrUnion(rel->baserestrictinfo,
- clausegroup),
+ allclauses,
rel->relid,
JOIN_INNER);
/* Like costsize.c, force estimate to be at least one row */
@@ -1741,10 +1732,10 @@ function_index_operand(Expr *funcOpnd, RelOptInfo *rel, IndexOptInfo *index)
* the latter fails to recognize a restriction opclause's operator
* as a member of an index's opclass, it asks match_special_index_operator()
* whether the clause should be considered an indexqual anyway.
- * expand_indexqual_conditions() converts a list of "raw" indexqual
- * conditions (with implicit AND semantics across list elements) into
- * a list that the executor can actually handle. For operators that
- * are members of the index's opclass this transformation is a no-op,
+ * expand_indexqual_conditions() converts a list of lists of RestrictInfo
+ * nodes (with implicit AND semantics across list elements) into
+ * a list of clauses that the executor can actually handle. For operators
+ * that are members of the index's opclass this transformation is a no-op,
* but operators recognized by match_special_index_operator() must be
* converted into one or more "regular" indexqual conditions.
*----------
@@ -1765,10 +1756,9 @@ match_special_index_operator(Expr *clause, Oid opclass,
bool indexkey_on_left)
{
bool isIndexable = false;
- Node *leftop,
- *rightop;
+ Node *rightop;
Oid expr_op;
- Const *patt = NULL;
+ Const *patt;
Const *prefix = NULL;
Const *rest = NULL;
@@ -1781,7 +1771,6 @@ match_special_index_operator(Expr *clause, Oid opclass,
return false;
/* we know these will succeed */
- leftop = get_leftop(clause);
rightop = get_rightop(clause);
expr_op = ((OpExpr *) clause)->opno;
@@ -1795,7 +1784,6 @@ match_special_index_operator(Expr *clause, Oid opclass,
{
case OID_TEXT_LIKE_OP:
case OID_BPCHAR_LIKE_OP:
- case OID_VARCHAR_LIKE_OP:
case OID_NAME_LIKE_OP:
/* the right-hand const is type text for all of these */
isIndexable = pattern_fixed_prefix(patt, Pattern_Type_Like,
@@ -1809,7 +1797,6 @@ match_special_index_operator(Expr *clause, Oid opclass,
case OID_TEXT_ICLIKE_OP:
case OID_BPCHAR_ICLIKE_OP:
- case OID_VARCHAR_ICLIKE_OP:
case OID_NAME_ICLIKE_OP:
/* the right-hand const is type text for all of these */
isIndexable = pattern_fixed_prefix(patt, Pattern_Type_Like_IC,
@@ -1818,7 +1805,6 @@ match_special_index_operator(Expr *clause, Oid opclass,
case OID_TEXT_REGEXEQ_OP:
case OID_BPCHAR_REGEXEQ_OP:
- case OID_VARCHAR_REGEXEQ_OP:
case OID_NAME_REGEXEQ_OP:
/* the right-hand const is type text for all of these */
isIndexable = pattern_fixed_prefix(patt, Pattern_Type_Regex,
@@ -1827,7 +1813,6 @@ match_special_index_operator(Expr *clause, Oid opclass,
case OID_TEXT_ICREGEXEQ_OP:
case OID_BPCHAR_ICREGEXEQ_OP:
- case OID_VARCHAR_ICREGEXEQ_OP:
case OID_NAME_ICREGEXEQ_OP:
/* the right-hand const is type text for all of these */
isIndexable = pattern_fixed_prefix(patt, Pattern_Type_Regex_IC,
@@ -1855,8 +1840,11 @@ match_special_index_operator(Expr *clause, Oid opclass,
/*
* Must also check that index's opclass supports the operators we will
* want to apply. (A hash index, for example, will not support ">=".)
- * We cheat a little by not checking for availability of "=" ... any
- * index type should support "=", methinks.
+ * Currently, only btree supports the operators we need.
+ *
+ * We insist on the opclass being the specific one we expect,
+ * else we'd do the wrong thing if someone were to make a reverse-sort
+ * opclass with the same operators.
*/
switch (expr_op)
{
@@ -1864,69 +1852,44 @@ match_special_index_operator(Expr *clause, Oid opclass,
case OID_TEXT_ICLIKE_OP:
case OID_TEXT_REGEXEQ_OP:
case OID_TEXT_ICREGEXEQ_OP:
- if (lc_collate_is_c())
- isIndexable = (op_in_opclass(find_operator(">=", TEXTOID), opclass)
- && op_in_opclass(find_operator("<", TEXTOID), opclass));
- else
- isIndexable = (op_in_opclass(find_operator("~>=~", TEXTOID), opclass)
- && op_in_opclass(find_operator("~<~", TEXTOID), opclass));
- break;
-
- case OID_BYTEA_LIKE_OP:
- isIndexable = (op_in_opclass(find_operator(">=", BYTEAOID), opclass)
- && op_in_opclass(find_operator("<", BYTEAOID), opclass));
+ /* text operators will be used for varchar inputs, too */
+ isIndexable =
+ (opclass == TEXT_PATTERN_BTREE_OPS_OID) ||
+ (opclass == TEXT_BTREE_OPS_OID && lc_collate_is_c()) ||
+ (opclass == VARCHAR_PATTERN_BTREE_OPS_OID) ||
+ (opclass == VARCHAR_BTREE_OPS_OID && lc_collate_is_c());
break;
case OID_BPCHAR_LIKE_OP:
case OID_BPCHAR_ICLIKE_OP:
case OID_BPCHAR_REGEXEQ_OP:
case OID_BPCHAR_ICREGEXEQ_OP:
- if (lc_collate_is_c())
- isIndexable = (op_in_opclass(find_operator(">=", BPCHAROID), opclass)
- && op_in_opclass(find_operator("<", BPCHAROID), opclass));
- else
- isIndexable = (op_in_opclass(find_operator("~>=~", BPCHAROID), opclass)
- && op_in_opclass(find_operator("~<~", BPCHAROID), opclass));
- break;
-
- case OID_VARCHAR_LIKE_OP:
- case OID_VARCHAR_ICLIKE_OP:
- case OID_VARCHAR_REGEXEQ_OP:
- case OID_VARCHAR_ICREGEXEQ_OP:
- if (lc_collate_is_c())
- isIndexable = (op_in_opclass(find_operator(">=", VARCHAROID), opclass)
- && op_in_opclass(find_operator("<", VARCHAROID), opclass));
- else
- isIndexable = (op_in_opclass(find_operator("~>=~", VARCHAROID), opclass)
- && op_in_opclass(find_operator("~<~", VARCHAROID), opclass));
+ isIndexable =
+ (opclass == BPCHAR_PATTERN_BTREE_OPS_OID) ||
+ (opclass == BPCHAR_BTREE_OPS_OID && lc_collate_is_c());
break;
case OID_NAME_LIKE_OP:
case OID_NAME_ICLIKE_OP:
case OID_NAME_REGEXEQ_OP:
case OID_NAME_ICREGEXEQ_OP:
- if (lc_collate_is_c())
- isIndexable = (op_in_opclass(find_operator(">=", NAMEOID), opclass)
- && op_in_opclass(find_operator("<", NAMEOID), opclass));
- else
- isIndexable = (op_in_opclass(find_operator("~>=~", NAMEOID), opclass)
- && op_in_opclass(find_operator("~<~", NAMEOID), opclass));
+ isIndexable =
+ (opclass == NAME_PATTERN_BTREE_OPS_OID) ||
+ (opclass == NAME_BTREE_OPS_OID && lc_collate_is_c());
+ break;
+
+ case OID_BYTEA_LIKE_OP:
+ isIndexable = (opclass == BYTEA_BTREE_OPS_OID);
break;
case OID_INET_SUB_OP:
case OID_INET_SUBEQ_OP:
- /* for SUB we actually need ">" not ">=", but this should do */
- if (!op_in_opclass(find_operator(">=", INETOID), opclass) ||
- !op_in_opclass(find_operator("<=", INETOID), opclass))
- isIndexable = false;
+ isIndexable = (opclass == INET_BTREE_OPS_OID);
break;
case OID_CIDR_SUB_OP:
case OID_CIDR_SUBEQ_OP:
- /* for SUB we actually need ">" not ">=", but this should do */
- if (!op_in_opclass(find_operator(">=", CIDROID), opclass) ||
- !op_in_opclass(find_operator("<=", CIDROID), opclass))
- isIndexable = false;
+ isIndexable = (opclass == CIDR_BTREE_OPS_OID);
break;
}
@@ -1935,185 +1898,217 @@ match_special_index_operator(Expr *clause, Oid opclass,
/*
* expand_indexqual_conditions
- * Given a list of (implicitly ANDed) indexqual clauses,
- * expand any "special" index operators into clauses that the indexscan
- * machinery will know what to do with. Clauses that were not
- * recognized by match_special_index_operator() must be passed through
- * unchanged.
+ * Given a list of sublists of RestrictInfo nodes, produce a flat list
+ * of index qual clauses. Standard qual clauses (those in the index's
+ * opclass) are passed through unchanged. "Special" index operators
+ * are expanded into clauses that the indexscan machinery will know
+ * what to do with.
+ *
+ * The input list is ordered by index key, and so the output list is too.
+ * (The latter is not depended on by any part of the planner, so far as I can
+ * tell; but some parts of the executor do assume that the indxqual list
+ * ultimately delivered to the executor is so ordered. One such place is
+ * _bt_orderkeys() in the btree support. Perhaps that ought to be fixed
+ * someday --- tgl 7/00)
*/
List *
-expand_indexqual_conditions(List *indexquals)
+expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
{
List *resultquals = NIL;
- List *q;
+ int *indexkeys = index->indexkeys;
+ Oid *classes = index->classlist;
+
+ if (clausegroups == NIL)
+ return NIL;
- foreach(q, indexquals)
+ do
{
- Expr *clause = (Expr *) lfirst(q);
-
- /* we know these will succeed */
- Node *leftop = get_leftop(clause);
- Node *rightop = get_rightop(clause);
- Oid expr_op = ((OpExpr *) clause)->opno;
- Const *patt = (Const *) rightop;
- Const *prefix = NULL;
- Const *rest = NULL;
- Pattern_Prefix_Status pstatus;
-
- switch (expr_op)
+ Oid curClass = classes[0];
+ List *i;
+
+ foreach(i, (List *) lfirst(clausegroups))
{
- /*
- * LIKE and regex operators are not members of any index
- * opclass, so if we find one in an indexqual list we can
- * assume that it was accepted by
- * match_special_index_operator().
- */
- case OID_TEXT_LIKE_OP:
- case OID_BPCHAR_LIKE_OP:
- case OID_VARCHAR_LIKE_OP:
- case OID_NAME_LIKE_OP:
- case OID_BYTEA_LIKE_OP:
- pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like,
- &prefix, &rest);
- resultquals = nconc(resultquals,
- prefix_quals(leftop, expr_op,
- prefix, pstatus));
- break;
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(i);
- case OID_TEXT_ICLIKE_OP:
- case OID_BPCHAR_ICLIKE_OP:
- case OID_VARCHAR_ICLIKE_OP:
- case OID_NAME_ICLIKE_OP:
- /* the right-hand const is type text for all of these */
- pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like_IC,
- &prefix, &rest);
- resultquals = nconc(resultquals,
- prefix_quals(leftop, expr_op,
- prefix, pstatus));
- break;
+ resultquals = nconc(resultquals,
+ expand_indexqual_condition(rinfo->clause,
+ curClass));
+ }
- case OID_TEXT_REGEXEQ_OP:
- case OID_BPCHAR_REGEXEQ_OP:
- case OID_VARCHAR_REGEXEQ_OP:
- case OID_NAME_REGEXEQ_OP:
- /* the right-hand const is type text for all of these */
- pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex,
- &prefix, &rest);
- resultquals = nconc(resultquals,
- prefix_quals(leftop, expr_op,
- prefix, pstatus));
- break;
+ clausegroups = lnext(clausegroups);
- case OID_TEXT_ICREGEXEQ_OP:
- case OID_BPCHAR_ICREGEXEQ_OP:
- case OID_VARCHAR_ICREGEXEQ_OP:
- case OID_NAME_ICREGEXEQ_OP:
- /* the right-hand const is type text for all of these */
- pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex_IC,
- &prefix, &rest);
- resultquals = nconc(resultquals,
- prefix_quals(leftop, expr_op,
- prefix, pstatus));
- break;
+ indexkeys++;
+ classes++;
- case OID_INET_SUB_OP:
- case OID_INET_SUBEQ_OP:
- case OID_CIDR_SUB_OP:
- case OID_CIDR_SUBEQ_OP:
- resultquals = nconc(resultquals,
- network_prefix_quals(leftop, expr_op,
- patt->constvalue));
- break;
+ } while (clausegroups != NIL &&
+ !DoneMatchingIndexKeys(indexkeys, classes));
- default:
- resultquals = lappend(resultquals, clause);
- break;
- }
- }
+ Assert(clausegroups == NIL); /* else more groups than indexkeys... */
return resultquals;
}
/*
+ * expand_indexqual_condition --- expand a single indexqual condition
+ */
+static List *
+expand_indexqual_condition(Expr *clause, Oid opclass)
+{
+ /* we know these will succeed */
+ Node *leftop = get_leftop(clause);
+ Node *rightop = get_rightop(clause);
+ Oid expr_op = ((OpExpr *) clause)->opno;
+ Const *patt = (Const *) rightop;
+ Const *prefix = NULL;
+ Const *rest = NULL;
+ Pattern_Prefix_Status pstatus;
+ List *result;
+
+ switch (expr_op)
+ {
+ /*
+ * LIKE and regex operators are not members of any index
+ * opclass, so if we find one in an indexqual list we can
+ * assume that it was accepted by match_special_index_operator().
+ */
+ case OID_TEXT_LIKE_OP:
+ case OID_BPCHAR_LIKE_OP:
+ case OID_NAME_LIKE_OP:
+ case OID_BYTEA_LIKE_OP:
+ pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like,
+ &prefix, &rest);
+ result = prefix_quals(leftop, opclass, prefix, pstatus);
+ break;
+
+ case OID_TEXT_ICLIKE_OP:
+ case OID_BPCHAR_ICLIKE_OP:
+ case OID_NAME_ICLIKE_OP:
+ /* the right-hand const is type text for all of these */
+ pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like_IC,
+ &prefix, &rest);
+ result = prefix_quals(leftop, opclass, prefix, pstatus);
+ break;
+
+ case OID_TEXT_REGEXEQ_OP:
+ case OID_BPCHAR_REGEXEQ_OP:
+ case OID_NAME_REGEXEQ_OP:
+ /* the right-hand const is type text for all of these */
+ pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex,
+ &prefix, &rest);
+ result = prefix_quals(leftop, opclass, prefix, pstatus);
+ break;
+
+ case OID_TEXT_ICREGEXEQ_OP:
+ case OID_BPCHAR_ICREGEXEQ_OP:
+ case OID_NAME_ICREGEXEQ_OP:
+ /* the right-hand const is type text for all of these */
+ pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex_IC,
+ &prefix, &rest);
+ result = prefix_quals(leftop, opclass, prefix, pstatus);
+ break;
+
+ case OID_INET_SUB_OP:
+ case OID_INET_SUBEQ_OP:
+ case OID_CIDR_SUB_OP:
+ case OID_CIDR_SUBEQ_OP:
+ result = network_prefix_quals(leftop, expr_op, opclass,
+ patt->constvalue);
+ break;
+
+ default:
+ result = makeList1(clause);
+ break;
+ }
+
+ return result;
+}
+
+/*
* Given a fixed prefix that all the "leftop" values must have,
- * generate suitable indexqual condition(s). expr_op is the original
- * LIKE or regex operator; we use it to deduce the appropriate comparison
+ * generate suitable indexqual condition(s). opclass is the index
+ * operator class; we use it to deduce the appropriate comparison
* operators and operand datatypes.
*/
static List *
-prefix_quals(Node *leftop, Oid expr_op,
+prefix_quals(Node *leftop, Oid opclass,
Const *prefix_const, Pattern_Prefix_Status pstatus)
{
List *result;
Oid datatype;
Oid oproid;
- const char *oprname;
- char *prefix;
- Const *con;
Expr *expr;
- Const *greaterstr = NULL;
+ Const *greaterstr;
Assert(pstatus != Pattern_Prefix_None);
- switch (expr_op)
+ switch (opclass)
{
- case OID_TEXT_LIKE_OP:
- case OID_TEXT_ICLIKE_OP:
- case OID_TEXT_REGEXEQ_OP:
- case OID_TEXT_ICREGEXEQ_OP:
+ case TEXT_BTREE_OPS_OID:
+ case TEXT_PATTERN_BTREE_OPS_OID:
datatype = TEXTOID;
break;
- case OID_BYTEA_LIKE_OP:
- datatype = BYTEAOID;
+ case VARCHAR_BTREE_OPS_OID:
+ case VARCHAR_PATTERN_BTREE_OPS_OID:
+ datatype = VARCHAROID;
break;
- case OID_BPCHAR_LIKE_OP:
- case OID_BPCHAR_ICLIKE_OP:
- case OID_BPCHAR_REGEXEQ_OP:
- case OID_BPCHAR_ICREGEXEQ_OP:
+ case BPCHAR_BTREE_OPS_OID:
+ case BPCHAR_PATTERN_BTREE_OPS_OID:
datatype = BPCHAROID;
break;
- case OID_VARCHAR_LIKE_OP:
- case OID_VARCHAR_ICLIKE_OP:
- case OID_VARCHAR_REGEXEQ_OP:
- case OID_VARCHAR_ICREGEXEQ_OP:
- datatype = VARCHAROID;
+ case NAME_BTREE_OPS_OID:
+ case NAME_PATTERN_BTREE_OPS_OID:
+ datatype = NAMEOID;
break;
- case OID_NAME_LIKE_OP:
- case OID_NAME_ICLIKE_OP:
- case OID_NAME_REGEXEQ_OP:
- case OID_NAME_ICREGEXEQ_OP:
- datatype = NAMEOID;
+ case BYTEA_BTREE_OPS_OID:
+ datatype = BYTEAOID;
break;
default:
- elog(ERROR, "prefix_quals: unexpected operator %u", expr_op);
+ elog(ERROR, "prefix_quals: unexpected opclass %u", opclass);
return NIL;
}
- /* Prefix constant is text for all except BYTEA_LIKE */
- if (datatype != BYTEAOID)
- prefix = DatumGetCString(DirectFunctionCall1(textout,
- prefix_const->constvalue));
- else
- prefix = DatumGetCString(DirectFunctionCall1(byteaout,
- prefix_const->constvalue));
+ /*
+ * If necessary, coerce the prefix constant to the right type.
+ * The given prefix constant is either text or bytea type.
+ */
+ if (prefix_const->consttype != datatype)
+ {
+ char *prefix;
+
+ switch (prefix_const->consttype)
+ {
+ case TEXTOID:
+ prefix = DatumGetCString(DirectFunctionCall1(textout,
+ prefix_const->constvalue));
+ break;
+ case BYTEAOID:
+ prefix = DatumGetCString(DirectFunctionCall1(byteaout,
+ prefix_const->constvalue));
+ break;
+ default:
+ elog(ERROR, "prefix_quals: unexpected consttype %u",
+ prefix_const->consttype);
+ return NIL;
+ }
+ prefix_const = string_to_const(prefix, datatype);
+ pfree(prefix);
+ }
/*
* If we found an exact-match pattern, generate an "=" indexqual.
*/
if (pstatus == Pattern_Prefix_Exact)
{
- oprname = (datatype == BYTEAOID || lc_collate_is_c() ? "=" : "~=~");
- oproid = find_operator(oprname, datatype);
+ oproid = get_opclass_member(opclass, BTEqualStrategyNumber);
if (oproid == InvalidOid)
- elog(ERROR, "prefix_quals: no operator %s for type %u", oprname, datatype);
- con = string_to_const(prefix, datatype);
+ elog(ERROR, "prefix_quals: no operator = for opclass %u", opclass);
expr = make_opclause(oproid, BOOLOID, false,
- (Expr *) leftop, (Expr *) con);
+ (Expr *) leftop, (Expr *) prefix_const);
result = makeList1(expr);
return result;
}
@@ -2123,13 +2118,11 @@ prefix_quals(Node *leftop, Oid expr_op,
*
* We can always say "x >= prefix".
*/
- oprname = (datatype == BYTEAOID || lc_collate_is_c() ? ">=" : "~>=~");
- oproid = find_operator(oprname, datatype);
+ oproid = get_opclass_member(opclass, BTGreaterEqualStrategyNumber);
if (oproid == InvalidOid)
- elog(ERROR, "prefix_quals: no operator %s for type %u", oprname, datatype);
- con = string_to_const(prefix, datatype);
+ elog(ERROR, "prefix_quals: no operator >= for opclass %u", opclass);
expr = make_opclause(oproid, BOOLOID, false,
- (Expr *) leftop, (Expr *) con);
+ (Expr *) leftop, (Expr *) prefix_const);
result = makeList1(expr);
/*-------
@@ -2137,13 +2130,12 @@ prefix_quals(Node *leftop, Oid expr_op,
* "x < greaterstr".
*-------
*/
- greaterstr = make_greater_string(con);
+ greaterstr = make_greater_string(prefix_const);
if (greaterstr)
{
- oprname = (datatype == BYTEAOID || lc_collate_is_c() ? "<" : "~<~");
- oproid = find_operator(oprname, datatype);
+ oproid = get_opclass_member(opclass, BTLessStrategyNumber);
if (oproid == InvalidOid)
- elog(ERROR, "prefix_quals: no operator %s for type %u", oprname, datatype);
+ elog(ERROR, "prefix_quals: no operator < for opclass %u", opclass);
expr = make_opclause(oproid, BOOLOID, false,
(Expr *) leftop, (Expr *) greaterstr);
result = lappend(result, expr);
@@ -2155,19 +2147,18 @@ prefix_quals(Node *leftop, Oid expr_op,
/*
* Given a leftop and a rightop, and a inet-class sup/sub operator,
* generate suitable indexqual condition(s). expr_op is the original
- * operator.
+ * operator, and opclass is the index opclass.
*/
static List *
-network_prefix_quals(Node *leftop, Oid expr_op, Datum rightop)
+network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass, Datum rightop)
{
bool is_eq;
- char *opr1name;
- Datum opr1right;
- Datum opr2right;
+ Oid datatype;
Oid opr1oid;
Oid opr2oid;
+ Datum opr1right;
+ Datum opr2right;
List *result;
- Oid datatype;
Expr *expr;
switch (expr_op)
@@ -2198,12 +2189,20 @@ network_prefix_quals(Node *leftop, Oid expr_op, Datum rightop)
* create clause "key >= network_scan_first( rightop )", or ">" if the
* operator disallows equality.
*/
-
- opr1name = is_eq ? ">=" : ">";
- opr1oid = find_operator(opr1name, datatype);
- if (opr1oid == InvalidOid)
- elog(ERROR, "network_prefix_quals: no %s operator for type %u",
- opr1name, datatype);
+ if (is_eq)
+ {
+ opr1oid = get_opclass_member(opclass, BTGreaterEqualStrategyNumber);
+ if (opr1oid == InvalidOid)
+ elog(ERROR, "network_prefix_quals: no >= operator for opclass %u",
+ opclass);
+ }
+ else
+ {
+ opr1oid = get_opclass_member(opclass, BTGreaterStrategyNumber);
+ if (opr1oid == InvalidOid)
+ elog(ERROR, "network_prefix_quals: no > operator for opclass %u",
+ opclass);
+ }
opr1right = network_scan_first(rightop);
@@ -2215,10 +2214,10 @@ network_prefix_quals(Node *leftop, Oid expr_op, Datum rightop)
/* create clause "key <= network_scan_last( rightop )" */
- opr2oid = find_operator("<=", datatype);
+ opr2oid = get_opclass_member(opclass, BTLessEqualStrategyNumber);
if (opr2oid == InvalidOid)
- elog(ERROR, "network_prefix_quals: no <= operator for type %u",
- datatype);
+ elog(ERROR, "network_prefix_quals: no <= operator for opclass %u",
+ opclass);
opr2right = network_scan_last(rightop);
@@ -2235,18 +2234,6 @@ network_prefix_quals(Node *leftop, Oid expr_op, Datum rightop)
* Handy subroutines for match_special_index_operator() and friends.
*/
-/* See if there is a binary op of the given name for the given datatype */
-/* NB: we assume that only built-in system operators are searched for */
-static Oid
-find_operator(const char *opname, Oid datatype)
-{
- return GetSysCacheOid(OPERNAMENSP,
- PointerGetDatum(opname),
- ObjectIdGetDatum(datatype),
- ObjectIdGetDatum(datatype),
- ObjectIdGetDatum(PG_CATALOG_NAMESPACE));
-}
-
/*
* Generate a Datum of the appropriate type from a C string.
* Note that all of the supported types are pass-by-ref, so the
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 3984c666f51..25648beed19 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.88 2003/02/15 20:12:40 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.89 2003/05/26 00:11:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -328,7 +328,7 @@ create_seqscan_path(Query *root, RelOptInfo *rel)
*
* 'rel' is the parent rel
* 'index' is an index on 'rel'
- * 'restriction_clauses' is a list of RestrictInfo nodes
+ * 'restriction_clauses' is a list of lists of RestrictInfo nodes
* to be used as index qual conditions in the scan.
* 'pathkeys' describes the ordering of the path.
* 'indexscandir' is ForwardScanDirection or BackwardScanDirection
@@ -352,9 +352,8 @@ create_index_path(Query *root,
pathnode->path.parent = rel;
pathnode->path.pathkeys = pathkeys;
- indexquals = get_actual_clauses(restriction_clauses);
- /* expand special operators to indexquals the executor can handle */
- indexquals = expand_indexqual_conditions(indexquals);
+ /* Convert RestrictInfo nodes to indexquals the executor can handle */
+ indexquals = expand_indexqual_conditions(index, restriction_clauses);
/*
* We are making a pathnode for a single-scan indexscan; therefore,
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 9dc0c7f1c19..fc35c2d6d41 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.96 2003/04/29 22:13:10 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.97 2003/05/26 00:11:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -32,7 +32,6 @@
static Node *coerce_type_typmod(Node *node,
Oid targetTypeId, int32 targetTypMod,
CoercionForm cformat, bool isExplicit);
-static Oid PreferredType(CATEGORY category, Oid type);
static Node *build_func_call(Oid funcid, Oid rettype, List *args,
CoercionForm fformat);
@@ -66,28 +65,43 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
if (can_coerce_type(1, &exprtype, &targettype, ccontext))
expr = coerce_type(pstate, expr, exprtype, targettype,
ccontext, cformat);
- /*
- * String hacks to get transparent conversions for char and varchar:
- * if a coercion to text is available, use it for forced coercions to
- * char(n) or varchar(n).
- *
- * This is pretty grotty, but seems easier to maintain than providing
- * entries in pg_cast that parallel all the ones for text.
- */
- else if (ccontext >= COERCION_ASSIGNMENT &&
- (targettype == BPCHAROID || targettype == VARCHAROID))
+ else if (ccontext >= COERCION_ASSIGNMENT)
{
- Oid text_id = TEXTOID;
+ /*
+ * String hacks to get transparent conversions for char and varchar:
+ * if a coercion to text is available, use it for forced coercions to
+ * char(n) or varchar(n) or domains thereof.
+ *
+ * This is pretty grotty, but seems easier to maintain than providing
+ * entries in pg_cast that parallel all the ones for text.
+ */
+ Oid targetbasetype = getBaseType(targettype);
- if (can_coerce_type(1, &exprtype, &text_id, ccontext))
+ if (targetbasetype == BPCHAROID || targetbasetype == VARCHAROID)
{
- expr = coerce_type(pstate, expr, exprtype, text_id,
- ccontext, cformat);
- /* Need a RelabelType if no typmod coercion is performed */
- if (targettypmod < 0)
- expr = (Node *) makeRelabelType((Expr *) expr,
- targettype, -1,
- cformat);
+ Oid text_id = TEXTOID;
+
+ if (can_coerce_type(1, &exprtype, &text_id, ccontext))
+ {
+ expr = coerce_type(pstate, expr, exprtype, text_id,
+ ccontext, cformat);
+ if (targetbasetype != targettype)
+ {
+ /* need to coerce to domain over char or varchar */
+ expr = coerce_to_domain(expr, targetbasetype, targettype,
+ cformat);
+ }
+ else
+ {
+ /* need a RelabelType if no typmod coercion will be performed */
+ if (targettypmod < 0)
+ expr = (Node *) makeRelabelType((Expr *) expr,
+ targettype, -1,
+ cformat);
+ }
+ }
+ else
+ expr = NULL;
}
else
expr = NULL;
@@ -923,7 +937,10 @@ enforce_generic_type_consistency(Oid *actual_arg_types,
/* TypeCategory()
- * Assign a category to the specified OID.
+ * Assign a category to the specified type OID.
+ *
+ * NB: this must not return INVALID_TYPE.
+ *
* XXX This should be moved to system catalog lookups
* to allow for better type extensibility.
* - thomas 2001-09-30
@@ -1026,7 +1043,11 @@ TypeCategory(Oid inType)
/* IsPreferredType()
- * Check if this type is a preferred type.
+ * Check if this type is a preferred type for the given category.
+ *
+ * If category is INVALID_TYPE, then we'll return TRUE for preferred types
+ * of any category; otherwise, only for preferred types of that category.
+ *
* XXX This should be moved to system catalog lookups
* to allow for better type extensibility.
* - thomas 2001-09-30
@@ -1034,39 +1055,34 @@ TypeCategory(Oid inType)
bool
IsPreferredType(CATEGORY category, Oid type)
{
- return (type == PreferredType(category, type));
-} /* IsPreferredType() */
+ Oid preftype;
+ if (category == INVALID_TYPE)
+ category = TypeCategory(type);
+ else if (category != TypeCategory(type))
+ return false;
-/* PreferredType()
- * Return the preferred type OID for the specified category.
- * XXX This should be moved to system catalog lookups
- * to allow for better type extensibility.
- * - thomas 2001-09-30
- */
-static Oid
-PreferredType(CATEGORY category, Oid type)
-{
- Oid result;
-
+ /*
+ * This switch should agree with TypeCategory(), above. Note that
+ * at this point, category certainly matches the type.
+ */
switch (category)
{
- case (INVALID_TYPE):
case (UNKNOWN_TYPE):
case (GENERIC_TYPE):
- result = UNKNOWNOID;
+ preftype = UNKNOWNOID;
break;
case (BOOLEAN_TYPE):
- result = BOOLOID;
+ preftype = BOOLOID;
break;
case (STRING_TYPE):
- result = TEXTOID;
+ preftype = TEXTOID;
break;
case (BITSTRING_TYPE):
- result = VARBITOID;
+ preftype = VARBITOID;
break;
case (NUMERIC_TYPE):
@@ -1077,52 +1093,59 @@ PreferredType(CATEGORY category, Oid type)
type == REGOPERATOROID ||
type == REGCLASSOID ||
type == REGTYPEOID)
- result = OIDOID;
+ preftype = OIDOID;
else
- result = FLOAT8OID;
+ preftype = FLOAT8OID;
break;
case (DATETIME_TYPE):
if (type == DATEOID)
- result = TIMESTAMPOID;
+ preftype = TIMESTAMPOID;
else
- result = TIMESTAMPTZOID;
+ preftype = TIMESTAMPTZOID;
break;
case (TIMESPAN_TYPE):
- result = INTERVALOID;
+ preftype = INTERVALOID;
break;
case (GEOMETRIC_TYPE):
- result = type;
+ preftype = type;
break;
case (NETWORK_TYPE):
- result = INETOID;
+ preftype = INETOID;
break;
case (USER_TYPE):
- result = type;
+ preftype = type;
break;
default:
- elog(ERROR, "PreferredType: unknown category");
- result = UNKNOWNOID;
+ elog(ERROR, "IsPreferredType: unknown category");
+ preftype = UNKNOWNOID;
break;
}
- return result;
-} /* PreferredType() */
+
+ return (type == preftype);
+} /* IsPreferredType() */
/* IsBinaryCoercible()
* Check if srctype is binary-coercible to targettype.
*
* This notion allows us to cheat and directly exchange values without
- * going through the trouble of calling a conversion function.
+ * going through the trouble of calling a conversion function. Note that
+ * in general, this should only be an implementation shortcut. Before 7.4,
+ * this was also used as a heuristic for resolving overloaded functions and
+ * operators, but that's basically a bad idea.
*
* As of 7.3, binary coercibility isn't hardwired into the code anymore.
* We consider two types binary-coercible if there is an implicitly
- * invokable, no-function-needed pg_cast entry.
+ * invokable, no-function-needed pg_cast entry. Also, a domain is always
+ * binary-coercible to its base type, though *not* vice versa (in the other
+ * direction, one must apply domain constraint checks before accepting the
+ * value as legitimate).
*
* This function replaces IsBinaryCompatible(), which was an inherently
* symmetric test. Since the pg_cast entries aren't necessarily symmetric,
@@ -1139,13 +1162,11 @@ IsBinaryCoercible(Oid srctype, Oid targettype)
if (srctype == targettype)
return true;
- /* Perhaps the types are domains; if so, look at their base types */
+ /* If srctype is a domain, reduce to its base type */
if (OidIsValid(srctype))
srctype = getBaseType(srctype);
- if (OidIsValid(targettype))
- targettype = getBaseType(targettype);
- /* Somewhat-fast path if same base type */
+ /* Somewhat-fast path for domain -> base type case */
if (srctype == targettype)
return true;
@@ -1174,8 +1195,13 @@ IsBinaryCoercible(Oid srctype, Oid targettype)
* ccontext determines the set of available casts.
*
* If we find a suitable entry in pg_cast, return TRUE, and set *funcid
- * to the castfunc value (which may be InvalidOid for a binary-compatible
- * coercion).
+ * to the castfunc value, which may be InvalidOid for a binary-compatible
+ * coercion.
+ *
+ * NOTE: *funcid == InvalidOid does not necessarily mean that no work is
+ * needed to do the coercion; if the target is a domain then we may need to
+ * apply domain constraint checking. If you want to check for a zero-effort
+ * conversion then use IsBinaryCoercible().
*/
bool
find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
@@ -1193,7 +1219,7 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
if (OidIsValid(targetTypeId))
targetTypeId = getBaseType(targetTypeId);
- /* Domains are automatically binary-compatible with their base type */
+ /* Domains are always coercible to and from their base type */
if (sourceTypeId == targetTypeId)
return true;
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 058b9aad73d..e9a40e03179 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.147 2003/04/29 22:13:10 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.148 2003/05/26 00:11:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -16,7 +16,6 @@
#include "access/heapam.h"
#include "catalog/catname.h"
-#include "catalog/namespace.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_proc.h"
#include "lib/stringinfo.h"
@@ -37,13 +36,7 @@ static Oid **argtype_inherit(int nargs, Oid *argtypes);
static int find_inheritors(Oid relid, Oid **supervec);
static Oid **gen_cross_product(InhPaths *arginh, int nargs);
-static int match_argtypes(int nargs,
- Oid *input_typeids,
- FuncCandidateList function_typeids,
- FuncCandidateList *candidates);
static FieldSelect *setup_field_select(Node *input, char *attname, Oid relid);
-static FuncCandidateList func_select_candidate(int nargs, Oid *input_typeids,
- FuncCandidateList candidates);
static void unknown_attribute(const char *schemaname, const char *relname,
const char *attname);
@@ -355,21 +348,24 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
}
-/* match_argtypes()
+/* func_match_argtypes()
*
- * Given a list of possible typeid arrays to a function and an array of
- * input typeids, produce a shortlist of those function typeid arrays
- * that match the input typeids (either exactly or by coercion), and
- * return the number of such arrays.
+ * Given a list of candidate functions (having the right name and number
+ * of arguments) and an array of input datatype OIDs, produce a shortlist of
+ * those candidates that actually accept the input datatypes (either exactly
+ * or by coercion), and return the number of such candidates.
+ *
+ * Note that can_coerce_type will assume that UNKNOWN inputs are coercible to
+ * anything, so candidates will not be eliminated on that basis.
*
* NB: okay to modify input list structure, as long as we find at least
- * one match.
+ * one match. If no match at all, the list must remain unmodified.
*/
-static int
-match_argtypes(int nargs,
- Oid *input_typeids,
- FuncCandidateList function_typeids,
- FuncCandidateList *candidates) /* return value */
+int
+func_match_argtypes(int nargs,
+ Oid *input_typeids,
+ FuncCandidateList raw_candidates,
+ FuncCandidateList *candidates) /* return value */
{
FuncCandidateList current_candidate;
FuncCandidateList next_candidate;
@@ -377,7 +373,7 @@ match_argtypes(int nargs,
*candidates = NULL;
- for (current_candidate = function_typeids;
+ for (current_candidate = raw_candidates;
current_candidate != NULL;
current_candidate = next_candidate)
{
@@ -392,21 +388,65 @@ match_argtypes(int nargs,
}
return ncandidates;
-} /* match_argtypes() */
+} /* func_match_argtypes() */
/* func_select_candidate()
- * Given the input argtype array and more than one candidate
- * for the function, attempt to resolve the conflict.
+ * Given the input argtype array and more than one candidate
+ * for the function, attempt to resolve the conflict.
+ *
* Returns the selected candidate if the conflict can be resolved,
* otherwise returns NULL.
*
- * By design, this is pretty similar to oper_select_candidate in parse_oper.c.
- * However, the calling convention is a little different: we assume the caller
- * already pruned away "candidates" that aren't actually coercion-compatible
- * with the input types, whereas oper_select_candidate must do that itself.
+ * Note that the caller has already determined that there is no candidate
+ * exactly matching the input argtypes, and has pruned away any "candidates"
+ * that aren't actually coercion-compatible with the input types.
+ *
+ * This is also used for resolving ambiguous operator references. Formerly
+ * parse_oper.c had its own, essentially duplicate code for the purpose.
+ * The following comments (formerly in parse_oper.c) are kept to record some
+ * of the history of these heuristics.
+ *
+ * OLD COMMENTS:
+ *
+ * This routine is new code, replacing binary_oper_select_candidate()
+ * which dates from v4.2/v1.0.x days. It tries very hard to match up
+ * operators with types, including allowing type coercions if necessary.
+ * The important thing is that the code do as much as possible,
+ * while _never_ doing the wrong thing, where "the wrong thing" would
+ * be returning an operator when other better choices are available,
+ * or returning an operator which is a non-intuitive possibility.
+ * - thomas 1998-05-21
+ *
+ * The comments below came from binary_oper_select_candidate(), and
+ * illustrate the issues and choices which are possible:
+ * - thomas 1998-05-20
+ *
+ * current wisdom holds that the default operator should be one in which
+ * both operands have the same type (there will only be one such
+ * operator)
+ *
+ * 7.27.93 - I have decided not to do this; it's too hard to justify, and
+ * it's easy enough to typecast explicitly - avi
+ * [the rest of this routine was commented out since then - ay]
+ *
+ * 6/23/95 - I don't complete agree with avi. In particular, casting
+ * floats is a pain for users. Whatever the rationale behind not doing
+ * this is, I need the following special case to work.
+ *
+ * In the WHERE clause of a query, if a float is specified without
+ * quotes, we treat it as float8. I added the float48* operators so
+ * that we can operate on float4 and float8. But now we have more than
+ * one matching operator if the right arg is unknown (eg. float
+ * specified with quotes). This break some stuff in the regression
+ * test where there are floats in quotes not properly casted. Below is
+ * the solution. In addition to requiring the operator operates on the
+ * same type for both operands [as in the code Avi originally
+ * commented out], we also require that the operators be equivalent in
+ * some sense. (see equivalentOpersAfterPromotion for details.)
+ * - ay 6/95
*/
-static FuncCandidateList
+FuncCandidateList
func_select_candidate(int nargs,
Oid *input_typeids,
FuncCandidateList candidates)
@@ -419,59 +459,28 @@ func_select_candidate(int nargs,
int ncandidates;
int nbestMatch,
nmatch;
+ Oid input_base_typeids[FUNC_MAX_ARGS];
CATEGORY slot_category[FUNC_MAX_ARGS],
current_category;
bool slot_has_preferred_type[FUNC_MAX_ARGS];
bool resolved_unknowns;
/*
- * Run through all candidates and keep those with the most matches on
- * exact types. Keep all candidates if none match.
+ * If any input types are domains, reduce them to their base types.
+ * This ensures that we will consider functions on the base type to be
+ * "exact matches" in the exact-match heuristic; it also makes it possible
+ * to do something useful with the type-category heuristics. Note that
+ * this makes it difficult, but not impossible, to use functions declared
+ * to take a domain as an input datatype. Such a function will be
+ * selected over the base-type function only if it is an exact match at
+ * all argument positions, and so was already chosen by our caller.
*/
- ncandidates = 0;
- nbestMatch = 0;
- last_candidate = NULL;
- for (current_candidate = candidates;
- current_candidate != NULL;
- current_candidate = current_candidate->next)
- {
- current_typeids = current_candidate->args;
- nmatch = 0;
- for (i = 0; i < nargs; i++)
- {
- if (input_typeids[i] != UNKNOWNOID &&
- current_typeids[i] == input_typeids[i])
- nmatch++;
- }
-
- /* take this one as the best choice so far? */
- if ((nmatch > nbestMatch) || (last_candidate == NULL))
- {
- nbestMatch = nmatch;
- candidates = current_candidate;
- last_candidate = current_candidate;
- ncandidates = 1;
- }
- /* no worse than the last choice, so keep this one too? */
- else if (nmatch == nbestMatch)
- {
- last_candidate->next = current_candidate;
- last_candidate = current_candidate;
- ncandidates++;
- }
- /* otherwise, don't bother keeping this one... */
- }
-
- if (last_candidate) /* terminate rebuilt list */
- last_candidate->next = NULL;
-
- if (ncandidates == 1)
- return candidates;
+ for (i = 0; i < nargs; i++)
+ input_base_typeids[i] = getBaseType(input_typeids[i]);
/*
- * Still too many candidates? Run through all candidates and keep
- * those with the most matches on exact types + binary-compatible
- * types. Keep all candidates if none match.
+ * Run through all candidates and keep those with the most matches on
+ * exact types. Keep all candidates if none match.
*/
ncandidates = 0;
nbestMatch = 0;
@@ -484,11 +493,9 @@ func_select_candidate(int nargs,
nmatch = 0;
for (i = 0; i < nargs; i++)
{
- if (input_typeids[i] != UNKNOWNOID)
- {
- if (IsBinaryCoercible(input_typeids[i], current_typeids[i]))
- nmatch++;
- }
+ if (input_base_typeids[i] != UNKNOWNOID &&
+ current_typeids[i] == input_base_typeids[i])
+ nmatch++;
}
/* take this one as the best choice so far? */
@@ -516,10 +523,14 @@ func_select_candidate(int nargs,
return candidates;
/*
- * Still too many candidates? Now look for candidates which are
- * preferred types at the args that will require coercion. Keep all
- * candidates if none match.
+ * Still too many candidates? Now look for candidates which have either
+ * exact matches or preferred types at the args that will require coercion.
+ * (Restriction added in 7.4: preferred type must be of same category as
+ * input type; give no preference to cross-category conversions to
+ * preferred types.) Keep all candidates if none match.
*/
+ for (i = 0; i < nargs; i++) /* avoid multiple lookups */
+ slot_category[i] = TypeCategory(input_base_typeids[i]);
ncandidates = 0;
nbestMatch = 0;
last_candidate = NULL;
@@ -531,11 +542,10 @@ func_select_candidate(int nargs,
nmatch = 0;
for (i = 0; i < nargs; i++)
{
- if (input_typeids[i] != UNKNOWNOID)
+ if (input_base_typeids[i] != UNKNOWNOID)
{
- current_category = TypeCategory(current_typeids[i]);
- if (current_typeids[i] == input_typeids[i] ||
- IsPreferredType(current_category, current_typeids[i]))
+ if (current_typeids[i] == input_base_typeids[i] ||
+ IsPreferredType(slot_category[i], current_typeids[i]))
nmatch++;
}
}
@@ -565,6 +575,11 @@ func_select_candidate(int nargs,
* Still too many candidates? Try assigning types for the unknown
* columns.
*
+ * NOTE: for a binary operator with one unknown and one non-unknown input,
+ * we already tried the heuristic of looking for a candidate with the
+ * known input type on both sides (see binary_oper_exact()). That's
+ * essentially a special case of the general algorithm we try next.
+ *
* We do this by examining each unknown argument position to see if we
* can determine a "type category" for it. If any candidate has an
* input datatype of STRING category, use STRING category (this bias
@@ -588,7 +603,7 @@ func_select_candidate(int nargs,
{
bool have_conflict;
- if (input_typeids[i] != UNKNOWNOID)
+ if (input_base_typeids[i] != UNKNOWNOID)
continue;
resolved_unknowns = true; /* assume we can do it */
slot_category[i] = INVALID_TYPE;
@@ -656,7 +671,7 @@ func_select_candidate(int nargs,
current_typeids = current_candidate->args;
for (i = 0; i < nargs; i++)
{
- if (input_typeids[i] != UNKNOWNOID)
+ if (input_base_typeids[i] != UNKNOWNOID)
continue;
current_type = current_typeids[i];
current_category = TypeCategory(current_type);
@@ -694,7 +709,7 @@ func_select_candidate(int nargs,
if (ncandidates == 1)
return candidates;
- return NULL; /* failed to determine a unique candidate */
+ return NULL; /* failed to select a best candidate */
} /* func_select_candidate() */
@@ -734,16 +749,17 @@ func_get_detail(List *funcname,
bool *retset, /* return value */
Oid **true_typeids) /* return value */
{
- FuncCandidateList function_typeids;
+ FuncCandidateList raw_candidates;
FuncCandidateList best_candidate;
/* Get list of possible candidates from namespace search */
- function_typeids = FuncnameGetCandidates(funcname, nargs);
+ raw_candidates = FuncnameGetCandidates(funcname, nargs);
/*
- * See if there is an exact match
+ * Quickly check if there is an exact match to the input datatypes
+ * (there can be only one)
*/
- for (best_candidate = function_typeids;
+ for (best_candidate = raw_candidates;
best_candidate != NULL;
best_candidate = best_candidate->next)
{
@@ -815,7 +831,7 @@ func_get_detail(List *funcname,
* didn't find an exact match, so now try to match up
* candidates...
*/
- if (function_typeids != NULL)
+ if (raw_candidates != NULL)
{
Oid **input_typeid_vector = NULL;
Oid *current_input_typeids;
@@ -829,17 +845,18 @@ func_get_detail(List *funcname,
do
{
- FuncCandidateList current_function_typeids;
+ FuncCandidateList current_candidates;
int ncandidates;
- ncandidates = match_argtypes(nargs, current_input_typeids,
- function_typeids,
- &current_function_typeids);
+ ncandidates = func_match_argtypes(nargs,
+ current_input_typeids,
+ raw_candidates,
+ &current_candidates);
/* one match only? then run with it... */
if (ncandidates == 1)
{
- best_candidate = current_function_typeids;
+ best_candidate = current_candidates;
break;
}
@@ -851,7 +868,7 @@ func_get_detail(List *funcname,
{
best_candidate = func_select_candidate(nargs,
current_input_typeids,
- current_function_typeids);
+ current_candidates);
/*
* If we were able to choose a best candidate, we're
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index 6238258ed2f..21f73994c42 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -8,18 +8,13 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_oper.c,v 1.63 2003/04/29 22:13:10 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_oper.c,v 1.64 2003/05/26 00:11:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
-#include "access/genam.h"
-#include "access/heapam.h"
-#include "catalog/catname.h"
-#include "catalog/indexing.h"
-#include "catalog/namespace.h"
#include "catalog/pg_operator.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
@@ -289,107 +284,29 @@ binary_oper_exact(Oid arg1, Oid arg2,
/* oper_select_candidate()
- * Given the input argtype array and one or more candidates
- * for the function argtype array, attempt to resolve the conflict.
- * Returns the selected argtype array if the conflict can be resolved,
- * otherwise returns NULL.
+ * Given the input argtype array and one or more candidates
+ * for the operator, attempt to resolve the conflict.
*
- * By design, this is pretty similar to func_select_candidate in parse_func.c.
- * However, we can do a couple of extra things here because we know we can
- * have no more than two args to deal with. Also, the calling convention
- * is a little different: we must prune away "candidates" that aren't actually
- * coercion-compatible with the input types, whereas in parse_func.c that
- * gets done by match_argtypes before func_select_candidate is called.
+ * Returns the OID of the selected operator if the conflict can be resolved,
+ * otherwise returns InvalidOid.
*
- * This routine is new code, replacing binary_oper_select_candidate()
- * which dates from v4.2/v1.0.x days. It tries very hard to match up
- * operators with types, including allowing type coercions if necessary.
- * The important thing is that the code do as much as possible,
- * while _never_ doing the wrong thing, where "the wrong thing" would
- * be returning an operator when other better choices are available,
- * or returning an operator which is a non-intuitive possibility.
- * - thomas 1998-05-21
- *
- * The comments below came from binary_oper_select_candidate(), and
- * illustrate the issues and choices which are possible:
- * - thomas 1998-05-20
- *
- * current wisdom holds that the default operator should be one in which
- * both operands have the same type (there will only be one such
- * operator)
- *
- * 7.27.93 - I have decided not to do this; it's too hard to justify, and
- * it's easy enough to typecast explicitly - avi
- * [the rest of this routine was commented out since then - ay]
- *
- * 6/23/95 - I don't complete agree with avi. In particular, casting
- * floats is a pain for users. Whatever the rationale behind not doing
- * this is, I need the following special case to work.
- *
- * In the WHERE clause of a query, if a float is specified without
- * quotes, we treat it as float8. I added the float48* operators so
- * that we can operate on float4 and float8. But now we have more than
- * one matching operator if the right arg is unknown (eg. float
- * specified with quotes). This break some stuff in the regression
- * test where there are floats in quotes not properly casted. Below is
- * the solution. In addition to requiring the operator operates on the
- * same type for both operands [as in the code Avi originally
- * commented out], we also require that the operators be equivalent in
- * some sense. (see equivalentOpersAfterPromotion for details.)
- * - ay 6/95
+ * Note that the caller has already determined that there is no candidate
+ * exactly matching the input argtype(s). Incompatible candidates are not yet
+ * pruned away, however.
*/
static Oid
oper_select_candidate(int nargs,
Oid *input_typeids,
FuncCandidateList candidates)
{
- FuncCandidateList current_candidate;
- FuncCandidateList last_candidate;
- Oid *current_typeids;
- Oid current_type;
- int unknownOids;
- int i;
int ncandidates;
- int nbestMatch,
- nmatch;
- CATEGORY slot_category[FUNC_MAX_ARGS],
- current_category;
- bool slot_has_preferred_type[FUNC_MAX_ARGS];
- bool resolved_unknowns;
/*
- * First, delete any candidates that cannot actually accept the given
- * input types, whether directly or by coercion. (Note that
- * can_coerce_type will assume that UNKNOWN inputs are coercible to
- * anything, so candidates will not be eliminated on that basis.)
+ * Delete any candidates that cannot actually accept the given
+ * input types, whether directly or by coercion.
*/
- ncandidates = 0;
- last_candidate = NULL;
- for (current_candidate = candidates;
- current_candidate != NULL;
- current_candidate = current_candidate->next)
- {
- if (can_coerce_type(nargs, input_typeids, current_candidate->args,
- COERCION_IMPLICIT))
- {
- if (last_candidate == NULL)
- {
- candidates = current_candidate;
- last_candidate = current_candidate;
- ncandidates = 1;
- }
- else
- {
- last_candidate->next = current_candidate;
- last_candidate = current_candidate;
- ncandidates++;
- }
- }
- /* otherwise, don't bother keeping this one... */
- }
-
- if (last_candidate) /* terminate rebuilt list */
- last_candidate->next = NULL;
+ ncandidates = func_match_argtypes(nargs, input_typeids,
+ candidates, &candidates);
/* Done if no candidate or only one candidate survives */
if (ncandidates == 0)
@@ -398,317 +315,15 @@ oper_select_candidate(int nargs,
return candidates->oid;
/*
- * Run through all candidates and keep those with the most matches on
- * exact types. Keep all candidates if none match.
- */
- ncandidates = 0;
- nbestMatch = 0;
- last_candidate = NULL;
- for (current_candidate = candidates;
- current_candidate != NULL;
- current_candidate = current_candidate->next)
- {
- current_typeids = current_candidate->args;
- nmatch = 0;
- for (i = 0; i < nargs; i++)
- {
- if (input_typeids[i] != UNKNOWNOID &&
- current_typeids[i] == input_typeids[i])
- nmatch++;
- }
-
- /* take this one as the best choice so far? */
- if ((nmatch > nbestMatch) || (last_candidate == NULL))
- {
- nbestMatch = nmatch;
- candidates = current_candidate;
- last_candidate = current_candidate;
- ncandidates = 1;
- }
- /* no worse than the last choice, so keep this one too? */
- else if (nmatch == nbestMatch)
- {
- last_candidate->next = current_candidate;
- last_candidate = current_candidate;
- ncandidates++;
- }
- /* otherwise, don't bother keeping this one... */
- }
-
- if (last_candidate) /* terminate rebuilt list */
- last_candidate->next = NULL;
-
- if (ncandidates == 1)
- return candidates->oid;
-
- /*
- * Still too many candidates? Run through all candidates and keep
- * those with the most matches on exact types + binary-compatible
- * types. Keep all candidates if none match.
- */
- ncandidates = 0;
- nbestMatch = 0;
- last_candidate = NULL;
- for (current_candidate = candidates;
- current_candidate != NULL;
- current_candidate = current_candidate->next)
- {
- current_typeids = current_candidate->args;
- nmatch = 0;
- for (i = 0; i < nargs; i++)
- {
- if (input_typeids[i] != UNKNOWNOID)
- {
- if (IsBinaryCoercible(input_typeids[i], current_typeids[i]))
- nmatch++;
- }
- }
-
- /* take this one as the best choice so far? */
- if ((nmatch > nbestMatch) || (last_candidate == NULL))
- {
- nbestMatch = nmatch;
- candidates = current_candidate;
- last_candidate = current_candidate;
- ncandidates = 1;
- }
- /* no worse than the last choice, so keep this one too? */
- else if (nmatch == nbestMatch)
- {
- last_candidate->next = current_candidate;
- last_candidate = current_candidate;
- ncandidates++;
- }
- /* otherwise, don't bother keeping this one... */
- }
-
- if (last_candidate) /* terminate rebuilt list */
- last_candidate->next = NULL;
-
- if (ncandidates == 1)
- return candidates->oid;
-
- /*
- * Still too many candidates? Now look for candidates which are
- * preferred types at the args that will require coercion. Keep all
- * candidates if none match.
- */
- ncandidates = 0;
- nbestMatch = 0;
- last_candidate = NULL;
- for (current_candidate = candidates;
- current_candidate != NULL;
- current_candidate = current_candidate->next)
- {
- current_typeids = current_candidate->args;
- nmatch = 0;
- for (i = 0; i < nargs; i++)
- {
- if (input_typeids[i] != UNKNOWNOID)
- {
- current_category = TypeCategory(current_typeids[i]);
- if (current_typeids[i] == input_typeids[i] ||
- IsPreferredType(current_category, current_typeids[i]))
- nmatch++;
- }
- }
-
- if ((nmatch > nbestMatch) || (last_candidate == NULL))
- {
- nbestMatch = nmatch;
- candidates = current_candidate;
- last_candidate = current_candidate;
- ncandidates = 1;
- }
- else if (nmatch == nbestMatch)
- {
- last_candidate->next = current_candidate;
- last_candidate = current_candidate;
- ncandidates++;
- }
- }
-
- if (last_candidate) /* terminate rebuilt list */
- last_candidate->next = NULL;
-
- if (ncandidates == 1)
- return candidates->oid;
-
- /*
- * Still too many candidates? Try assigning types for the unknown
- * columns.
- *
- * First try: if we have an unknown and a non-unknown input, see whether
- * there is a candidate all of whose input types are the same as the
- * known input type (there can be at most one such candidate). If so,
- * use that candidate. NOTE that this is cool only because operators
- * can't have more than 2 args, so taking the last non-unknown as
- * current_type can yield only one possibility if there is also an
- * unknown.
- */
- unknownOids = FALSE;
- current_type = UNKNOWNOID;
- for (i = 0; i < nargs; i++)
- {
- if ((input_typeids[i] != UNKNOWNOID)
- && (input_typeids[i] != InvalidOid))
- current_type = input_typeids[i];
- else
- unknownOids = TRUE;
- }
-
- if (unknownOids && (current_type != UNKNOWNOID))
- {
- for (current_candidate = candidates;
- current_candidate != NULL;
- current_candidate = current_candidate->next)
- {
- current_typeids = current_candidate->args;
- nmatch = 0;
- for (i = 0; i < nargs; i++)
- {
- if (current_type == current_typeids[i])
- nmatch++;
- }
- if (nmatch == nargs)
- return current_candidate->oid;
- }
- }
-
- /*
- * Second try: same algorithm as for unknown resolution in
- * parse_func.c.
- *
- * We do this by examining each unknown argument position to see if we
- * can determine a "type category" for it. If any candidate has an
- * input datatype of STRING category, use STRING category (this bias
- * towards STRING is appropriate since unknown-type literals look like
- * strings). Otherwise, if all the candidates agree on the type
- * category of this argument position, use that category. Otherwise,
- * fail because we cannot determine a category.
- *
- * If we are able to determine a type category, also notice whether any
- * of the candidates takes a preferred datatype within the category.
- *
- * Having completed this examination, remove candidates that accept the
- * wrong category at any unknown position. Also, if at least one
- * candidate accepted a preferred type at a position, remove
- * candidates that accept non-preferred types.
- *
- * If we are down to one candidate at the end, we win.
+ * Use the same heuristics as for ambiguous functions to resolve
+ * the conflict.
*/
- resolved_unknowns = false;
- for (i = 0; i < nargs; i++)
- {
- bool have_conflict;
-
- if (input_typeids[i] != UNKNOWNOID)
- continue;
- resolved_unknowns = true; /* assume we can do it */
- slot_category[i] = INVALID_TYPE;
- slot_has_preferred_type[i] = false;
- have_conflict = false;
- for (current_candidate = candidates;
- current_candidate != NULL;
- current_candidate = current_candidate->next)
- {
- current_typeids = current_candidate->args;
- current_type = current_typeids[i];
- current_category = TypeCategory(current_type);
- if (slot_category[i] == INVALID_TYPE)
- {
- /* first candidate */
- slot_category[i] = current_category;
- slot_has_preferred_type[i] =
- IsPreferredType(current_category, current_type);
- }
- else if (current_category == slot_category[i])
- {
- /* more candidates in same category */
- slot_has_preferred_type[i] |=
- IsPreferredType(current_category, current_type);
- }
- else
- {
- /* category conflict! */
- if (current_category == STRING_TYPE)
- {
- /* STRING always wins if available */
- slot_category[i] = current_category;
- slot_has_preferred_type[i] =
- IsPreferredType(current_category, current_type);
- }
- else
- {
- /*
- * Remember conflict, but keep going (might find
- * STRING)
- */
- have_conflict = true;
- }
- }
- }
- if (have_conflict && slot_category[i] != STRING_TYPE)
- {
- /* Failed to resolve category conflict at this position */
- resolved_unknowns = false;
- break;
- }
- }
+ candidates = func_select_candidate(nargs, input_typeids, candidates);
- if (resolved_unknowns)
- {
- /* Strip non-matching candidates */
- ncandidates = 0;
- last_candidate = NULL;
- for (current_candidate = candidates;
- current_candidate != NULL;
- current_candidate = current_candidate->next)
- {
- bool keepit = true;
-
- current_typeids = current_candidate->args;
- for (i = 0; i < nargs; i++)
- {
- if (input_typeids[i] != UNKNOWNOID)
- continue;
- current_type = current_typeids[i];
- current_category = TypeCategory(current_type);
- if (current_category != slot_category[i])
- {
- keepit = false;
- break;
- }
- if (slot_has_preferred_type[i] &&
- !IsPreferredType(current_category, current_type))
- {
- keepit = false;
- break;
- }
- }
- if (keepit)
- {
- /* keep this candidate */
- last_candidate = current_candidate;
- ncandidates++;
- }
- else
- {
- /* forget this candidate */
- if (last_candidate)
- last_candidate->next = current_candidate->next;
- else
- candidates = current_candidate->next;
- }
- }
- if (last_candidate) /* terminate rebuilt list */
- last_candidate->next = NULL;
- }
-
- if (ncandidates == 1)
+ if (candidates)
return candidates->oid;
- return InvalidOid; /* failed to determine a unique candidate */
+ return InvalidOid; /* failed to select a best candidate */
} /* oper_select_candidate() */
@@ -751,7 +366,7 @@ oper(List *opname, Oid ltypeId, Oid rtypeId, bool noError)
/*
* Unspecified type for one of the arguments? then use the
- * other
+ * other (XXX this is probably dead code?)
*/
if (rtypeId == InvalidOid)
rtypeId = ltypeId;
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 5ff4b1931da..77ef33b8783 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -15,7 +15,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/selfuncs.c,v 1.137 2003/05/15 15:50:18 petere Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/selfuncs.c,v 1.138 2003/05/26 00:11:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -77,9 +77,11 @@
#include <math.h>
#include "access/heapam.h"
+#include "access/nbtree.h"
#include "access/tuptoaster.h"
#include "catalog/catname.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic.h"
@@ -177,10 +179,9 @@ static bool get_restriction_var(List *args, int varRelid,
Var **var, Node **other,
bool *varonleft);
static void get_join_vars(List *args, Var **var1, Var **var2);
-static Selectivity prefix_selectivity(Query *root, Var *var, Oid vartype,
- Const *prefix);
+static Selectivity prefix_selectivity(Query *root, Var *var,
+ Oid opclass, Const *prefix);
static Selectivity pattern_selectivity(Const *patt, Pattern_Type ptype);
-static Oid find_operator(const char *opname, Oid datatype);
static Datum string_to_datum(const char *str, Oid datatype);
static Const *string_to_const(const char *str, Oid datatype);
@@ -837,6 +838,7 @@ patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype)
Datum constval;
Oid consttype;
Oid vartype;
+ Oid opclass;
Pattern_Prefix_Status pstatus;
Const *patt = NULL;
Const *prefix = NULL;
@@ -884,21 +886,77 @@ patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype)
if (vartype != consttype)
vartype = getBaseType(vartype);
+ /*
+ * We should now be able to recognize the var's datatype. Choose the
+ * index opclass from which we must draw the comparison operators.
+ *
+ * NOTE: It would be more correct to use the PATTERN opclasses than
+ * the simple ones, but at the moment ANALYZE will not generate statistics
+ * for the PATTERN operators. But our results are so approximate anyway
+ * that it probably hardly matters.
+ */
+ switch (vartype)
+ {
+ case TEXTOID:
+ opclass = TEXT_BTREE_OPS_OID;
+ break;
+ case VARCHAROID:
+ opclass = VARCHAR_BTREE_OPS_OID;
+ break;
+ case BPCHAROID:
+ opclass = BPCHAR_BTREE_OPS_OID;
+ break;
+ case NAMEOID:
+ opclass = NAME_BTREE_OPS_OID;
+ break;
+ case BYTEAOID:
+ opclass = BYTEA_BTREE_OPS_OID;
+ break;
+ default:
+ return DEFAULT_MATCH_SEL;
+ }
+
/* divide pattern into fixed prefix and remainder */
patt = (Const *) other;
pstatus = pattern_fixed_prefix(patt, ptype, &prefix, &rest);
+ /*
+ * If necessary, coerce the prefix constant to the right type.
+ * (The "rest" constant need not be changed.)
+ */
+ if (prefix && prefix->consttype != vartype)
+ {
+ char *prefixstr;
+
+ switch (prefix->consttype)
+ {
+ case TEXTOID:
+ prefixstr = DatumGetCString(DirectFunctionCall1(textout,
+ prefix->constvalue));
+ break;
+ case BYTEAOID:
+ prefixstr = DatumGetCString(DirectFunctionCall1(byteaout,
+ prefix->constvalue));
+ break;
+ default:
+ elog(ERROR, "patternsel: unexpected consttype %u",
+ prefix->consttype);
+ return DEFAULT_MATCH_SEL;
+ }
+ prefix = string_to_const(prefixstr, vartype);
+ pfree(prefixstr);
+ }
+
if (pstatus == Pattern_Prefix_Exact)
{
/*
* Pattern specifies an exact match, so pretend operator is '='
*/
- Oid eqopr = find_operator("=", vartype);
+ Oid eqopr = get_opclass_member(opclass, BTEqualStrategyNumber);
List *eqargs;
if (eqopr == InvalidOid)
- elog(ERROR, "patternsel: no = operator for type %u",
- vartype);
+ elog(ERROR, "patternsel: no = operator for opclass %u", opclass);
eqargs = makeList2(var, prefix);
result = DatumGetFloat8(DirectFunctionCall4(eqsel,
PointerGetDatum(root),
@@ -918,7 +976,7 @@ patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype)
Selectivity selec;
if (pstatus == Pattern_Prefix_Partial)
- prefixsel = prefix_selectivity(root, var, vartype, prefix);
+ prefixsel = prefix_selectivity(root, var, opclass, prefix);
else
prefixsel = 1.0;
restsel = pattern_selectivity(rest, ptype);
@@ -3020,10 +3078,13 @@ get_join_vars(List *args, Var **var1, Var **var2)
/*
* Extract the fixed prefix, if any, for a pattern.
- * *prefix is set to a palloc'd prefix string,
- * or to NULL if no fixed prefix exists for the pattern.
- * *rest is set to point to the remainder of the pattern after the
- * portion describing the fixed prefix.
+ *
+ * *prefix is set to a palloc'd prefix string (in the form of a Const node),
+ * or to NULL if no fixed prefix exists for the pattern.
+ * *rest is set to a palloc'd Const representing the remainder of the pattern
+ * after the portion describing the fixed prefix.
+ * Each of these has the same type (TEXT or BYTEA) as the given pattern Const.
+ *
* The return value distinguishes no fixed prefix, a partial prefix,
* or an exact-match-only pattern.
*/
@@ -3035,7 +3096,6 @@ like_fixed_prefix(Const *patt_const, bool case_insensitive,
char *match;
char *patt;
int pattlen;
- char *prefix;
char *rest;
Oid typeid = patt_const->consttype;
int pos,
@@ -3058,7 +3118,7 @@ like_fixed_prefix(Const *patt_const, bool case_insensitive,
pattlen = toast_raw_datum_size(patt_const->constvalue) - VARHDRSZ;
}
- prefix = match = palloc(pattlen + 1);
+ match = palloc(pattlen + 1);
match_pos = 0;
for (pos = 0; pos < pattlen; pos++)
@@ -3093,12 +3153,11 @@ like_fixed_prefix(Const *patt_const, bool case_insensitive,
match[match_pos] = '\0';
rest = &patt[pos];
- *prefix_const = string_to_const(prefix, typeid);
+ *prefix_const = string_to_const(match, typeid);
*rest_const = string_to_const(rest, typeid);
pfree(patt);
pfree(match);
- prefix = NULL;
/* in LIKE, an empty pattern is an exact match! */
if (pos == pattlen)
@@ -3120,7 +3179,6 @@ regex_fixed_prefix(Const *patt_const, bool case_insensitive,
match_pos,
paren_depth;
char *patt;
- char *prefix;
char *rest;
Oid typeid = patt_const->consttype;
@@ -3176,7 +3234,7 @@ regex_fixed_prefix(Const *patt_const, bool case_insensitive,
}
/* OK, allocate space for pattern */
- prefix = match = palloc(strlen(patt) + 1);
+ match = palloc(strlen(patt) + 1);
match_pos = 0;
/* note start at pos 1 to skip leading ^ */
@@ -3231,18 +3289,20 @@ regex_fixed_prefix(Const *patt_const, bool case_insensitive,
{
rest = &patt[pos + 1];
- *prefix_const = string_to_const(prefix, typeid);
+ *prefix_const = string_to_const(match, typeid);
*rest_const = string_to_const(rest, typeid);
+ pfree(patt);
+ pfree(match);
+
return Pattern_Prefix_Exact; /* pattern specifies exact match */
}
- *prefix_const = string_to_const(prefix, typeid);
+ *prefix_const = string_to_const(match, typeid);
*rest_const = string_to_const(rest, typeid);
pfree(patt);
pfree(match);
- prefix = NULL;
if (match_pos > 0)
return Pattern_Prefix_Partial;
@@ -3284,10 +3344,8 @@ pattern_fixed_prefix(Const *patt, Pattern_Type ptype,
* A fixed prefix "foo" is estimated as the selectivity of the expression
* "var >= 'foo' AND var < 'fop'" (see also indxqual.c).
*
- * Because of constant-folding, we can assume that the prefixcon constant's
- * type exactly matches the operator's declared input type; but it's not
- * safe to make the same assumption for the Var, so the type to use for the
- * Var must be passed in separately.
+ * We use the >= and < operators from the specified btree opclass to do the
+ * estimation. The given Var and Const must be of the associated datatype.
*
* XXX Note: we make use of the upper bound to estimate operator selectivity
* even if the locale is such that we cannot rely on the upper-bound string.
@@ -3295,27 +3353,17 @@ pattern_fixed_prefix(Const *patt, Pattern_Type ptype,
* more useful to use the upper-bound code than not.
*/
static Selectivity
-prefix_selectivity(Query *root, Var *var, Oid vartype, Const *prefixcon)
+prefix_selectivity(Query *root, Var *var, Oid opclass, Const *prefixcon)
{
Selectivity prefixsel;
Oid cmpopr;
- char *prefix;
List *cmpargs;
Const *greaterstrcon;
- cmpopr = find_operator(">=", vartype);
+ cmpopr = get_opclass_member(opclass, BTGreaterEqualStrategyNumber);
if (cmpopr == InvalidOid)
- elog(ERROR, "prefix_selectivity: no >= operator for type %u",
- vartype);
- if (prefixcon->consttype != BYTEAOID)
- prefix = DatumGetCString(DirectFunctionCall1(textout, prefixcon->constvalue));
- else
- prefix = DatumGetCString(DirectFunctionCall1(byteaout, prefixcon->constvalue));
-
- /* If var is type NAME, must adjust type of comparison constant */
- if (vartype == NAMEOID)
- prefixcon = string_to_const(prefix, NAMEOID);
-
+ elog(ERROR, "prefix_selectivity: no >= operator for opclass %u",
+ opclass);
cmpargs = makeList2(var, prefixcon);
/* Assume scalargtsel is appropriate for all supported types */
prefixsel = DatumGetFloat8(DirectFunctionCall4(scalargtsel,
@@ -3334,10 +3382,10 @@ prefix_selectivity(Query *root, Var *var, Oid vartype, Const *prefixcon)
{
Selectivity topsel;
- cmpopr = find_operator("<", vartype);
+ cmpopr = get_opclass_member(opclass, BTLessStrategyNumber);
if (cmpopr == InvalidOid)
- elog(ERROR, "prefix_selectivity: no < operator for type %u",
- vartype);
+ elog(ERROR, "prefix_selectivity: no < operator for opclass %u",
+ opclass);
cmpargs = makeList2(var, greaterstrcon);
/* Assume scalarltsel is appropriate for all supported types */
topsel = DatumGetFloat8(DirectFunctionCall4(scalarltsel,
@@ -3702,18 +3750,6 @@ make_greater_string(const Const *str_const)
return (Const *) NULL;
}
-/* See if there is a binary op of the given name for the given datatype */
-/* NB: we assume that only built-in system operators are searched for */
-static Oid
-find_operator(const char *opname, Oid datatype)
-{
- return GetSysCacheOid(OPERNAMENSP,
- PointerGetDatum(opname),
- ObjectIdGetDatum(datatype),
- ObjectIdGetDatum(datatype),
- ObjectIdGetDatum(PG_CATALOG_NAMESPACE));
-}
-
/*
* Generate a Datum of the appropriate type from a C string.
* Note that all of the supported types are pass-by-ref, so the
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 77fb26a4807..5085c6025c2 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/varchar.c,v 1.96 2003/05/12 23:08:50 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/varchar.c,v 1.97 2003/05/26 00:11:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -343,7 +343,10 @@ name_bpchar(PG_FUNCTION_ARGS)
/*****************************************************************************
- * varchar - varchar() *
+ * varchar - varchar(n)
+ *
+ * Note: varchar piggybacks on type text for most operations, and so has no
+ * C-coded functions except for I/O and typmod checking.
*****************************************************************************/
/*
@@ -700,7 +703,7 @@ bpcharcmp(PG_FUNCTION_ARGS)
/*
* bpchar needs a specialized hash function because we want to ignore
- * trailing blanks in comparisons. (varchar can use plain hashvarlena.)
+ * trailing blanks in comparisons.
*/
Datum
hashbpchar(PG_FUNCTION_ARGS)
@@ -720,187 +723,3 @@ hashbpchar(PG_FUNCTION_ARGS)
return result;
}
-
-
-/*****************************************************************************
- * Functions used for varchar
- *****************************************************************************/
-
-Datum
-varcharlen(PG_FUNCTION_ARGS)
-{
- VarChar *arg = PG_GETARG_VARCHAR_P(0);
-
- /* optimization for single byte encoding */
- if (pg_database_encoding_max_length() <= 1)
- PG_RETURN_INT32(VARSIZE(arg) - VARHDRSZ);
-
- PG_RETURN_INT32(
- pg_mbstrlen_with_len(VARDATA(arg), VARSIZE(arg) - VARHDRSZ)
- );
-}
-
-Datum
-varcharoctetlen(PG_FUNCTION_ARGS)
-{
- VarChar *arg = PG_GETARG_VARCHAR_P(0);
-
- PG_RETURN_INT32(VARSIZE(arg) - VARHDRSZ);
-}
-
-
-/*****************************************************************************
- * Comparison Functions used for varchar
- *
- * Note: btree indexes need these routines not to leak memory; therefore,
- * be careful to free working copies of toasted datums. Most places don't
- * need to be so careful.
- *****************************************************************************/
-
-Datum
-varchareq(PG_FUNCTION_ARGS)
-{
- VarChar *arg1 = PG_GETARG_VARCHAR_P(0);
- VarChar *arg2 = PG_GETARG_VARCHAR_P(1);
- int len1,
- len2;
- bool result;
-
- len1 = VARSIZE(arg1) - VARHDRSZ;
- len2 = VARSIZE(arg2) - VARHDRSZ;
-
- /* fast path for different-length inputs */
- if (len1 != len2)
- result = false;
- else
- result = (varstr_cmp(VARDATA(arg1), len1, VARDATA(arg2), len2) == 0);
-
- PG_FREE_IF_COPY(arg1, 0);
- PG_FREE_IF_COPY(arg2, 1);
-
- PG_RETURN_BOOL(result);
-}
-
-Datum
-varcharne(PG_FUNCTION_ARGS)
-{
- VarChar *arg1 = PG_GETARG_VARCHAR_P(0);
- VarChar *arg2 = PG_GETARG_VARCHAR_P(1);
- int len1,
- len2;
- bool result;
-
- len1 = VARSIZE(arg1) - VARHDRSZ;
- len2 = VARSIZE(arg2) - VARHDRSZ;
-
- /* fast path for different-length inputs */
- if (len1 != len2)
- result = true;
- else
- result = (varstr_cmp(VARDATA(arg1), len1, VARDATA(arg2), len2) != 0);
-
- PG_FREE_IF_COPY(arg1, 0);
- PG_FREE_IF_COPY(arg2, 1);
-
- PG_RETURN_BOOL(result);
-}
-
-Datum
-varcharlt(PG_FUNCTION_ARGS)
-{
- VarChar *arg1 = PG_GETARG_VARCHAR_P(0);
- VarChar *arg2 = PG_GETARG_VARCHAR_P(1);
- int len1,
- len2;
- int cmp;
-
- len1 = VARSIZE(arg1) - VARHDRSZ;
- len2 = VARSIZE(arg2) - VARHDRSZ;
-
- cmp = varstr_cmp(VARDATA(arg1), len1, VARDATA(arg2), len2);
-
- PG_FREE_IF_COPY(arg1, 0);
- PG_FREE_IF_COPY(arg2, 1);
-
- PG_RETURN_BOOL(cmp < 0);
-}
-
-Datum
-varcharle(PG_FUNCTION_ARGS)
-{
- VarChar *arg1 = PG_GETARG_VARCHAR_P(0);
- VarChar *arg2 = PG_GETARG_VARCHAR_P(1);
- int len1,
- len2;
- int cmp;
-
- len1 = VARSIZE(arg1) - VARHDRSZ;
- len2 = VARSIZE(arg2) - VARHDRSZ;
-
- cmp = varstr_cmp(VARDATA(arg1), len1, VARDATA(arg2), len2);
-
- PG_FREE_IF_COPY(arg1, 0);
- PG_FREE_IF_COPY(arg2, 1);
-
- PG_RETURN_BOOL(cmp <= 0);
-}
-
-Datum
-varchargt(PG_FUNCTION_ARGS)
-{
- VarChar *arg1 = PG_GETARG_VARCHAR_P(0);
- VarChar *arg2 = PG_GETARG_VARCHAR_P(1);
- int len1,
- len2;
- int cmp;
-
- len1 = VARSIZE(arg1) - VARHDRSZ;
- len2 = VARSIZE(arg2) - VARHDRSZ;
-
- cmp = varstr_cmp(VARDATA(arg1), len1, VARDATA(arg2), len2);
-
- PG_FREE_IF_COPY(arg1, 0);
- PG_FREE_IF_COPY(arg2, 1);
-
- PG_RETURN_BOOL(cmp > 0);
-}
-
-Datum
-varcharge(PG_FUNCTION_ARGS)
-{
- VarChar *arg1 = PG_GETARG_VARCHAR_P(0);
- VarChar *arg2 = PG_GETARG_VARCHAR_P(1);
- int len1,
- len2;
- int cmp;
-
- len1 = VARSIZE(arg1) - VARHDRSZ;
- len2 = VARSIZE(arg2) - VARHDRSZ;
-
- cmp = varstr_cmp(VARDATA(arg1), len1, VARDATA(arg2), len2);
-
- PG_FREE_IF_COPY(arg1, 0);
- PG_FREE_IF_COPY(arg2, 1);
-
- PG_RETURN_BOOL(cmp >= 0);
-}
-
-Datum
-varcharcmp(PG_FUNCTION_ARGS)
-{
- VarChar *arg1 = PG_GETARG_VARCHAR_P(0);
- VarChar *arg2 = PG_GETARG_VARCHAR_P(1);
- int len1,
- len2;
- int cmp;
-
- len1 = VARSIZE(arg1) - VARHDRSZ;
- len2 = VARSIZE(arg2) - VARHDRSZ;
-
- cmp = varstr_cmp(VARDATA(arg1), len1, VARDATA(arg2), len2);
-
- PG_FREE_IF_COPY(arg1, 0);
- PG_FREE_IF_COPY(arg2, 1);
-
- PG_RETURN_INT32(cmp);
-}
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 19178cc5243..fcd9dc2f59b 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.94 2003/05/13 04:38:58 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.95 2003/05/26 00:11:27 tgl Exp $
*
* NOTES
* Eventually, the index information should go through here, too.
@@ -80,6 +80,33 @@ op_requires_recheck(Oid opno, Oid opclass)
return result;
}
+/*
+ * get_opclass_member
+ * Get the OID of the operator that implements the specified strategy
+ * for the specified opclass.
+ *
+ * Returns InvalidOid if there is no pg_amop entry for the given keys.
+ */
+Oid
+get_opclass_member(Oid opclass, int16 strategy)
+{
+ HeapTuple tp;
+ Form_pg_amop amop_tup;
+ Oid result;
+
+ tp = SearchSysCache(AMOPSTRATEGY,
+ ObjectIdGetDatum(opclass),
+ Int16GetDatum(strategy),
+ 0, 0);
+ if (!HeapTupleIsValid(tp))
+ return InvalidOid;
+ amop_tup = (Form_pg_amop) GETSTRUCT(tp);
+ result = amop_tup->amopopr;
+ ReleaseSysCache(tp);
+ return result;
+}
+
+
/* ---------- ATTRIBUTE CACHES ---------- */
/*