On Tue, 2005-01-18 at 00:54 -0500, Tom Lane wrote: > I won't stand in the way of you doing this
Attached is a revised patch. Barring any objections, I intend to apply this sometime tomorrow. -Neil
Index: src/backend/parser/parse_expr.c =================================================================== RCS file: /var/lib/cvs/pgsql/src/backend/parser/parse_expr.c,v retrieving revision 1.179 diff -c -r1.179 parse_expr.c *** src/backend/parser/parse_expr.c 12 Jan 2005 17:32:36 -0000 1.179 --- src/backend/parser/parse_expr.c 19 Jan 2005 07:07:48 -0000 *************** *** 37,45 **** --- 37,63 ---- bool Transform_null_equals = false; + static Node *transformParamRef(ParseState *pstate, ParamRef *pref); + static Node *transformAExprOp(ParseState *pstate, A_Expr *a); + static Node *transformAExprAnd(ParseState *pstate, A_Expr *a); + static Node *transformAExprOr(ParseState *pstate, A_Expr *a); + static Node *transformAExprNot(ParseState *pstate, A_Expr *a); + static Node *transformAExprOpAny(ParseState *pstate, A_Expr *a); + static Node *transformAExprOpAll(ParseState *pstate, A_Expr *a); + static Node *transformAExprDistinct(ParseState *pstate, A_Expr *a); + static Node *transformAExprNullIf(ParseState *pstate, A_Expr *a); + static Node *transformAExprOf(ParseState *pstate, A_Expr *a); + static Node *transformFuncCall(ParseState *pstate, FuncCall *fn); + static Node *transformCaseExpr(ParseState *pstate, CaseExpr *c); + static Node *transformSubLink(ParseState *pstate, SubLink *sublink); + static Node *transformArrayExpr(ParseState *pstate, ArrayExpr *a); + static Node *transformRowExpr(ParseState *pstate, RowExpr *r); + static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c); + static Node *transformBooleanTest(ParseState *pstate, BooleanTest *b); static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref); static Node *transformWholeRowRef(ParseState *pstate, char *schemaname, char *relname); + static Node *transformBooleanTest(ParseState *pstate, BooleanTest *b); static Node *transformIndirection(ParseState *pstate, Node *basenode, List *indirection); static Node *typecast_expression(ParseState *pstate, Node *expr, *************** *** 90,154 **** switch (nodeTag(expr)) { case T_ColumnRef: ! { ! result = transformColumnRef(pstate, (ColumnRef *) expr); ! break; ! } ! case T_ParamRef: ! { ! ParamRef *pref = (ParamRef *) expr; ! int paramno = pref->number; ! ParseState *toppstate; ! Param *param; ! ! /* ! * Find topmost ParseState, which is where paramtype info ! * lives. ! */ ! toppstate = pstate; ! while (toppstate->parentParseState != NULL) ! toppstate = toppstate->parentParseState; ! /* Check parameter number is in range */ ! if (paramno <= 0) /* probably can't happen? */ ! ereport(ERROR, ! (errcode(ERRCODE_UNDEFINED_PARAMETER), ! errmsg("there is no parameter $%d", paramno))); ! if (paramno > toppstate->p_numparams) ! { ! if (!toppstate->p_variableparams) ! ereport(ERROR, ! (errcode(ERRCODE_UNDEFINED_PARAMETER), ! errmsg("there is no parameter $%d", ! paramno))); ! /* Okay to enlarge param array */ ! if (toppstate->p_paramtypes) ! toppstate->p_paramtypes = ! (Oid *) repalloc(toppstate->p_paramtypes, ! paramno * sizeof(Oid)); ! else ! toppstate->p_paramtypes = ! (Oid *) palloc(paramno * sizeof(Oid)); ! /* Zero out the previously-unreferenced slots */ ! MemSet(toppstate->p_paramtypes + toppstate->p_numparams, ! 0, ! (paramno - toppstate->p_numparams) * sizeof(Oid)); ! toppstate->p_numparams = paramno; ! } ! if (toppstate->p_variableparams) ! { ! /* If not seen before, initialize to UNKNOWN type */ ! if (toppstate->p_paramtypes[paramno - 1] == InvalidOid) ! toppstate->p_paramtypes[paramno - 1] = UNKNOWNOID; ! } - param = makeNode(Param); - param->paramkind = PARAM_NUM; - param->paramid = (AttrNumber) paramno; - param->paramtype = toppstate->p_paramtypes[paramno - 1]; - result = (Node *) param; - break; - } case T_A_Const: { A_Const *con = (A_Const *) expr; --- 108,120 ---- switch (nodeTag(expr)) { case T_ColumnRef: ! result = transformColumnRef(pstate, (ColumnRef *) expr); ! break; ! case T_ParamRef: ! result = transformParamRef(pstate, (ParamRef *) expr); ! break; case T_A_Const: { A_Const *con = (A_Const *) expr; *************** *** 160,165 **** --- 126,132 ---- con->typename); break; } + case T_A_Indirection: { A_Indirection *ind = (A_Indirection *) expr; *************** *** 169,174 **** --- 136,142 ---- ind->indirection); break; } + case T_TypeCast: { TypeCast *tc = (TypeCast *) expr; *************** *** 177,182 **** --- 145,151 ---- result = typecast_expression(pstate, arg, tc->typename); break; } + case T_A_Expr: { A_Expr *a = (A_Expr *) expr; *************** *** 184,884 **** switch (a->kind) { case AEXPR_OP: ! { ! Node *lexpr = a->lexpr; ! Node *rexpr = a->rexpr; ! ! /* ! * Special-case "foo = NULL" and "NULL = foo" ! * for compatibility with standards-broken ! * products (like Microsoft's). Turn these ! * into IS NULL exprs. ! */ ! if (Transform_null_equals && ! list_length(a->name) == 1 && ! strcmp(strVal(linitial(a->name)), "=") == 0 && ! (exprIsNullConstant(lexpr) || ! exprIsNullConstant(rexpr))) ! { ! NullTest *n = makeNode(NullTest); ! ! n->nulltesttype = IS_NULL; ! ! if (exprIsNullConstant(lexpr)) ! n->arg = (Expr *) rexpr; ! else ! n->arg = (Expr *) lexpr; ! ! result = transformExpr(pstate, ! (Node *) n); ! } ! else if (lexpr && IsA(lexpr, RowExpr) && ! rexpr && IsA(rexpr, SubLink) && ! ((SubLink *) rexpr)->subLinkType == EXPR_SUBLINK) ! { ! /* ! * Convert "row op subselect" into a ! * MULTIEXPR sublink. Formerly the ! * grammar did this, but now that a row ! * construct is allowed anywhere in ! * expressions, it's easier to do it here. ! */ ! SubLink *s = (SubLink *) rexpr; ! ! s->subLinkType = MULTIEXPR_SUBLINK; ! s->lefthand = ((RowExpr *) lexpr)->args; ! s->operName = a->name; ! result = transformExpr(pstate, (Node *) s); ! } ! else if (lexpr && IsA(lexpr, RowExpr) && ! rexpr && IsA(rexpr, RowExpr)) ! { ! /* "row op row" */ ! result = make_row_op(pstate, a->name, ! lexpr, rexpr); ! } ! else ! { ! /* Ordinary scalar operator */ ! lexpr = transformExpr(pstate, lexpr); ! rexpr = transformExpr(pstate, rexpr); ! ! result = (Node *) make_op(pstate, ! a->name, ! lexpr, ! rexpr); ! } ! } break; case AEXPR_AND: ! { ! Node *lexpr = transformExpr(pstate, ! a->lexpr); ! Node *rexpr = transformExpr(pstate, ! a->rexpr); ! ! lexpr = coerce_to_boolean(pstate, lexpr, "AND"); ! rexpr = coerce_to_boolean(pstate, rexpr, "AND"); ! ! result = (Node *) makeBoolExpr(AND_EXPR, ! list_make2(lexpr, ! rexpr)); ! } break; case AEXPR_OR: ! { ! Node *lexpr = transformExpr(pstate, ! a->lexpr); ! Node *rexpr = transformExpr(pstate, ! a->rexpr); ! ! lexpr = coerce_to_boolean(pstate, lexpr, "OR"); ! rexpr = coerce_to_boolean(pstate, rexpr, "OR"); ! ! result = (Node *) makeBoolExpr(OR_EXPR, ! list_make2(lexpr, ! rexpr)); ! } break; case AEXPR_NOT: ! { ! Node *rexpr = transformExpr(pstate, ! a->rexpr); ! ! rexpr = coerce_to_boolean(pstate, rexpr, "NOT"); ! ! result = (Node *) makeBoolExpr(NOT_EXPR, ! list_make1(rexpr)); ! } break; case AEXPR_OP_ANY: ! { ! Node *lexpr = transformExpr(pstate, ! a->lexpr); ! Node *rexpr = transformExpr(pstate, ! a->rexpr); ! ! result = (Node *) make_scalar_array_op(pstate, ! a->name, ! true, ! lexpr, ! rexpr); ! } break; case AEXPR_OP_ALL: ! { ! Node *lexpr = transformExpr(pstate, ! a->lexpr); ! Node *rexpr = transformExpr(pstate, ! a->rexpr); ! ! result = (Node *) make_scalar_array_op(pstate, ! a->name, ! false, ! lexpr, ! rexpr); ! } break; case AEXPR_DISTINCT: ! { ! Node *lexpr = a->lexpr; ! Node *rexpr = a->rexpr; ! ! if (lexpr && IsA(lexpr, RowExpr) && ! rexpr && IsA(rexpr, RowExpr)) ! { ! /* "row op row" */ ! result = make_row_distinct_op(pstate, a->name, ! lexpr, rexpr); ! } ! else ! { ! /* Ordinary scalar operator */ ! lexpr = transformExpr(pstate, lexpr); ! rexpr = transformExpr(pstate, rexpr); ! ! result = (Node *) make_distinct_op(pstate, ! a->name, ! lexpr, ! rexpr); ! } ! } break; case AEXPR_NULLIF: ! { ! Node *lexpr = transformExpr(pstate, ! a->lexpr); ! Node *rexpr = transformExpr(pstate, ! a->rexpr); ! ! result = (Node *) make_op(pstate, ! a->name, ! lexpr, ! rexpr); ! if (((OpExpr *) result)->opresulttype != BOOLOID) ! ereport(ERROR, ! (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("NULLIF requires = operator to yield boolean"))); ! ! /* ! * We rely on NullIfExpr and OpExpr being same ! * struct ! */ ! NodeSetTag(result, T_NullIfExpr); ! } break; case AEXPR_OF: ! { ! /* ! * Checking an expression for match to type. ! * Will result in a boolean constant node. ! */ ! ListCell *telem; ! A_Const *n; ! Oid ltype, ! rtype; ! bool matched = FALSE; ! Node *lexpr = transformExpr(pstate, ! a->lexpr); ! ! ltype = exprType(lexpr); ! foreach(telem, (List *) a->rexpr) ! { ! rtype = LookupTypeName(lfirst(telem)); ! matched = (rtype == ltype); ! if (matched) ! break; ! } ! ! /* ! * Expect two forms: equals or not equals. ! * Flip the sense of the result for not ! * equals. ! */ ! if (strcmp(strVal(linitial(a->name)), "!=") == 0) ! matched = (!matched); ! ! n = makeNode(A_Const); ! n->val.type = T_String; ! n->val.val.str = (matched ? "t" : "f"); ! n->typename = SystemTypeName("bool"); ! ! result = transformExpr(pstate, (Node *) n); ! } break; } break; } case T_FuncCall: ! { ! FuncCall *fn = (FuncCall *) expr; ! List *targs; ! ListCell *args; - /* - * Transform the list of arguments. We use a shallow list - * copy and then transform-in-place to avoid O(N^2) - * behavior from repeated lappend's. - * - * XXX: repeated lappend() would no longer result in O(n^2) - * behavior; worth reconsidering this design? - */ - targs = list_copy(fn->args); - foreach(args, targs) - { - lfirst(args) = transformExpr(pstate, - (Node *) lfirst(args)); - } - result = ParseFuncOrColumn(pstate, - fn->funcname, - targs, - fn->agg_star, - fn->agg_distinct, - false); - break; - } case T_SubLink: ! { ! SubLink *sublink = (SubLink *) expr; ! List *qtrees; ! Query *qtree; ! ! /* If we already transformed this node, do nothing */ ! if (IsA(sublink->subselect, Query)) ! { ! result = expr; ! break; ! } ! pstate->p_hasSubLinks = true; ! qtrees = parse_sub_analyze(sublink->subselect, pstate); ! if (list_length(qtrees) != 1) ! elog(ERROR, "bad query in sub-select"); ! qtree = (Query *) linitial(qtrees); ! if (qtree->commandType != CMD_SELECT || ! qtree->resultRelation != 0) ! elog(ERROR, "bad query in sub-select"); ! sublink->subselect = (Node *) qtree; ! ! if (sublink->subLinkType == EXISTS_SUBLINK) ! { ! /* ! * EXISTS needs no lefthand or combining operator. ! * These fields should be NIL already, but make sure. ! */ ! sublink->lefthand = NIL; ! sublink->operName = NIL; ! sublink->operOids = NIL; ! sublink->useOr = FALSE; ! } ! else if (sublink->subLinkType == EXPR_SUBLINK || ! sublink->subLinkType == ARRAY_SUBLINK) ! { ! ListCell *tlist_item = list_head(qtree->targetList); ! ! /* ! * Make sure the subselect delivers a single column ! * (ignoring resjunk targets). ! */ ! if (tlist_item == NULL || ! ((TargetEntry *) lfirst(tlist_item))->resdom->resjunk) ! ereport(ERROR, ! (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("subquery must return a column"))); ! while ((tlist_item = lnext(tlist_item)) != NULL) ! { ! if (!((TargetEntry *) lfirst(tlist_item))->resdom->resjunk) ! ereport(ERROR, ! (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("subquery must return only one column"))); ! } ! ! /* ! * EXPR and ARRAY need no lefthand or combining ! * operator. These fields should be NIL already, but ! * make sure. ! */ ! sublink->lefthand = NIL; ! sublink->operName = NIL; ! sublink->operOids = NIL; ! sublink->useOr = FALSE; ! } ! else ! { ! /* ALL, ANY, or MULTIEXPR: generate operator list */ ! List *left_list = sublink->lefthand; ! List *right_list = qtree->targetList; ! int row_length = list_length(left_list); ! bool needNot = false; ! List *op = sublink->operName; ! char *opname = strVal(llast(op)); ! ListCell *l; ! ListCell *ll_item; ! ! /* transform lefthand expressions */ ! foreach(l, left_list) ! lfirst(l) = transformExpr(pstate, lfirst(l)); ! ! /* ! * If the expression is "<> ALL" (with unqualified ! * opname) then convert it to "NOT IN". This is a ! * hack to improve efficiency of expressions output by ! * pre-7.4 Postgres. ! */ ! if (sublink->subLinkType == ALL_SUBLINK && ! list_length(op) == 1 && strcmp(opname, "<>") == 0) ! { ! sublink->subLinkType = ANY_SUBLINK; ! opname = pstrdup("="); ! op = list_make1(makeString(opname)); ! sublink->operName = op; ! needNot = true; ! } ! ! /* Set useOr if op is "<>" (possibly qualified) */ ! if (strcmp(opname, "<>") == 0) ! sublink->useOr = TRUE; ! else ! sublink->useOr = FALSE; ! ! /* Combining operators other than =/<> is dubious... */ ! if (row_length != 1 && ! strcmp(opname, "=") != 0 && ! strcmp(opname, "<>") != 0) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("row comparison cannot use operator %s", ! opname))); ! ! /* ! * To build the list of combining operator OIDs, we ! * must scan subquery's targetlist to find values that ! * will be matched against lefthand values. We need ! * to ignore resjunk targets, so doing the outer ! * iteration over right_list is easier than doing it ! * over left_list. ! */ ! sublink->operOids = NIL; ! ! ll_item = list_head(left_list); ! foreach(l, right_list) ! { ! TargetEntry *tent = (TargetEntry *) lfirst(l); ! Node *lexpr; ! Operator optup; ! Form_pg_operator opform; ! ! if (tent->resdom->resjunk) ! continue; ! ! if (ll_item == NULL) ! ereport(ERROR, ! (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("subquery has too many columns"))); ! lexpr = lfirst(ll_item); ! ll_item = lnext(ll_item); ! ! /* ! * It's OK to use oper() not compatible_oper() ! * here, because make_subplan() will insert type ! * coercion calls if needed. ! */ ! optup = oper(op, ! exprType(lexpr), ! exprType((Node *) tent->expr), ! false); ! opform = (Form_pg_operator) GETSTRUCT(optup); ! ! if (opform->oprresult != BOOLOID) ! ereport(ERROR, ! (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("operator %s must return type boolean, not type %s", ! opname, ! format_type_be(opform->oprresult)), ! errhint("The operator of a quantified predicate subquery must return type boolean."))); ! ! if (get_func_retset(opform->oprcode)) ! ereport(ERROR, ! (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("operator %s must not return a set", ! opname), ! errhint("The operator of a quantified predicate subquery must return type boolean."))); ! ! sublink->operOids = lappend_oid(sublink->operOids, ! oprid(optup)); ! ! ReleaseSysCache(optup); ! } ! if (ll_item != NULL) ! ereport(ERROR, ! (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("subquery has too few columns"))); ! ! if (needNot) ! { ! expr = coerce_to_boolean(pstate, expr, "NOT"); ! expr = (Node *) makeBoolExpr(NOT_EXPR, ! list_make1(expr)); ! } ! } ! result = (Node *) expr; ! break; ! } case T_CaseExpr: ! { ! CaseExpr *c = (CaseExpr *) expr; ! CaseExpr *newc; ! Node *arg; ! CaseTestExpr *placeholder; ! List *newargs; ! List *typeids; ! ListCell *l; ! Node *defresult; ! Oid ptype; ! ! /* If we already transformed this node, do nothing */ ! if (OidIsValid(c->casetype)) ! { ! result = expr; ! break; ! } ! newc = makeNode(CaseExpr); ! ! /* transform the test expression, if any */ ! arg = transformExpr(pstate, (Node *) c->arg); ! ! /* generate placeholder for test expression */ ! if (arg) ! { ! /* ! * If test expression is an untyped literal, force it to ! * text. We have to do something now because we won't be ! * able to do this coercion on the placeholder. This is ! * not as flexible as what was done in 7.4 and before, ! * but it's good enough to handle the sort of silly ! * coding commonly seen. ! */ ! if (exprType(arg) == UNKNOWNOID) ! arg = coerce_to_common_type(pstate, arg, ! TEXTOID, "CASE"); ! placeholder = makeNode(CaseTestExpr); ! placeholder->typeId = exprType(arg); ! placeholder->typeMod = exprTypmod(arg); ! } ! else ! placeholder = NULL; ! ! newc->arg = (Expr *) arg; ! ! /* transform the list of arguments */ ! newargs = NIL; ! typeids = NIL; ! foreach(l, c->args) ! { ! CaseWhen *w = (CaseWhen *) lfirst(l); ! CaseWhen *neww = makeNode(CaseWhen); ! Node *warg; ! ! Assert(IsA(w, CaseWhen)); ! ! warg = (Node *) w->expr; ! if (placeholder) ! { ! /* shorthand form was specified, so expand... */ ! warg = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", ! (Node *) placeholder, ! warg); ! } ! neww->expr = (Expr *) transformExpr(pstate, warg); ! ! neww->expr = (Expr *) coerce_to_boolean(pstate, ! (Node *) neww->expr, ! "CASE/WHEN"); ! ! warg = (Node *) w->result; ! neww->result = (Expr *) transformExpr(pstate, warg); ! ! newargs = lappend(newargs, neww); ! typeids = lappend_oid(typeids, exprType((Node *) neww->result)); ! } ! ! newc->args = newargs; ! ! /* transform the default clause */ ! defresult = (Node *) c->defresult; ! if (defresult == NULL) ! { ! A_Const *n = makeNode(A_Const); ! ! n->val.type = T_Null; ! defresult = (Node *) n; ! } ! newc->defresult = (Expr *) transformExpr(pstate, defresult); ! ! /* ! * Note: default result is considered the most significant ! * type in determining preferred type. This is how the ! * code worked before, but it seems a little bogus to me ! * --- tgl ! */ ! typeids = lcons_oid(exprType((Node *) newc->defresult), typeids); ! ! ptype = select_common_type(typeids, "CASE"); ! Assert(OidIsValid(ptype)); ! newc->casetype = ptype; ! ! /* Convert default result clause, if necessary */ ! newc->defresult = (Expr *) ! coerce_to_common_type(pstate, ! (Node *) newc->defresult, ! ptype, ! "CASE/ELSE"); ! ! /* Convert when-clause results, if necessary */ ! foreach(l, newc->args) ! { ! CaseWhen *w = (CaseWhen *) lfirst(l); ! ! w->result = (Expr *) ! coerce_to_common_type(pstate, ! (Node *) w->result, ! ptype, ! "CASE/WHEN"); ! } ! ! result = (Node *) newc; ! break; ! } case T_ArrayExpr: ! { ! ArrayExpr *a = (ArrayExpr *) expr; ! ArrayExpr *newa = makeNode(ArrayExpr); ! List *newelems = NIL; ! List *newcoercedelems = NIL; ! List *typeids = NIL; ! ListCell *element; ! Oid array_type; ! Oid element_type; ! ! /* Transform the element expressions */ ! foreach(element, a->elements) ! { ! Node *e = (Node *) lfirst(element); ! Node *newe; ! ! newe = transformExpr(pstate, e); ! newelems = lappend(newelems, newe); ! typeids = lappend_oid(typeids, exprType(newe)); ! } ! ! /* Select a common type for the elements */ ! element_type = select_common_type(typeids, "ARRAY"); ! ! /* Coerce arguments to common type if necessary */ ! foreach(element, newelems) ! { ! Node *e = (Node *) lfirst(element); ! Node *newe; ! ! newe = coerce_to_common_type(pstate, e, ! element_type, ! "ARRAY"); ! newcoercedelems = lappend(newcoercedelems, newe); ! } ! ! /* Do we have an array type to use? */ ! array_type = get_array_type(element_type); ! if (array_type != InvalidOid) ! { ! /* Elements are presumably of scalar type */ ! newa->multidims = false; ! } ! else ! { ! /* Must be nested array expressions */ ! newa->multidims = true; ! ! array_type = element_type; ! element_type = get_element_type(array_type); ! if (!OidIsValid(element_type)) ! ereport(ERROR, ! (errcode(ERRCODE_UNDEFINED_OBJECT), ! errmsg("could not find array type for data type %s", ! format_type_be(array_type)))); ! } ! ! newa->array_typeid = array_type; ! newa->element_typeid = element_type; ! newa->elements = newcoercedelems; ! ! result = (Node *) newa; ! break; ! } case T_RowExpr: ! { ! RowExpr *r = (RowExpr *) expr; ! RowExpr *newr = makeNode(RowExpr); ! List *newargs = NIL; ! ListCell *arg; ! ! /* Transform the field expressions */ ! foreach(arg, r->args) ! { ! Node *e = (Node *) lfirst(arg); ! Node *newe; ! ! newe = transformExpr(pstate, e); ! newargs = lappend(newargs, newe); ! } ! newr->args = newargs; ! ! /* Barring later casting, we consider the type RECORD */ ! newr->row_typeid = RECORDOID; ! newr->row_format = COERCE_IMPLICIT_CAST; ! ! result = (Node *) newr; ! break; ! } case T_CoalesceExpr: ! { ! CoalesceExpr *c = (CoalesceExpr *) expr; ! CoalesceExpr *newc = makeNode(CoalesceExpr); ! List *newargs = NIL; ! List *newcoercedargs = NIL; ! List *typeids = NIL; ! ListCell *args; ! ! foreach(args, c->args) ! { ! Node *e = (Node *) lfirst(args); ! Node *newe; ! ! newe = transformExpr(pstate, e); ! newargs = lappend(newargs, newe); ! typeids = lappend_oid(typeids, exprType(newe)); ! } ! ! newc->coalescetype = select_common_type(typeids, "COALESCE"); ! ! /* Convert arguments if necessary */ ! foreach(args, newargs) ! { ! Node *e = (Node *) lfirst(args); ! Node *newe; ! ! newe = coerce_to_common_type(pstate, e, ! newc->coalescetype, ! "COALESCE"); ! newcoercedargs = lappend(newcoercedargs, newe); ! } ! ! newc->args = newcoercedargs; ! result = (Node *) newc; ! break; ! } case T_NullTest: { --- 153,213 ---- switch (a->kind) { case AEXPR_OP: ! result = transformAExprOp(pstate, a); break; case AEXPR_AND: ! result = transformAExprAnd(pstate, a); break; case AEXPR_OR: ! result = transformAExprOr(pstate, a); break; case AEXPR_NOT: ! result = transformAExprNot(pstate, a); break; case AEXPR_OP_ANY: ! result = transformAExprOpAny(pstate, a); break; case AEXPR_OP_ALL: ! result = transformAExprOpAll(pstate, a); break; case AEXPR_DISTINCT: ! result = transformAExprDistinct(pstate, a); break; case AEXPR_NULLIF: ! result = transformAExprNullIf(pstate, a); break; case AEXPR_OF: ! result = transformAExprOf(pstate, a); break; + default: + elog(ERROR, "unrecognized A_Expr kind: %d", a->kind); } break; } + case T_FuncCall: ! result = transformFuncCall(pstate, (FuncCall *) expr); ! break; case T_SubLink: ! result = transformSubLink(pstate, (SubLink *) expr); ! break; case T_CaseExpr: ! result = transformCaseExpr(pstate, (CaseExpr *) expr); ! break; case T_ArrayExpr: ! result = transformArrayExpr(pstate, (ArrayExpr *) expr); ! break; case T_RowExpr: ! result = transformRowExpr(pstate, (RowExpr *) expr); ! break; case T_CoalesceExpr: ! result = transformCoalesceExpr(pstate, (CoalesceExpr *) expr); ! break; case T_NullTest: { *************** *** 891,935 **** } case T_BooleanTest: ! { ! BooleanTest *b = (BooleanTest *) expr; ! const char *clausename; ! ! switch (b->booltesttype) ! { ! case IS_TRUE: ! clausename = "IS TRUE"; ! break; ! case IS_NOT_TRUE: ! clausename = "IS NOT TRUE"; ! break; ! case IS_FALSE: ! clausename = "IS FALSE"; ! break; ! case IS_NOT_FALSE: ! clausename = "IS NOT FALSE"; ! break; ! case IS_UNKNOWN: ! clausename = "IS UNKNOWN"; ! break; ! case IS_NOT_UNKNOWN: ! clausename = "IS NOT UNKNOWN"; ! break; ! default: ! elog(ERROR, "unrecognized booltesttype: %d", ! (int) b->booltesttype); ! clausename = NULL; /* keep compiler quiet */ ! } ! ! b->arg = (Expr *) transformExpr(pstate, (Node *) b->arg); ! ! b->arg = (Expr *) coerce_to_boolean(pstate, ! (Node *) b->arg, ! clausename); ! ! result = expr; ! break; ! } /********************************************* * Quietly accept node types that may be presented when we are --- 220,227 ---- } case T_BooleanTest: ! result = transformBooleanTest(pstate, (BooleanTest *) expr); ! break; /********************************************* * Quietly accept node types that may be presented when we are *************** *** 1203,1208 **** --- 495,1278 ---- return node; } + static Node * + transformParamRef(ParseState *pstate, ParamRef *pref) + { + int paramno = pref->number; + ParseState *toppstate; + Param *param; + + /* + * Find topmost ParseState, which is where paramtype info lives. + */ + toppstate = pstate; + while (toppstate->parentParseState != NULL) + toppstate = toppstate->parentParseState; + + /* Check parameter number is in range */ + if (paramno <= 0) /* probably can't happen? */ + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("there is no parameter $%d", paramno))); + if (paramno > toppstate->p_numparams) + { + if (!toppstate->p_variableparams) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("there is no parameter $%d", + paramno))); + /* Okay to enlarge param array */ + if (toppstate->p_paramtypes) + toppstate->p_paramtypes = + (Oid *) repalloc(toppstate->p_paramtypes, + paramno * sizeof(Oid)); + else + toppstate->p_paramtypes = + (Oid *) palloc(paramno * sizeof(Oid)); + /* Zero out the previously-unreferenced slots */ + MemSet(toppstate->p_paramtypes + toppstate->p_numparams, + 0, + (paramno - toppstate->p_numparams) * sizeof(Oid)); + toppstate->p_numparams = paramno; + } + if (toppstate->p_variableparams) + { + /* If not seen before, initialize to UNKNOWN type */ + if (toppstate->p_paramtypes[paramno - 1] == InvalidOid) + toppstate->p_paramtypes[paramno - 1] = UNKNOWNOID; + } + + param = makeNode(Param); + param->paramkind = PARAM_NUM; + param->paramid = (AttrNumber) paramno; + param->paramtype = toppstate->p_paramtypes[paramno - 1]; + + return (Node *) param; + } + + static Node * + transformAExprOp(ParseState *pstate, A_Expr *a) + { + Node *lexpr = a->lexpr; + Node *rexpr = a->rexpr; + Node *result; + + /* + * Special-case "foo = NULL" and "NULL = foo" for compatibility + * with standards-broken products (like Microsoft's). Turn these + * into IS NULL exprs. + */ + if (Transform_null_equals && + list_length(a->name) == 1 && + strcmp(strVal(linitial(a->name)), "=") == 0 && + (exprIsNullConstant(lexpr) || + exprIsNullConstant(rexpr))) + { + NullTest *n = makeNode(NullTest); + + n->nulltesttype = IS_NULL; + + if (exprIsNullConstant(lexpr)) + n->arg = (Expr *) rexpr; + else + n->arg = (Expr *) lexpr; + + result = transformExpr(pstate, (Node *) n); + } + else if (lexpr && IsA(lexpr, RowExpr) && + rexpr && IsA(rexpr, SubLink) && + ((SubLink *) rexpr)->subLinkType == EXPR_SUBLINK) + { + /* + * Convert "row op subselect" into a MULTIEXPR sublink. + * Formerly the grammar did this, but now that a row construct + * is allowed anywhere in expressions, it's easier to do it + * here. + */ + SubLink *s = (SubLink *) rexpr; + + s->subLinkType = MULTIEXPR_SUBLINK; + s->lefthand = ((RowExpr *) lexpr)->args; + s->operName = a->name; + result = transformExpr(pstate, (Node *) s); + } + else if (lexpr && IsA(lexpr, RowExpr) && + rexpr && IsA(rexpr, RowExpr)) + { + /* "row op row" */ + result = make_row_op(pstate, a->name, + lexpr, rexpr); + } + else + { + /* Ordinary scalar operator */ + lexpr = transformExpr(pstate, lexpr); + rexpr = transformExpr(pstate, rexpr); + + result = (Node *) make_op(pstate, + a->name, + lexpr, + rexpr); + } + + return result; + } + + static Node * + transformAExprAnd(ParseState *pstate, A_Expr *a) + { + Node *lexpr = transformExpr(pstate, a->lexpr); + Node *rexpr = transformExpr(pstate, a->rexpr); + + lexpr = coerce_to_boolean(pstate, lexpr, "AND"); + rexpr = coerce_to_boolean(pstate, rexpr, "AND"); + + return (Node *) makeBoolExpr(AND_EXPR, + list_make2(lexpr, + rexpr)); + } + + static Node * + transformAExprOr(ParseState *pstate, A_Expr *a) + { + Node *lexpr = transformExpr(pstate, a->lexpr); + Node *rexpr = transformExpr(pstate, a->rexpr); + + lexpr = coerce_to_boolean(pstate, lexpr, "OR"); + rexpr = coerce_to_boolean(pstate, rexpr, "OR"); + + return (Node *) makeBoolExpr(OR_EXPR, + list_make2(lexpr, + rexpr)); + } + + static Node * + transformAExprNot(ParseState *pstate, A_Expr *a) + { + Node *rexpr = transformExpr(pstate, a->rexpr); + + rexpr = coerce_to_boolean(pstate, rexpr, "NOT"); + + return (Node *) makeBoolExpr(NOT_EXPR, + list_make1(rexpr)); + } + + static Node * + transformAExprOpAny(ParseState *pstate, A_Expr *a) + { + Node *lexpr = transformExpr(pstate, a->lexpr); + Node *rexpr = transformExpr(pstate, a->rexpr); + + return (Node *) make_scalar_array_op(pstate, + a->name, + true, + lexpr, + rexpr); + } + + static Node * + transformAExprOpAll(ParseState *pstate, A_Expr *a) + { + Node *lexpr = transformExpr(pstate, a->lexpr); + Node *rexpr = transformExpr(pstate, a->rexpr); + + return (Node *) make_scalar_array_op(pstate, + a->name, + false, + lexpr, + rexpr); + } + + static Node * + transformAExprDistinct(ParseState *pstate, A_Expr *a) + { + Node *lexpr = a->lexpr; + Node *rexpr = a->rexpr; + + if (lexpr && IsA(lexpr, RowExpr) && + rexpr && IsA(rexpr, RowExpr)) + { + /* "row op row" */ + return make_row_distinct_op(pstate, a->name, + lexpr, rexpr); + } + else + { + /* Ordinary scalar operator */ + lexpr = transformExpr(pstate, lexpr); + rexpr = transformExpr(pstate, rexpr); + + return (Node *) make_distinct_op(pstate, + a->name, + lexpr, + rexpr); + } + } + + static Node * + transformAExprNullIf(ParseState *pstate, A_Expr *a) + { + Node *lexpr = transformExpr(pstate, a->lexpr); + Node *rexpr = transformExpr(pstate, a->rexpr); + Node *result; + + result = (Node *) make_op(pstate, + a->name, + lexpr, + rexpr); + if (((OpExpr *) result)->opresulttype != BOOLOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("NULLIF requires = operator to yield boolean"))); + + /* + * We rely on NullIfExpr and OpExpr being the same struct + */ + NodeSetTag(result, T_NullIfExpr); + + return result; + } + + static Node * + transformAExprOf(ParseState *pstate, A_Expr *a) + { + /* + * Checking an expression for match to type. Will result in a + * boolean constant node. + */ + ListCell *telem; + A_Const *n; + Oid ltype, + rtype; + bool matched = false; + Node *lexpr = transformExpr(pstate, a->lexpr); + + ltype = exprType(lexpr); + foreach(telem, (List *) a->rexpr) + { + rtype = LookupTypeName(lfirst(telem)); + matched = (rtype == ltype); + if (matched) + break; + } + + /* + * Expect two forms: equals or not equals. Flip the sense of the + * result for not equals. + */ + if (strcmp(strVal(linitial(a->name)), "!=") == 0) + matched = (!matched); + + n = makeNode(A_Const); + n->val.type = T_String; + n->val.val.str = (matched ? "t" : "f"); + n->typename = SystemTypeName("bool"); + + return transformExpr(pstate, (Node *) n); + } + + static Node * + transformFuncCall(ParseState *pstate, FuncCall *fn) + { + List *targs; + ListCell *args; + + /* + * Transform the list of arguments. We use a shallow list copy + * and then transform-in-place to avoid O(N^2) behavior from + * repeated lappend's. + * + * XXX: repeated lappend() would no longer result in O(n^2) + * behavior; worth reconsidering this design? + */ + targs = list_copy(fn->args); + foreach(args, targs) + { + lfirst(args) = transformExpr(pstate, + (Node *) lfirst(args)); + } + + return ParseFuncOrColumn(pstate, + fn->funcname, + targs, + fn->agg_star, + fn->agg_distinct, + false); + } + + static Node * + transformCaseExpr(ParseState *pstate, CaseExpr *c) + { + CaseExpr *newc; + Node *arg; + CaseTestExpr *placeholder; + List *newargs; + List *typeids; + ListCell *l; + Node *defresult; + Oid ptype; + + /* If we already transformed this node, do nothing */ + if (OidIsValid(c->casetype)) + return (Node *) c; + + newc = makeNode(CaseExpr); + + /* transform the test expression, if any */ + arg = transformExpr(pstate, (Node *) c->arg); + + /* generate placeholder for test expression */ + if (arg) + { + /* + * If test expression is an untyped literal, force it to text. + * We have to do something now because we won't be able to do + * this coercion on the placeholder. This is not as flexible + * as what was done in 7.4 and before, but it's good enough to + * handle the sort of silly coding commonly seen. + */ + if (exprType(arg) == UNKNOWNOID) + arg = coerce_to_common_type(pstate, arg, + TEXTOID, "CASE"); + placeholder = makeNode(CaseTestExpr); + placeholder->typeId = exprType(arg); + placeholder->typeMod = exprTypmod(arg); + } + else + placeholder = NULL; + + newc->arg = (Expr *) arg; + + /* transform the list of arguments */ + newargs = NIL; + typeids = NIL; + foreach(l, c->args) + { + CaseWhen *w = (CaseWhen *) lfirst(l); + CaseWhen *neww = makeNode(CaseWhen); + Node *warg; + + Assert(IsA(w, CaseWhen)); + + warg = (Node *) w->expr; + if (placeholder) + { + /* shorthand form was specified, so expand... */ + warg = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", + (Node *) placeholder, + warg); + } + neww->expr = (Expr *) transformExpr(pstate, warg); + + neww->expr = (Expr *) coerce_to_boolean(pstate, + (Node *) neww->expr, + "CASE/WHEN"); + + warg = (Node *) w->result; + neww->result = (Expr *) transformExpr(pstate, warg); + + newargs = lappend(newargs, neww); + typeids = lappend_oid(typeids, exprType((Node *) neww->result)); + } + + newc->args = newargs; + + /* transform the default clause */ + defresult = (Node *) c->defresult; + if (defresult == NULL) + { + A_Const *n = makeNode(A_Const); + + n->val.type = T_Null; + defresult = (Node *) n; + } + newc->defresult = (Expr *) transformExpr(pstate, defresult); + + /* + * Note: default result is considered the most significant type in + * determining preferred type. This is how the code worked before, + * but it seems a little bogus to me + * --- tgl + */ + typeids = lcons_oid(exprType((Node *) newc->defresult), typeids); + + ptype = select_common_type(typeids, "CASE"); + Assert(OidIsValid(ptype)); + newc->casetype = ptype; + + /* Convert default result clause, if necessary */ + newc->defresult = (Expr *) + coerce_to_common_type(pstate, + (Node *) newc->defresult, + ptype, + "CASE/ELSE"); + + /* Convert when-clause results, if necessary */ + foreach(l, newc->args) + { + CaseWhen *w = (CaseWhen *) lfirst(l); + + w->result = (Expr *) + coerce_to_common_type(pstate, + (Node *) w->result, + ptype, + "CASE/WHEN"); + } + + return (Node *) newc; + } + + static Node * + transformSubLink(ParseState *pstate, SubLink *sublink) + { + List *qtrees; + Query *qtree; + Node *result = (Node *) sublink; + + /* If we already transformed this node, do nothing */ + if (IsA(sublink->subselect, Query)) + return result; + + pstate->p_hasSubLinks = true; + qtrees = parse_sub_analyze(sublink->subselect, pstate); + if (list_length(qtrees) != 1) + elog(ERROR, "bad query in sub-select"); + qtree = (Query *) linitial(qtrees); + if (qtree->commandType != CMD_SELECT || + qtree->resultRelation != 0) + elog(ERROR, "bad query in sub-select"); + sublink->subselect = (Node *) qtree; + + if (sublink->subLinkType == EXISTS_SUBLINK) + { + /* + * EXISTS needs no lefthand or combining operator. These + * fields should be NIL already, but make sure. + */ + sublink->lefthand = NIL; + sublink->operName = NIL; + sublink->operOids = NIL; + sublink->useOr = FALSE; + } + else if (sublink->subLinkType == EXPR_SUBLINK || + sublink->subLinkType == ARRAY_SUBLINK) + { + ListCell *tlist_item = list_head(qtree->targetList); + + /* + * Make sure the subselect delivers a single column (ignoring + * resjunk targets). + */ + if (tlist_item == NULL || + ((TargetEntry *) lfirst(tlist_item))->resdom->resjunk) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subquery must return a column"))); + while ((tlist_item = lnext(tlist_item)) != NULL) + { + if (!((TargetEntry *) lfirst(tlist_item))->resdom->resjunk) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subquery must return only one column"))); + } + + /* + * EXPR and ARRAY need no lefthand or combining + * operator. These fields should be NIL already, but make + * sure. + */ + sublink->lefthand = NIL; + sublink->operName = NIL; + sublink->operOids = NIL; + sublink->useOr = FALSE; + } + else + { + /* ALL, ANY, or MULTIEXPR: generate operator list */ + List *left_list = sublink->lefthand; + List *right_list = qtree->targetList; + int row_length = list_length(left_list); + bool needNot = false; + List *op = sublink->operName; + char *opname = strVal(llast(op)); + ListCell *l; + ListCell *ll_item; + + /* transform lefthand expressions */ + foreach(l, left_list) + lfirst(l) = transformExpr(pstate, lfirst(l)); + + /* + * If the expression is "<> ALL" (with unqualified opname) + * then convert it to "NOT IN". This is a hack to improve + * efficiency of expressions output by pre-7.4 Postgres. + */ + if (sublink->subLinkType == ALL_SUBLINK && + list_length(op) == 1 && strcmp(opname, "<>") == 0) + { + sublink->subLinkType = ANY_SUBLINK; + opname = pstrdup("="); + op = list_make1(makeString(opname)); + sublink->operName = op; + needNot = true; + } + + /* Set useOr if op is "<>" (possibly qualified) */ + if (strcmp(opname, "<>") == 0) + sublink->useOr = TRUE; + else + sublink->useOr = FALSE; + + /* Combining operators other than =/<> is dubious... */ + if (row_length != 1 && + strcmp(opname, "=") != 0 && + strcmp(opname, "<>") != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("row comparison cannot use operator %s", + opname))); + + /* + * To build the list of combining operator OIDs, we must scan + * subquery's targetlist to find values that will be matched + * against lefthand values. We need to ignore resjunk + * targets, so doing the outer iteration over right_list is + * easier than doing it over left_list. + */ + sublink->operOids = NIL; + + ll_item = list_head(left_list); + foreach(l, right_list) + { + TargetEntry *tent = (TargetEntry *) lfirst(l); + Node *lexpr; + Operator optup; + Form_pg_operator opform; + + if (tent->resdom->resjunk) + continue; + + if (ll_item == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subquery has too many columns"))); + lexpr = lfirst(ll_item); + ll_item = lnext(ll_item); + + /* + * It's OK to use oper() not compatible_oper() here, + * because make_subplan() will insert type coercion calls + * if needed. + */ + optup = oper(op, + exprType(lexpr), + exprType((Node *) tent->expr), + false); + opform = (Form_pg_operator) GETSTRUCT(optup); + + if (opform->oprresult != BOOLOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("operator %s must return type boolean, not type %s", + opname, + format_type_be(opform->oprresult)), + errhint("The operator of a quantified predicate subquery must return type boolean."))); + + if (get_func_retset(opform->oprcode)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("operator %s must not return a set", + opname), + errhint("The operator of a quantified predicate subquery must return type boolean."))); + + sublink->operOids = lappend_oid(sublink->operOids, + oprid(optup)); + + ReleaseSysCache(optup); + } + if (ll_item != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subquery has too few columns"))); + + if (needNot) + { + result = coerce_to_boolean(pstate, result, "NOT"); + result = (Node *) makeBoolExpr(NOT_EXPR, + list_make1(result)); + } + } + + return result; + } + + static Node * + transformArrayExpr(ParseState *pstate, ArrayExpr *a) + { + ArrayExpr *newa = makeNode(ArrayExpr); + List *newelems = NIL; + List *newcoercedelems = NIL; + List *typeids = NIL; + ListCell *element; + Oid array_type; + Oid element_type; + + /* Transform the element expressions */ + foreach(element, a->elements) + { + Node *e = (Node *) lfirst(element); + Node *newe; + + newe = transformExpr(pstate, e); + newelems = lappend(newelems, newe); + typeids = lappend_oid(typeids, exprType(newe)); + } + + /* Select a common type for the elements */ + element_type = select_common_type(typeids, "ARRAY"); + + /* Coerce arguments to common type if necessary */ + foreach(element, newelems) + { + Node *e = (Node *) lfirst(element); + Node *newe; + + newe = coerce_to_common_type(pstate, e, + element_type, + "ARRAY"); + newcoercedelems = lappend(newcoercedelems, newe); + } + + /* Do we have an array type to use? */ + array_type = get_array_type(element_type); + if (array_type != InvalidOid) + { + /* Elements are presumably of scalar type */ + newa->multidims = false; + } + else + { + /* Must be nested array expressions */ + newa->multidims = true; + + array_type = element_type; + element_type = get_element_type(array_type); + if (!OidIsValid(element_type)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find array type for data type %s", + format_type_be(array_type)))); + } + + newa->array_typeid = array_type; + newa->element_typeid = element_type; + newa->elements = newcoercedelems; + + return (Node *) newa; + } + + static Node * + transformRowExpr(ParseState *pstate, RowExpr *r) + { + RowExpr *newr = makeNode(RowExpr); + List *newargs = NIL; + ListCell *arg; + + /* Transform the field expressions */ + foreach(arg, r->args) + { + Node *e = (Node *) lfirst(arg); + Node *newe; + + newe = transformExpr(pstate, e); + newargs = lappend(newargs, newe); + } + newr->args = newargs; + + /* Barring later casting, we consider the type RECORD */ + newr->row_typeid = RECORDOID; + newr->row_format = COERCE_IMPLICIT_CAST; + + return (Node *) newr; + } + + static Node * + transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c) + { + CoalesceExpr *newc = makeNode(CoalesceExpr); + List *newargs = NIL; + List *newcoercedargs = NIL; + List *typeids = NIL; + ListCell *args; + + foreach(args, c->args) + { + Node *e = (Node *) lfirst(args); + Node *newe; + + newe = transformExpr(pstate, e); + newargs = lappend(newargs, newe); + typeids = lappend_oid(typeids, exprType(newe)); + } + + newc->coalescetype = select_common_type(typeids, "COALESCE"); + + /* Convert arguments if necessary */ + foreach(args, newargs) + { + Node *e = (Node *) lfirst(args); + Node *newe; + + newe = coerce_to_common_type(pstate, e, + newc->coalescetype, + "COALESCE"); + newcoercedargs = lappend(newcoercedargs, newe); + } + + newc->args = newcoercedargs; + return (Node *) newc; + } + + static Node * + transformBooleanTest(ParseState *pstate, BooleanTest *b) + { + const char *clausename; + + switch (b->booltesttype) + { + case IS_TRUE: + clausename = "IS TRUE"; + break; + case IS_NOT_TRUE: + clausename = "IS NOT TRUE"; + break; + case IS_FALSE: + clausename = "IS FALSE"; + break; + case IS_NOT_FALSE: + clausename = "IS NOT FALSE"; + break; + case IS_UNKNOWN: + clausename = "IS UNKNOWN"; + break; + case IS_NOT_UNKNOWN: + clausename = "IS NOT UNKNOWN"; + break; + default: + elog(ERROR, "unrecognized booltesttype: %d", + (int) b->booltesttype); + clausename = NULL; /* keep compiler quiet */ + } + + b->arg = (Expr *) transformExpr(pstate, (Node *) b->arg); + + b->arg = (Expr *) coerce_to_boolean(pstate, + (Node *) b->arg, + clausename); + + return (Node *) b; + } + /* * Construct a whole-row reference to represent the notation "relation.*". *
---------------------------(end of broadcast)--------------------------- TIP 3: if posting/reading through Usenet, please send an appropriate subscribe-nomail command to [EMAIL PROTECTED] so that your message can get through to the mailing list cleanly