001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
005: *
006: *
007: * The contents of this file are subject to the terms of either the GNU
008: * General Public License Version 2 only ("GPL") or the Common Development
009: * and Distribution License("CDDL") (collectively, the "License"). You
010: * may not use this file except in compliance with the License. You can obtain
011: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
012: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
013: * language governing permissions and limitations under the License.
014: *
015: * When distributing the software, include this License Header Notice in each
016: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
017: * Sun designates this particular file as subject to the "Classpath" exception
018: * as provided by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the License
020: * Header, with the fields enclosed by brackets [] replaced by your own
021: * identifying information: "Portions Copyrighted [year]
022: * [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * If you wish your version of this file to be governed by only the CDDL or
027: * only the GPL Version 2, indicate your decision by adding "[Contributor]
028: * elects to include this software in this distribution under the [CDDL or GPL
029: * Version 2] license." If you don't indicate a single choice of license, a
030: * recipient has the option to distribute your version of this file under
031: * either the CDDL, the GPL Version 2 or to extend the choice of license to
032: * its licensees as provided above. However, if you add GPL Version 2 code
033: * and therefore, elected the GPL Version 2 license, then the option applies
034: * only if the new code is made subject to such option by the copyright
035: * holder.
036: */
037: package oracle.toplink.essentials.internal.parsing;
038:
039: import java.util.ArrayList;
040: import java.util.Iterator;
041: import java.util.List;
042:
043: import oracle.toplink.essentials.queryframework.*;
044: import oracle.toplink.essentials.expressions.Expression;
045: import oracle.toplink.essentials.expressions.ExpressionBuilder;
046: import oracle.toplink.essentials.descriptors.ClassDescriptor;
047:
048: /**
049: * INTERNAL
050: * <p><b>Purpose</b>: Represent a SELECT
051: * <p><b>Responsibilities</b>:<ul>
052: * <li> Hold the distinct status
053: * <li> Modify a query based on the contents
054: *
055: * The SELECT statement determines the return type of an EJBQL query.
056: * The SELECT may also determine the distinct state of a query
057: *
058: * A SELECT can be one of the following:
059: * 1. SELECT OBJECT(someObject)... This query will return a collection of objects
060: * 2. SELECT anObject.anAttribute ... This will return a collection of anAttribute
061: * 3. SELECT <aggregateFunction> ... This will return a single value
062: * The allowable aggregateFunctions are: AVG, COUNT, MAX, MIN, SUM
063: * SELECT AVG(emp.salary)... Returns the average of all the employees salaries
064: * SELECT COUNT(emp)... Returns a count of the employees
065: * SELECT COUNT(emp.firstName)... Returns a count of the employee's firstNames
066: * SELECT MAX(emp.salary)... Returns the maximum employee salary
067: * SELECT MIN(emp.salary)... Returns the minimum employee salary
068: * SELECT SUM(emp.salary)... Returns the sum of all the employees salaries
069: *
070: * </ul>
071: * @author Jon Driscoll
072: * @since TopLink 5.0
073: */
074: public class SelectNode extends QueryNode {
075:
076: private List selectExpressions = new ArrayList();
077: private boolean distinct = false;
078:
079: public SelectNode() {
080: }
081:
082: /**
083: * INTERNAL
084: * Add an Order By Item to this node
085: */
086: private void addSelectExpression(Object theNode) {
087: selectExpressions.add(theNode);
088: }
089:
090: /** */
091: public List getSelectExpressions() {
092: return selectExpressions;
093: }
094:
095: /** */
096: public void setSelectExpressions(List exprs) {
097: selectExpressions = exprs;
098: }
099:
100: /** */
101: public boolean usesDistinct() {
102: return distinct;
103: }
104:
105: /** */
106: public void setDistinct(boolean distinct) {
107: this .distinct = distinct;
108: }
109:
110: /**
111: * INTERNAL
112: * Returns a DatabaseQuery instance representing the owning
113: * ParseTree. This implementation returns a ReadAllQuery for simple SELECT
114: * queries and a ReportQuery otherwise.
115: */
116: public DatabaseQuery createDatabaseQuery(ParseTreeContext context) {
117: ObjectLevelReadQuery query;
118: /* Disable the optimization for the time being.
119: The optimization exposes a problem with the ORDER BY clause in the
120: generated SQL (see glassfish issue 2084).
121: There is also a performance regression that needs to be
122: investigated (see glassfish issue 2171).
123: query = isReadAllQuery(context) ?
124: new ReadAllQuery() : new ReportQuery();
125: */
126: query = new ReportQuery();
127: query.dontUseDistinct();//gf bug 1395- prevents using distinct unless user specified
128: return query;
129: }
130:
131: /**
132: * INTERNAL
133: * Returns true if the SELECT clause consists of a single expression
134: * returning the base identification variable of the query and if the base
135: * variable is defined as a range variable w/o FETCH JOINs.
136: */
137: private boolean isReadAllQuery(ParseTreeContext context) {
138: if (!isSingleSelectExpression()) {
139: // multiple expressions in the select clause => ReportQuery
140: return false;
141: }
142:
143: Node node = getFirstSelectExpressionNode();
144: if (!node.isVariableNode()) {
145: // Does not select an identification variable (e.g. projection or
146: // aggregate function) => ReportQuery
147: return false;
148: }
149: String variable = ((VariableNode) node)
150: .getCanonicalVariableName();
151:
152: // Note, the base variable in ParseTreeContext is not yet set =>
153: // calculate it
154: String baseVariable = getParseTree().getFromNode()
155: .getFirstVariable();
156: if (!context.isRangeVariable(baseVariable)
157: || (context.getFetchJoins(baseVariable) != null)) {
158: // Query's base variable is not a range variable or the base
159: // variable has FETCH JOINs => ReportQuery
160: return false;
161: }
162:
163: // Use ReadAllQuery if the variable of the SELECT clause expression is
164: // the base variable
165: return baseVariable.equals(variable);
166: }
167:
168: /**
169: * INTERNAL
170: * Apply this node to the passed query
171: */
172: public void applyToQuery(DatabaseQuery theQuery,
173: GenerationContext context) {
174: ObjectLevelReadQuery readQuery = (ObjectLevelReadQuery) theQuery;
175: if (selectExpressions.isEmpty()) {
176: return;
177: }
178:
179: //set the distinct state
180: //BUG 3168673: Don't set distinct state if we're using Count
181: if (!(isSingleSelectExpression() && getFirstSelectExpressionNode()
182: .isCountNode())) {
183: // Set the distinct state for the query
184: if (usesDistinct()) {
185: getParseTree().setDistinctState(
186: ObjectLevelReadQuery.USE_DISTINCT);
187: readQuery
188: .setDistinctState(ObjectLevelReadQuery.USE_DISTINCT);
189: }
190: }
191:
192: if (readQuery instanceof ReportQuery) {
193: ReportQuery reportQuery = (ReportQuery) readQuery;
194: reportQuery.returnWithoutReportQueryResult();
195: if (isSingleSelectExpression()
196: && !getFirstSelectExpressionNode()
197: .isConstructorNode()) {
198: reportQuery.returnSingleAttribute();
199: }
200: }
201: SelectGenerationContext selectContext = (SelectGenerationContext) context;
202: for (Iterator i = selectExpressions.iterator(); i.hasNext();) {
203: Node node = (Node) i.next();
204: if (selectingRelationshipField(node, context)) {
205: selectContext.useOuterJoins();
206: }
207: node.applyToQuery(readQuery, context);
208: selectContext.dontUseOuterJoins();
209: }
210:
211: //indicate on the query if "return null if primary key null"
212: //This means we want nulls returned if we expect an outer join
213: if (this .hasOneToOneSelected(context)) {
214: readQuery.setProperty("return null if primary key is null",
215: Boolean.TRUE);
216: } else {
217: readQuery
218: .removeProperty("return null if primary key is null");
219: }
220:
221: }
222:
223: /**
224: * INTERNAL
225: **/
226: public boolean hasOneToOneSelected(GenerationContext context) {
227: // Iterate the select expression and return true if one of it has a
228: // oneToOne selected.
229: for (Iterator i = selectExpressions.iterator(); i.hasNext();) {
230: Node node = (Node) i.next();
231: if (hasOneToOneSelected(node, context)) {
232: return true;
233: }
234: }
235: return false;
236: }
237:
238: /**
239: * INTERNAL
240: * Answer true if there is a one-to-one relationship selected.
241: * This includes a chain of relationships.
242: * True: SELECT employee.address FROM ..... //Simple 1:1
243: * True: SELECT a.b.c.d FROM ..... //where a->b, b->c and c->d are all 1:1.
244: * False: SELECT OBJECT(employee) FROM ..... //simple SELECT
245: * False: SELECT phoneNumber.areaCode FROM ..... //direct-to-field
246: **/
247: private boolean hasOneToOneSelected(Node node,
248: GenerationContext context) {
249: //BUG 3240484: Not SELECTing 1:1 if it's in a COUNT
250: if (node.isCountNode()) {
251: return false;
252: }
253:
254: if (node.isAggregateNode()) {
255: // delegate to aggregate expression
256: return hasOneToOneSelected(node.getLeft(), context);
257: }
258:
259: if (node.isVariableNode()) {
260: return !nodeRefersToObject(node, context);
261: }
262:
263: if (node.isConstructorNode()) {
264: List args = ((ConstructorNode) node).getConstructorItems();
265: for (Iterator i = args.iterator(); i.hasNext();) {
266: Node arg = (Node) i.next();
267: if (hasOneToOneSelected(arg, context)) {
268: return true;
269: }
270: }
271: return false;
272: }
273:
274: // check whether it is a direct-to-field mapping
275: return !selectingDirectToField(node, context);
276: }
277:
278: /**
279: * Verify that the selected alias is a valid alias. If it's not valid,
280: * an Exception will be thrown, likely EJBQLException.aliasResolutionException.
281: *
282: * Valid: SELECT OBJECT(emp) FROM Employee emp WHERE ...
283: * Invalid: SELECT OBJECT(badAlias) FROM Employee emp WHERE ...
284: */
285: public void verifySelectedAlias(GenerationContext context) {
286: for (Iterator i = selectExpressions.iterator(); i.hasNext();) {
287: Node node = (Node) i.next();
288: //if the node is a DotNode, there is no selected alias
289: if (node.isDotNode()) {
290: return;
291: }
292: node.resolveClass(context);
293: }
294: }
295:
296: /**
297: * Answer true if the variable name given as argument is SELECTed.
298: *
299: * True: "SELECT OBJECT(emp) ...." & variableName = "emp"
300: * False: "SELECT OBJECT(somethingElse) ..." & variableName = "emp"
301: */
302: public boolean isSelected(String variableName) {
303: for (Iterator i = selectExpressions.iterator(); i.hasNext();) {
304: Node node = (Node) i.next();
305: //Make sure we've SELECted a VariableNode
306: if (node.isVariableNode()
307: && ((VariableNode) node).getCanonicalVariableName()
308: .equals(variableName)) {
309: return true;
310: }
311: }
312: return false;
313: }
314:
315: public boolean isSelectNode() {
316: return true;
317: }
318:
319: /**
320: * INTERNAL
321: * Check the select expression nodes for a path expression starting with a
322: * unqualified field access and if so, replace it by a qualified field
323: * access.
324: */
325: public Node qualifyAttributeAccess(ParseTreeContext context) {
326: for (int i = 0; i < selectExpressions.size(); i++) {
327: Node item = (Node) selectExpressions.get(i);
328: selectExpressions.set(i, item
329: .qualifyAttributeAccess(context));
330: }
331: return this ;
332: }
333:
334: /**
335: * INTERNAL
336: * Validate node.
337: */
338: public void validate(ParseTreeContext context) {
339: for (Iterator i = selectExpressions.iterator(); i.hasNext();) {
340: Node item = (Node) i.next();
341: item.validate(context);
342: }
343: }
344:
345: /**
346: * resolveClass: Answer the class associated with my left node.
347: */
348: public Class resolveClass(GenerationContext context) {
349: return getReferenceClass(context);
350: }
351:
352: /**
353: * INTERNAL
354: * Return a TopLink expression generated using the left node
355: */
356: public Expression generateExpression(GenerationContext context) {
357: return null;
358: }
359:
360: /**
361: * Compute the Reference class for this query
362: * @param context
363: * @return the class this query is querying for
364: */
365: public Class getReferenceClass(GenerationContext context) {
366: return getClassOfFirstVariable(context);
367: }
368:
369: /** */
370: private Class getClassOfFirstVariable(GenerationContext context) {
371: Class clazz = null;
372: String variable = getParseTree().getFromNode()
373: .getFirstVariable();
374: ParseTreeContext parseTreeContext = context
375: .getParseTreeContext();
376: if (parseTreeContext.isRangeVariable(variable)) {
377: String schema = parseTreeContext
378: .schemaForVariable(variable);
379: // variables is defines in a range variable declaration, so there
380: // is a schema name for this variable
381: clazz = parseTreeContext
382: .classForSchemaName(schema, context);
383: } else {
384: // variable is defined in a JOIN clause, so there is a a defining
385: // node for the variable
386: Node path = parseTreeContext.pathForVariable(variable);
387: clazz = path.resolveClass(context);
388: }
389: return clazz;
390: }
391:
392: /**
393: * INTERNAL
394: * Answer true if a variable in the IN clause is SELECTed
395: */
396: public boolean isVariableInINClauseSelected(
397: GenerationContext context) {
398: for (Iterator i = selectExpressions.iterator(); i.hasNext();) {
399: Node node = (Node) i.next();
400:
401: if (node.isVariableNode()) {
402: String variableNameForLeft = ((VariableNode) node)
403: .getCanonicalVariableName();
404: if (!context.getParseTreeContext().isRangeVariable(
405: variableNameForLeft)) {
406: return true;
407: }
408: }
409: }
410: return false;
411: }
412:
413: /**
414: * INTERNAL
415: * Answer true if this node refers to an object described later in the EJBQL
416: * True: SELECT p FROM Project p
417: * False: SELECT p.id FROM Project p
418: **/
419: public boolean nodeRefersToObject(Node node,
420: GenerationContext context) {
421: if (!node.isVariableNode()) {
422: return false;
423: }
424: String name = ((VariableNode) node).getCanonicalVariableName();
425: String alias = context.getParseTreeContext().schemaForVariable(
426: name);
427: if (alias != null) {
428: ClassDescriptor descriptor = context.getSession()
429: .getDescriptorForAlias(alias);
430: if (descriptor != null) {
431: return true;
432: }
433: }
434: return false;
435: }
436:
437: /**
438: * INTERNAL
439: */
440: private boolean selectingRelationshipField(Node node,
441: GenerationContext context) {
442: if ((node == null) || !node.isDotNode()) {
443: return false;
444: }
445: TypeHelper typeHelper = context.getParseTreeContext()
446: .getTypeHelper();
447: Node path = node.getLeft();
448: AttributeNode attribute = (AttributeNode) node.getRight();
449: return typeHelper.isRelationship(path.getType(), attribute
450: .getAttributeName());
451: }
452:
453: /**
454: * INTERNAL
455: * Answer true if the SELECT ends in a direct-to-field.
456: * true: SELECT phone.areaCode
457: * false: SELECT employee.address
458: */
459: private boolean selectingDirectToField(Node node,
460: GenerationContext context) {
461:
462: if ((node == null) || !node.isDotNode()) {
463: return false;
464: }
465: return ((DotNode) node).endsWithDirectToField(context);
466: }
467:
468: /**
469: * Returns the first select expression node.
470: */
471: private Node getFirstSelectExpressionNode() {
472: return selectExpressions.size() > 0 ? (Node) selectExpressions
473: .get(0) : null;
474: }
475:
476: /** */
477: private boolean isSingleSelectExpression() {
478: return selectExpressions.size() == 1;
479: }
480:
481: }
|