001: package org.apache.ojb.broker.accesslayer.sql;
002:
003: /* Copyright 2002-2005 The Apache Software Foundation
004: *
005: * Licensed under the Apache License, Version 2.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: import java.util.ArrayList;
019: import java.util.Iterator;
020: import java.util.List;
021: import java.util.Map;
022: import java.util.Set;
023: import java.lang.ref.WeakReference;
024:
025: import org.apache.commons.collections.set.ListOrderedSet;
026: import org.apache.ojb.broker.metadata.ClassDescriptor;
027: import org.apache.ojb.broker.metadata.DescriptorRepository;
028: import org.apache.ojb.broker.metadata.FieldDescriptor;
029: import org.apache.ojb.broker.metadata.JdbcType;
030: import org.apache.ojb.broker.platforms.Platform;
031: import org.apache.ojb.broker.query.Criteria;
032: import org.apache.ojb.broker.query.Query;
033: import org.apache.ojb.broker.query.ReportQuery;
034: import org.apache.ojb.broker.query.ReportQueryByCriteria;
035: import org.apache.ojb.broker.util.SqlHelper;
036: import org.apache.ojb.broker.util.logging.Logger;
037:
038: /**
039: * Model a SELECT Statement
040: *
041: * @author <a href="mailto:jbraeuchi@hotmail.com">Jakob Braeuchi</a>
042: * @version $Id: SqlSelectStatement.java,v 1.22.2.8 2005/12/22 18:25:51 brj Exp $
043: */
044: public class SqlSelectStatement extends SqlQueryStatement implements
045: SelectStatement {
046: private WeakReference fieldsForSelect;
047:
048: /**
049: * Constructor for SqlSelectStatement.
050: *
051: * @param pf
052: * @param cld
053: * @param query
054: * @param logger
055: */
056: public SqlSelectStatement(Platform pf, ClassDescriptor cld,
057: Query query, Logger logger) {
058: super (pf, cld, query, logger);
059: }
060:
061: /**
062: * Constructor for SqlSelectStatement.
063: *
064: * @param parent
065: * @param pf
066: * @param cld
067: * @param query
068: * @param logger
069: */
070: public SqlSelectStatement(SqlQueryStatement parent, Platform pf,
071: ClassDescriptor cld, Query query, Logger logger) {
072: super (parent, pf, cld, query, logger);
073: }
074:
075: /**
076: * Append a Column with alias: A0 name -> A0.name
077: * @param anAlias the TableAlias
078: * @param field
079: * @param buf
080: */
081: protected void appendColumn(TableAlias anAlias,
082: FieldDescriptor field, StringBuffer buf) {
083: buf.append(anAlias.alias);
084: buf.append(".");
085: buf.append(field.getColumnName());
086: }
087:
088: /**
089: * Appends to the statement a comma separated list of column names.
090: *
091: * DO NOT use this if order of columns is important. The row readers build reflectively and look up
092: * column names to find values, so this is safe. In the case of update, you CANNOT use this as the
093: * order of columns is important.
094: *
095: * @return list of column names for the set of all unique columns for multiple classes mapped to the
096: * same table.
097: */
098: protected List appendListOfColumnsForSelect(StringBuffer buf) {
099: FieldDescriptor[] fieldDescriptors = getFieldsForSelect();
100: ArrayList columnList = new ArrayList();
101: TableAlias searchAlias = getSearchTable();
102:
103: for (int i = 0; i < fieldDescriptors.length; i++) {
104: FieldDescriptor field = fieldDescriptors[i];
105: TableAlias alias = getTableAliasForClassDescriptor(field
106: .getClassDescriptor());
107: if (alias == null) {
108: alias = searchAlias;
109: }
110: if (i > 0) {
111: buf.append(",");
112: }
113: appendColumn(alias, field, buf);
114: columnList.add(field.getAttributeName());
115: }
116:
117: appendClazzColumnForSelect(buf);
118: return columnList;
119: }
120:
121: /**
122: * Get MultiJoined ClassDescriptors
123: * @param cld
124: */
125: private ClassDescriptor[] getMultiJoinedClassDescriptors(
126: ClassDescriptor cld) {
127: DescriptorRepository repository = cld.getRepository();
128: Class[] multiJoinedClasses = repository
129: .getSubClassesMultipleJoinedTables(cld, true);
130: ClassDescriptor[] result = new ClassDescriptor[multiJoinedClasses.length];
131:
132: for (int i = 0; i < multiJoinedClasses.length; i++) {
133: result[i] = repository
134: .getDescriptorFor(multiJoinedClasses[i]);
135: }
136:
137: return result;
138: }
139:
140: /**
141: * Create the OJB_CLAZZ pseudo column based on CASE WHEN.
142: * This column defines the Class to be instantiated.
143: * @param buf
144: */
145: private void appendClazzColumnForSelect(StringBuffer buf) {
146: ClassDescriptor cld = getSearchClassDescriptor();
147: ClassDescriptor[] clds = getMultiJoinedClassDescriptors(cld);
148:
149: if (clds.length == 0) {
150: return;
151: }
152:
153: buf.append(",CASE");
154:
155: for (int i = clds.length; i > 0; i--) {
156: buf.append(" WHEN ");
157:
158: ClassDescriptor subCld = clds[i - 1];
159: FieldDescriptor[] fieldDescriptors = subCld.getPkFields();
160:
161: TableAlias alias = getTableAliasForClassDescriptor(subCld);
162: for (int j = 0; j < fieldDescriptors.length; j++) {
163: FieldDescriptor field = fieldDescriptors[j];
164: if (j > 0) {
165: buf.append(" AND ");
166: }
167: appendColumn(alias, field, buf);
168: buf.append(" IS NOT NULL");
169: }
170: buf.append(" THEN '").append(subCld.getClassNameOfObject())
171: .append("'");
172: }
173: buf.append(" ELSE '").append(cld.getClassNameOfObject())
174: .append("'");
175: buf.append(" END AS " + SqlHelper.OJB_CLASS_COLUMN);
176: }
177:
178: /**
179: * Return the Fields to be selected.
180: *
181: * @return the Fields to be selected
182: */
183: protected FieldDescriptor[] getFieldsForSelect() {
184: if (fieldsForSelect == null || fieldsForSelect.get() == null) {
185: fieldsForSelect = new WeakReference(
186: buildFieldsForSelect(getSearchClassDescriptor()));
187: }
188: return (FieldDescriptor[]) fieldsForSelect.get();
189: }
190:
191: /**
192: * Return the Fields to be selected.
193: *
194: * @param cld the ClassDescriptor
195: * @return the Fields to be selected
196: */
197: protected FieldDescriptor[] buildFieldsForSelect(ClassDescriptor cld) {
198: DescriptorRepository repository = cld.getRepository();
199: Set fields = new ListOrderedSet(); // keep the order of the fields
200:
201: // add Standard Fields
202: // MBAIRD: if the object being queried on has multiple classes mapped to the table,
203: // then we will get all the fields that are a unique set across all those classes so if we need to
204: // we can materialize an extent
205: FieldDescriptor fds[] = repository
206: .getFieldDescriptorsForMultiMappedTable(cld);
207: for (int i = 0; i < fds.length; i++) {
208: fields.add(fds[i]);
209: }
210:
211: // add inherited Fields. This is important when querying for a class having a super-reference
212: fds = cld.getFieldDescriptor(true);
213: for (int i = 0; i < fds.length; i++) {
214: fields.add(fds[i]);
215: }
216:
217: // add Fields of joined subclasses
218: Class[] multiJoinedClasses = repository
219: .getSubClassesMultipleJoinedTables(cld, true);
220: for (int c = 0; c < multiJoinedClasses.length; c++) {
221: ClassDescriptor subCld = repository
222: .getDescriptorFor(multiJoinedClasses[c]);
223: fds = subCld.getFieldDescriptions();
224: for (int i = 0; i < fds.length; i++) {
225: fields.add(fds[i]);
226: }
227: }
228:
229: FieldDescriptor[] result = new FieldDescriptor[fields.size()];
230: fields.toArray(result);
231: return result;
232: }
233:
234: /**
235: * Appends to the statement a comma separated list of column names.
236: *
237: * @param columns defines the columns to be selected (for reports)
238: * @return list of column names
239: */
240: protected List appendListOfColumns(String[] columns,
241: StringBuffer buf) {
242: ArrayList columnList = new ArrayList();
243:
244: for (int i = 0; i < columns.length; i++) {
245: if (i > 0) {
246: buf.append(",");
247: }
248: appendColName(columns[i], false, null, buf);
249: columnList.add(columns[i]);
250: }
251: return columnList;
252: }
253:
254: /**
255: * @see org.apache.ojb.broker.accesslayer.sql.SqlQueryStatement#buildStatement()
256: */
257: protected String buildStatement() {
258: StringBuffer stmt = new StringBuffer(1024);
259: Query query = getQuery();
260: boolean first = true;
261: List orderByFields = null;
262: String[] attributes = null;
263: String[] joinAttributes = null;
264: Iterator it = getJoinTreeToCriteria().entrySet().iterator();
265: List columnList = new ArrayList();
266:
267: if (query instanceof ReportQuery) {
268: attributes = ((ReportQuery) query).getAttributes();
269: joinAttributes = ((ReportQuery) query).getJoinAttributes();
270: }
271:
272: while (it.hasNext()) {
273: Map.Entry entry = (Map.Entry) it.next();
274: Criteria whereCrit = (Criteria) entry.getValue();
275: Criteria havingCrit = query.getHavingCriteria();
276: StringBuffer where = new StringBuffer();
277: StringBuffer having = new StringBuffer();
278: List groupByFields;
279:
280: // Set correct tree of joins for the current criteria
281: setRoot((TableAlias) entry.getKey());
282:
283: if (whereCrit != null && whereCrit.isEmpty()) {
284: whereCrit = null;
285: }
286:
287: if (havingCrit != null && havingCrit.isEmpty()) {
288: havingCrit = null;
289: }
290:
291: if (first) {
292: first = false;
293: } else {
294: stmt.append(" UNION ");
295: }
296:
297: stmt.append("SELECT ");
298: if (query.isDistinct()) {
299: stmt.append("DISTINCT ");
300: }
301:
302: if (attributes == null || attributes.length == 0) {
303: /**
304: * MBAIRD: use the appendListofColumnsForSelect, as it finds
305: * the union of select items for all object mapped to the same table. This
306: * will allow us to load objects with unique mapping fields that are mapped
307: * to the same table.
308: */
309: columnList.addAll(appendListOfColumnsForSelect(stmt));
310: } else {
311: columnList
312: .addAll(appendListOfColumns(attributes, stmt));
313: }
314:
315: // BRJ:
316: // joinColumns are only used to force the building of a join;
317: // they are not appended to the select-clause !
318: // these columns are used in COUNT-ReportQueries and
319: // are taken from the query the COUNT is based on
320: if (joinAttributes != null && joinAttributes.length > 0) {
321: for (int i = 0; i < joinAttributes.length; i++) {
322: getAttributeInfo(joinAttributes[i], false, null,
323: getQuery().getPathClasses());
324: }
325: }
326:
327: groupByFields = query.getGroupBy();
328: ensureColumns(groupByFields, columnList);
329:
330: orderByFields = query.getOrderBy();
331: columnList = ensureColumns(orderByFields, columnList, stmt);
332: /*
333: arminw:
334: TODO: this feature doesn't work, so remove this in future
335: */
336: /**
337: * treeder: going to map superclass tables here,
338: * not sure about the columns, just using all columns for now
339: */
340: ClassDescriptor cld = getBaseClassDescriptor();
341: ClassDescriptor cldSuper = null;
342: if (cld.getSuperClass() != null) {
343: // then we have a super class so join tables
344: cldSuper = cld.getRepository().getDescriptorFor(
345: cld.getSuperClass());
346: appendSuperClassColumns(cldSuper, stmt);
347: }
348:
349: stmt.append(" FROM ");
350: appendTableWithJoins(getRoot(), where, stmt);
351:
352: if (cld.getSuperClass() != null) {
353: appendSuperClassJoin(cld, cldSuper, stmt, where);
354: }
355:
356: appendWhereClause(where, whereCrit, stmt);
357: appendGroupByClause(groupByFields, stmt);
358: appendHavingClause(having, havingCrit, stmt);
359: }
360:
361: appendOrderByClause(orderByFields, columnList, stmt);
362:
363: if (query instanceof ReportQueryByCriteria) {
364: ((ReportQueryByCriteria) query)
365: .setAttributeFieldDescriptors(m_attrToFld);
366: }
367:
368: return stmt.toString();
369: }
370:
371: /*
372: arminw:
373: TODO: this feature doesn't work, so remove this in future
374: */
375: private void appendSuperClassJoin(ClassDescriptor cld,
376: ClassDescriptor cldSuper, StringBuffer stmt,
377: StringBuffer where) {
378: stmt.append(",");
379: appendTable(cldSuper, stmt);
380:
381: if (where != null) {
382: if (where.length() > 0) {
383: where.append(" AND ");
384: }
385:
386: // get reference field in super class
387: // TODO: do not use the superclassfield anymore, just assume that the id is the same in both tables - @see PBroker.storeToDb
388: int super FieldRef = cld.getSuperClassFieldRef();
389: FieldDescriptor refField = cld
390: .getFieldDescriptorByIndex(super FieldRef);
391:
392: appendTable(cldSuper, where);
393: where.append(".");
394: appendField(cldSuper.getAutoIncrementFields()[0], where);
395: where.append(" = ");
396: appendTable(cld, where);
397: where.append(".");
398: appendField(refField, where);
399: }
400: }
401:
402: private void appendSuperClassColumns(ClassDescriptor cldSuper,
403: StringBuffer buf) {
404: FieldDescriptor[] fields = cldSuper.getFieldDescriptions();
405: for (int i = 0; i < fields.length; i++) {
406: FieldDescriptor field = fields[i];
407: if (i > 0) {
408: buf.append(",");
409: }
410: buf.append(cldSuper.getFullTableName());
411: buf.append(".");
412: buf.append(field.getColumnName());
413: }
414: }
415:
416: /**
417: * Append table name. Quote if necessary.
418: */
419: protected void appendTable(ClassDescriptor cld, StringBuffer buf) {
420: buf.append(cld.getFullTableName());
421: }
422:
423: /**
424: * Append column name. Quote if necessary.
425: */
426: protected void appendField(FieldDescriptor fld, StringBuffer buf) {
427: buf.append(fld.getColumnName());
428: }
429:
430: public Query getQueryInstance() {
431: return getQuery();
432: }
433:
434: public int getColumnIndex(FieldDescriptor fld) {
435: int index = JdbcType.MIN_INT;
436: FieldDescriptor[] fields = getFieldsForSelect();
437: if (fields != null) {
438: for (int i = 0; i < fields.length; i++) {
439: if (fields[i].equals(fld)) {
440: index = i + 1; // starts at 1
441: break;
442: }
443: }
444: }
445: return index;
446: }
447: }
|