parse_clause.c
来自「PostgreSQL7.4.6 for Linux」· C语言 代码 · 共 1,591 行 · 第 1/3 页
C
1,591 行
* Handle two special cases as mandated by the SQL92 spec: * * 1. Bare ColumnName (no qualifier or subscripts) * For a bare identifier, we search for a matching column name * in the existing target list. Multiple matches are an error * unless they refer to identical values; for example, * we allow SELECT a, a FROM table ORDER BY a * but not SELECT a AS b, b FROM table ORDER BY b * If no match is found, we fall through and treat the identifier * as an expression. * For GROUP BY, it is incorrect to match the grouping item against * targetlist entries: according to SQL92, an identifier in GROUP BY * is a reference to a column name exposed by FROM, not to a target * list column. However, many implementations (including pre-7.0 * PostgreSQL) accept this anyway. So for GROUP BY, we look first * to see if the identifier matches any FROM column name, and only * try for a targetlist name if it doesn't. This ensures that we * adhere to the spec in the case where the name could be both. * DISTINCT ON isn't in the standard, so we can do what we like there; * we choose to make it work like ORDER BY, on the rather flimsy * grounds that ordinary DISTINCT works on targetlist entries. * * 2. IntegerConstant * This means to use the n'th item in the existing target list. * Note that it would make no sense to order/group/distinct by an * actual constant, so this does not create a conflict with our * extension to order/group by an expression. * GROUP BY column-number is not allowed by SQL92, but since * the standard has no other behavior defined for this syntax, * we may as well accept this common extension. * * Note that pre-existing resjunk targets must not be used in either case, * since the user didn't write them in his SELECT list. * * If neither special case applies, fall through to treat the item as * an expression. *---------- */ if (IsA(node, ColumnRef) && length(((ColumnRef *) node)->fields) == 1 && ((ColumnRef *) node)->indirection == NIL) { char *name = strVal(lfirst(((ColumnRef *) node)->fields)); if (clause == GROUP_CLAUSE) { /* * In GROUP BY, we must prefer a match against a FROM-clause * column to one against the targetlist. Look to see if there * is a matching column. If so, fall through to let * transformExpr() do the rest. NOTE: if name could refer * ambiguously to more than one column name exposed by FROM, * colNameToVar will ereport(ERROR). That's just what we want * here. * * Small tweak for 7.4.3: ignore matches in upper query levels. * This effectively changes the search order for bare names to * (1) local FROM variables, (2) local targetlist aliases, * (3) outer FROM variables, whereas before it was (1) (3) (2). * SQL92 and SQL99 do not allow GROUPing BY an outer reference, * so this breaks no cases that are legal per spec, and it * seems a more self-consistent behavior. */ if (colNameToVar(pstate, name, true) != NULL) name = NULL; } if (name != NULL) { foreach(tl, tlist) { TargetEntry *tle = (TargetEntry *) lfirst(tl); Resdom *resnode = tle->resdom; if (!resnode->resjunk && strcmp(resnode->resname, name) == 0) { if (target_result != NULL) { if (!equal(target_result->expr, tle->expr)) ereport(ERROR, (errcode(ERRCODE_AMBIGUOUS_COLUMN), /* translator: first %s is name of a SQL construct, eg ORDER BY */ errmsg("%s \"%s\" is ambiguous", clauseText[clause], name))); } else target_result = tle; /* Stay in loop to check for ambiguity */ } } if (target_result != NULL) return target_result; /* return the first match */ } } if (IsA(node, A_Const)) { Value *val = &((A_Const *) node)->val; int targetlist_pos = 0; int target_pos; if (!IsA(val, Integer)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), /* translator: %s is name of a SQL construct, eg ORDER BY */ errmsg("non-integer constant in %s", clauseText[clause]))); target_pos = intVal(val); foreach(tl, tlist) { TargetEntry *tle = (TargetEntry *) lfirst(tl); Resdom *resnode = tle->resdom; if (!resnode->resjunk) { if (++targetlist_pos == target_pos) return tle; /* return the unique match */ } } ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), /* translator: %s is name of a SQL construct, eg ORDER BY */ errmsg("%s position %d is not in select list", clauseText[clause], target_pos))); } /* * Otherwise, we have an expression (this is a Postgres extension not * found in SQL92). Convert the untransformed node to a transformed * expression, and search for a match in the tlist. NOTE: it doesn't * really matter whether there is more than one match. Also, we are * willing to match a resjunk target here, though the above cases must * ignore resjunk targets. */ expr = transformExpr(pstate, node); foreach(tl, tlist) { TargetEntry *tle = (TargetEntry *) lfirst(tl); if (equal(expr, tle->expr)) return tle; } /* * If no matches, construct a new target entry which is appended to * the end of the target list. This target is given resjunk = TRUE so * that it will not be projected into the final tuple. */ target_result = transformTargetEntry(pstate, node, expr, NULL, true); lappend(tlist, target_result); return target_result;}/* * transformGroupClause - * transform a GROUP BY clause */List *transformGroupClause(ParseState *pstate, List *grouplist, List *targetlist, List *sortClause){ List *glist = NIL, *gl; foreach(gl, grouplist) { TargetEntry *tle; Oid restype; Oid ordering_op; GroupClause *grpcl; tle = findTargetlistEntry(pstate, lfirst(gl), targetlist, GROUP_CLAUSE); /* avoid making duplicate grouplist entries */ if (targetIsInSortList(tle, glist)) continue; /* if tlist item is an UNKNOWN literal, change it to TEXT */ restype = tle->resdom->restype; if (restype == UNKNOWNOID) { tle->expr = (Expr *) coerce_type(pstate, (Node *) tle->expr, restype, TEXTOID, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST); restype = tle->resdom->restype = TEXTOID; tle->resdom->restypmod = -1; } /* * If the GROUP BY clause matches the ORDER BY clause, we want to * adopt the ordering operators from the latter rather than using * the default ops. This allows "GROUP BY foo ORDER BY foo DESC" * to be done with only one sort step. Note we are assuming that * any user-supplied ordering operator will bring equal values * together, which is all that GROUP BY needs. */ if (sortClause && ((SortClause *) lfirst(sortClause))->tleSortGroupRef == tle->resdom->ressortgroupref) { ordering_op = ((SortClause *) lfirst(sortClause))->sortop; sortClause = lnext(sortClause); } else { ordering_op = ordering_oper_opid(restype); sortClause = NIL; /* disregard ORDER BY once match fails */ } grpcl = makeNode(GroupClause); grpcl->tleSortGroupRef = assignSortGroupRef(tle, targetlist); grpcl->sortop = ordering_op; glist = lappend(glist, grpcl); } return glist;}/* * transformSortClause - * transform an ORDER BY clause */List *transformSortClause(ParseState *pstate, List *orderlist, List *targetlist, bool resolveUnknown){ List *sortlist = NIL; List *olitem; foreach(olitem, orderlist) { SortBy *sortby = lfirst(olitem); TargetEntry *tle; tle = findTargetlistEntry(pstate, sortby->node, targetlist, ORDER_CLAUSE); sortlist = addTargetToSortList(pstate, tle, sortlist, targetlist, sortby->sortby_kind, sortby->useOp, resolveUnknown); } return sortlist;}/* * transformDistinctClause - * transform a DISTINCT or DISTINCT ON clause * * Since we may need to add items to the query's sortClause list, that list * is passed by reference. We might also need to add items to the query's * targetlist, but we assume that cannot be empty initially, so we can * lappend to it even though the pointer is passed by value. */List *transformDistinctClause(ParseState *pstate, List *distinctlist, List *targetlist, List **sortClause){ List *result = NIL; List *slitem; List *dlitem; /* No work if there was no DISTINCT clause */ if (distinctlist == NIL) return NIL; if (lfirst(distinctlist) == NIL) { /* We had SELECT DISTINCT */ /* * All non-resjunk elements from target list that are not already * in the sort list should be added to it. (We don't really care * what order the DISTINCT fields are checked in, so we can leave * the user's ORDER BY spec alone, and just add additional sort * keys to it to ensure that all targetlist items get sorted.) */ *sortClause = addAllTargetsToSortList(pstate, *sortClause, targetlist, true); /* * Now, DISTINCT list consists of all non-resjunk sortlist items. * Actually, all the sortlist items had better be non-resjunk! * Otherwise, user wrote SELECT DISTINCT with an ORDER BY item * that does not appear anywhere in the SELECT targetlist, and we * can't implement that with only one sorting pass... */ foreach(slitem, *sortClause) { SortClause *scl = (SortClause *) lfirst(slitem); TargetEntry *tle = get_sortgroupclause_tle(scl, targetlist); if (tle->resdom->resjunk) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("for SELECT DISTINCT, ORDER BY expressions must appear in select list"))); else result = lappend(result, copyObject(scl)); } } else { /* We had SELECT DISTINCT ON (expr, ...) */ /* * If the user writes both DISTINCT ON and ORDER BY, then the two * expression lists must match (until one or the other runs out). * Otherwise the ORDER BY requires a different sort order than the * DISTINCT does, and we can't implement that with only one sort * pass (and if we do two passes, the results will be rather * unpredictable). However, it's OK to have more DISTINCT ON * expressions than ORDER BY expressions; we can just add the * extra DISTINCT values to the sort list, much as we did above * for ordinary DISTINCT fields. * * Actually, it'd be OK for the common prefixes of the two lists to * match in any order, but implementing that check seems like more * trouble than it's worth. */ List *nextsortlist = *sortClause; foreach(dlitem, distinctlist) { TargetEntry *tle; tle = findTargetlistEntry(pstate, lfirst(dlitem), targetlist, DISTINCT_ON_CLAUSE); if (nextsortlist != NIL) { SortClause *scl = (SortClause *) lfirst(nextsortlist); if (tle->resdom->ressortgroupref != scl->tleSortGroupRef) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("SELECT DISTINCT ON expressions must match initial ORDER BY expressions"))); result = lappend(result, copyObject(scl)); nextsortlist = lnext(nextsortlist); } else { *sortClause = addTargetToSortList(pstate, tle, *sortClause, targetlist, SORTBY_ASC, NIL, true); /* * Probably, the tle should always have been added at the * end of the sort list ... but search to be safe. */ foreach(slitem, *sortClause) { SortClause *scl = (SortClause *) lfirst(slitem); if (tle->resdom->ressortgroupref == scl->tleSortGroupRef) { result = lappend(result, copyObject(scl)); break; } } if (slitem == NIL) /* should not happen */ elog(ERROR, "failed to add DISTINCT ON clause to target list"); } } } return result;}/* * addAllTargetsToSortList * Make sure all non-resjunk targets in the targetlist are in the * ORDER BY list, adding the not-yet-sorted ones to the end of the list. * This is typically used to help implement SELECT DISTINCT. * * See addTargetToSortList for info about pstate and resolveUnknown inputs. * * Returns the updated ORDER BY list. */List *addAllTargetsToSortList(ParseState *pstate, List *sortlist, List *targetlist, bool resolveUnknown){ List *i; foreach(i, targetlist) { TargetEntry *tle = (TargetEntry *) lfirst(i); if (!tle->resdom->resjunk) sortlist = addTargetToSortList(pstate, tle, sortlist, targetlist, SORTBY_ASC, NIL, resolveUnknown); } return sortlist;}/* * addTargetToSortList * If the given targetlist entry isn't already in the ORDER BY list, * add it to the end of the list, using the sortop with given name * or the default sort operator if opname == NIL. * * If resolveUnknown is TRUE, convert TLEs of type UNKNOWN to TEXT. If not, * do nothing (which implies the search for a sort operator will fail). * pstate should be provided if resolveUnknown is TRUE, but can be NULL * otherwise. * * Returns the updated ORDER BY list. */List *addTargetToSortList(ParseState *pstate, TargetEntry *tle, List *sortlist, List *targetlist, int sortby_kind, List *sortby_opname, bool resolveUnknown){ /* avoid making duplicate sortlist entries */ if (!targetIsInSortList(tle, sortlist)) { SortClause *sortcl = makeNode(SortClause); Oid restype = tle->resdom->restype; /* if tlist item is an UNKNOWN literal, change it to TEXT */ if (restype == UNKNOWNOID && resolveUnknown) { tle->expr = (Expr *) coerce_type(pstate, (Node *) tle->expr, restype, TEXTOID, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST); restype = tle->resdom->restype = TEXTOID; tle->resdom->restypmod = -1; } sortcl->tleSortGroupRef = assignSortGroupRef(tle, targetlist); switch (sortby_kind) { case SORTBY_ASC: sortcl->sortop = ordering_oper_opid(restype); break; case SORTBY_DESC: sortcl->sortop = reverse_ordering_oper_opid(restype); break; case SORTBY_USING: Assert(sortby_opname != NIL); sortcl->sortop = compatible_oper_opid(sortby_opname, restype, restype, false); break; default: elog(ERROR, "unrecognized sortby_kind: %d", sortby_kind); break; } sortlist = lappend(sortlist, sortcl); } return sortlist;}/* * assignSortGroupRef * Assign the targetentry an unused ressortgroupref, if it doesn't * already have one. Return the assigned or pre-existing refnumber. * * 'tlist' is the targetlist containing (or to contain) the given targetentry. */IndexassignSortGroupRef(TargetEntry *tle, List *tlist){ Index maxRef; List *l; if (tle->resdom->ressortgroupref) /* already has one? */ return tle->resdom->ressortgroupref; /* easiest way to pick an unused refnumber: max used + 1 */ maxRef = 0; foreach(l, tlist) { Index ref = ((TargetEntry *) lfirst(l))->resdom->ressortgroupref; if (ref > maxRef) maxRef = ref; } tle->resdom->ressortgroupref = maxRef + 1; return tle->resdom->ressortgroupref;}/* * targetIsInSortList * Is the given target item already in the sortlist? * * Works for both SortClause and GroupClause lists. Note that the main * reason we need this routine (and not just a quick test for nonzeroness * of ressortgroupref) is that a TLE might be in only one of the lists. */booltargetIsInSortList(TargetEntry *tle, List *sortList){ Index ref = tle->resdom->ressortgroupref; List *i; /* no need to scan list if tle has no marker */ if (ref == 0) return false; foreach(i, sortList) { SortClause *scl = (SortClause *) lfirst(i); if (scl->tleSortGroupRef == ref) return true; } return false;}
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?