1 package org.apache.torque.util;
2
3 /* ====================================================================
4 * The Apache Software License, Version 1.1
5 *
6 * Copyright (c) 2001-2003 The Apache Software Foundation. All rights
7 * reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in
18 * the documentation and/or other materials provided with the
19 * distribution.
20 *
21 * 3. The end-user documentation included with the redistribution,
22 * if any, must include the following acknowledgment:
23 * "This product includes software developed by the
24 * Apache Software Foundation (http://www.apache.org/)."
25 * Alternately, this acknowledgment may appear in the software itself,
26 * if and wherever such third-party acknowledgments normally appear.
27 *
28 * 4. The names "Apache" and "Apache Software Foundation" and
29 * "Apache Turbine" must not be used to endorse or promote products
30 * derived from this software without prior written permission. For
31 * written permission, please contact apache@apache.org.
32 *
33 * 5. Products derived from this software may not be called "Apache",
34 * "Apache Turbine", nor may "Apache" appear in their name, without
35 * prior written permission of the Apache Software Foundation.
36 *
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48 * SUCH DAMAGE.
49 * ====================================================================
50 *
51 * This software consists of voluntary contributions made by many
52 * individuals on behalf of the Apache Software Foundation. For more
53 * information on the Apache Software Foundation, please see
54 * <http://www.apache.org/>.
55 */
56
57 import java.io.BufferedOutputStream;
58 import java.io.ByteArrayOutputStream;
59 import java.io.IOException;
60 import java.io.ObjectOutputStream;
61 import java.io.Serializable;
62 import java.math.BigDecimal;
63 import java.sql.Connection;
64 import java.sql.PreparedStatement;
65 import java.sql.SQLException;
66 import java.sql.Statement;
67 import java.util.ArrayList;
68 import java.util.Collections;
69 import java.util.HashSet;
70 import java.util.Hashtable;
71 import java.util.Iterator;
72 import java.util.List;
73
74 import org.apache.commons.lang.StringUtils;
75
76 import org.apache.commons.logging.Log;
77 import org.apache.commons.logging.LogFactory;
78
79 import org.apache.torque.Torque;
80 import org.apache.torque.TorqueException;
81 import org.apache.torque.adapter.DB;
82 import org.apache.torque.map.ColumnMap;
83 import org.apache.torque.map.DatabaseMap;
84 import org.apache.torque.map.MapBuilder;
85 import org.apache.torque.map.TableMap;
86 import org.apache.torque.oid.IdGenerator;
87 import org.apache.torque.om.NumberKey;
88 import org.apache.torque.om.ObjectKey;
89 import org.apache.torque.om.SimpleKey;
90 import org.apache.torque.om.StringKey;
91
92 import com.workingdogs.village.Column;
93 import com.workingdogs.village.DataSet;
94 import com.workingdogs.village.KeyDef;
95 import com.workingdogs.village.QueryDataSet;
96 import com.workingdogs.village.Record;
97 import com.workingdogs.village.Schema;
98 import com.workingdogs.village.TableDataSet;
99
100 /***
101 * This is the base class for all Peer classes in the system. Peer
102 * classes are responsible for isolating all of the database access
103 * for a specific business object. They execute all of the SQL
104 * against the database. Over time this class has grown to include
105 * utility methods which ease execution of cross-database queries and
106 * the implementation of concrete Peers.
107 *
108 * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
109 * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
110 * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
111 * @author <a href="mailto:stephenh@chase3000.com">Stephen Haberman</a>
112 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
113 * @version $Id: BasePeer.java,v 1.76 2003/08/25 16:33:22 henning Exp $
114 */
115 public abstract class BasePeer implements java.io.Serializable
116 {
117 /*** Constant criteria key to reference ORDER BY columns. */
118 public static final String ORDER_BY = "ORDER BY";
119
120 /***
121 * Constant criteria key to remove Case Information from
122 * search/ordering criteria.
123 */
124 public static final String IGNORE_CASE = "IgNOrE cAsE";
125
126 /*** Classes that implement this class should override this value. */
127 public static final String TABLE_NAME = "TABLE_NAME";
128
129 /***
130 * The Torque default MapBuilder.
131 *
132 * @deprecated there is no default map builder!
133 */
134 public static final String DEFAULT_MAP_BUILDER =
135 "org.apache.torque.util.db.map.TurbineMapBuilder";
136
137 /*** Hashtable that contains the cached mapBuilders. */
138 private static Hashtable mapBuilders = new Hashtable(5);
139
140 /*** the log */
141 protected static Log log = LogFactory.getLog(BasePeer.class);
142
143 /***
144 * Converts a hashtable to a byte array for storage/serialization.
145 *
146 * @param hash The Hashtable to convert.
147 * @return A byte[] with the converted Hashtable.
148 * @throws TorqueException Any exceptions caught during processing will be
149 * rethrown wrapped into a TorqueException.
150 */
151 public static byte[] hashtableToByteArray(Hashtable hash)
152 throws TorqueException
153 {
154 Hashtable saveData = new Hashtable(hash.size());
155 String key = null;
156 Object value = null;
157 byte[] byteArray = null;
158
159 Iterator keys = hash.keySet().iterator();
160 while (keys.hasNext())
161 {
162 key = (String) keys.next();
163 value = hash.get(key);
164 if (value instanceof Serializable)
165 {
166 saveData.put(key, value);
167 }
168 }
169
170 ByteArrayOutputStream baos = null;
171 BufferedOutputStream bos = null;
172 ObjectOutputStream out = null;
173 try
174 {
175 // These objects are closed in the finally.
176 baos = new ByteArrayOutputStream();
177 bos = new BufferedOutputStream(baos);
178 out = new ObjectOutputStream(bos);
179
180 out.writeObject(saveData);
181 out.flush();
182 bos.flush();
183 byteArray = baos.toByteArray();
184 }
185 catch (Exception e)
186 {
187 throwTorqueException(e);
188 }
189 finally
190 {
191 if (out != null)
192 {
193 try
194 {
195 out.close();
196 }
197 catch (IOException ignored)
198 {
199 }
200 }
201
202 if (bos != null)
203 {
204 try
205 {
206 bos.close();
207 }
208 catch (IOException ignored)
209 {
210 }
211 }
212
213 if (baos != null)
214 {
215 try
216 {
217 baos.close();
218 }
219 catch (IOException ignored)
220 {
221 }
222 }
223 }
224 return byteArray;
225 }
226
227 private static void throwTorqueException(Exception e)
228 throws TorqueException
229 {
230 if (e instanceof TorqueException)
231 {
232 throw (TorqueException)e;
233 }
234 else
235 {
236 throw new TorqueException(e);
237 }
238 }
239
240 /***
241 * Sets up a Schema for a table. This schema is then normally
242 * used as the argument for initTableColumns().
243 *
244 * @param tableName The name of the table.
245 * @return A Schema.
246 */
247 public static Schema initTableSchema(String tableName)
248 {
249 return initTableSchema(tableName, Torque.getDefaultDB());
250 }
251
252 /***
253 * Sets up a Schema for a table. This schema is then normally
254 * used as the argument for initTableColumns
255 *
256 * @param tableName The propery name for the database in the
257 * configuration file.
258 * @param dbName The name of the database.
259 * @return A Schema.
260 */
261 public static Schema initTableSchema(String tableName, String dbName)
262 {
263 Schema schema = null;
264 Connection con = null;
265
266 try
267 {
268 con = Torque.getConnection(dbName);
269 schema = new Schema().schema(con, tableName);
270 }
271 catch (Exception e)
272 {
273 log.error(e);
274 throw new Error("Error in BasePeer.initTableSchema("
275 + tableName
276 + "): "
277 + e.getMessage());
278 }
279 finally
280 {
281 Torque.closeConnection(con);
282 }
283 return schema;
284 }
285
286 /***
287 * Creates a Column array for a table based on its Schema.
288 *
289 * @param schema A Schema object.
290 * @return A Column[].
291 */
292 public static Column[] initTableColumns(Schema schema)
293 {
294 Column[] columns = null;
295 try
296 {
297 int numberOfColumns = schema.numberOfColumns();
298 columns = new Column[numberOfColumns];
299 for (int i = 0; i < numberOfColumns; i++)
300 {
301 columns[i] = schema.column(i + 1);
302 }
303 }
304 catch (Exception e)
305 {
306 log.error(e);
307 throw new Error(
308 "Error in BasePeer.initTableColumns(): " + e.getMessage());
309 }
310 return columns;
311 }
312
313 /***
314 * Convenience method to create a String array of column names.
315 *
316 * @param columns A Column[].
317 * @return A String[].
318 */
319 public static String[] initColumnNames(Column[] columns)
320 {
321 String[] columnNames = null;
322 columnNames = new String[columns.length];
323 for (int i = 0; i < columns.length; i++)
324 {
325 columnNames[i] = columns[i].name().toUpperCase();
326 }
327 return columnNames;
328 }
329
330 /***
331 * Convenience method to create a String array of criteria keys.
332 *
333 * @param tableName Name of table.
334 * @param columnNames A String[].
335 * @return A String[].
336 */
337 public static String[] initCriteriaKeys(
338 String tableName,
339 String[] columnNames)
340 {
341 String[] keys = new String[columnNames.length];
342 for (int i = 0; i < columnNames.length; i++)
343 {
344 keys[i] = tableName + "." + columnNames[i].toUpperCase();
345 }
346 return keys;
347 }
348
349 /***
350 * Convenience method that uses straight JDBC to delete multiple
351 * rows. Village throws an Exception when multiple rows are
352 * deleted.
353 *
354 * @param con A Connection.
355 * @param table The table to delete records from.
356 * @param column The column in the where clause.
357 * @param value The value of the column.
358 * @throws TorqueException Any exceptions caught during processing will be
359 * rethrown wrapped into a TorqueException.
360 */
361 public static void deleteAll(
362 Connection con,
363 String table,
364 String column,
365 int value)
366 throws TorqueException
367 {
368 Statement statement = null;
369 try
370 {
371 statement = con.createStatement();
372
373 StringBuffer query = new StringBuffer();
374 query.append("DELETE FROM ")
375 .append(table)
376 .append(" WHERE ")
377 .append(column)
378 .append(" = ")
379 .append(value);
380
381 statement.executeUpdate(query.toString());
382 }
383 catch (SQLException e)
384 {
385 throw new TorqueException(e);
386 }
387 finally
388 {
389 if (statement != null)
390 {
391 try
392 {
393 statement.close();
394 }
395 catch (SQLException ignored)
396 {
397 }
398 }
399 }
400 }
401
402 /***
403 * Convenience method that uses straight JDBC to delete multiple
404 * rows. Village throws an Exception when multiple rows are
405 * deleted. This method attempts to get the default database from
406 * the pool.
407 *
408 * @param table The table to delete records from.
409 * @param column The column in the where clause.
410 * @param value The value of the column.
411 * @throws TorqueException Any exceptions caught during processing will be
412 * rethrown wrapped into a TorqueException.
413 */
414 public static void deleteAll(String table, String column, int value)
415 throws TorqueException
416 {
417 Connection con = null;
418 try
419 {
420 // Get a connection to the db.
421 con = Torque.getConnection("default");
422 deleteAll(con, table, column, value);
423 }
424 finally
425 {
426 Torque.closeConnection(con);
427 }
428 }
429
430 /***
431 * Method to perform deletes based on values and keys in a
432 * Criteria.
433 *
434 * @param criteria The criteria to use.
435 * @throws TorqueException Any exceptions caught during processing will be
436 * rethrown wrapped into a TorqueException.
437 */
438 public static void doDelete(Criteria criteria) throws TorqueException
439 {
440 Connection con = null;
441 try
442 {
443 con = Transaction.beginOptional(
444 criteria.getDbName(),
445 criteria.isUseTransaction());
446 doDelete(criteria, con);
447 Transaction.commit(con);
448 }
449 catch (TorqueException e)
450 {
451 Transaction.safeRollback(con);
452 throw e;
453 }
454 }
455
456 /***
457 * Method to perform deletes based on values and keys in a Criteria.
458 *
459 * @param criteria The criteria to use.
460 * @param con A Connection.
461 * @throws TorqueException Any exceptions caught during processing will be
462 * rethrown wrapped into a TorqueException.
463 */
464 public static void doDelete(Criteria criteria, Connection con)
465 throws TorqueException
466 {
467 DB db = Torque.getDB(criteria.getDbName());
468 DatabaseMap dbMap = Torque.getDatabaseMap(criteria.getDbName());
469
470 // Set up a list of required tables and add extra entries to
471 // criteria if directed to delete all related records.
472 // StringStack.add() only adds element if it is unique.
473 HashSet tables = new HashSet();
474 Iterator it = criteria.keySet().iterator();
475 while (it.hasNext())
476 {
477 String key = (String) it.next();
478 Criteria.Criterion c = criteria.getCriterion(key);
479 List tableNames = c.getAllTables();
480 for (int i = 0; i < tableNames.size(); i++)
481 {
482 String name = (String) tableNames.get(i);
483 String tableName2 = criteria.getTableForAlias(name);
484 if (tableName2 != null)
485 {
486 tables.add(new StringBuffer(
487 name.length() + tableName2.length() + 1)
488 .append(tableName2)
489 .append(' ')
490 .append(name)
491 .toString());
492 }
493 else
494 {
495 tables.add(name);
496 }
497 }
498
499 if (criteria.isCascade())
500 {
501 // This steps thru all the columns in the database.
502 TableMap[] tableMaps = dbMap.getTables();
503 for (int i = 0; i < tableMaps.length; i++)
504 {
505 ColumnMap[] columnMaps = tableMaps[i].getColumns();
506 for (int j = 0; j < columnMaps.length; j++)
507 {
508 // Only delete rows where the foreign key is
509 // also a primary key. Other rows need
510 // updating, but that is not implemented.
511 if (columnMaps[j].isForeignKey()
512 && columnMaps[j].isPrimaryKey()
513 && key.equals(columnMaps[j].getRelatedName()))
514 {
515 tables.add(tableMaps[i].getName());
516 criteria.add(columnMaps[j].getFullyQualifiedName(),
517 criteria.getValue(key));
518 }
519 }
520 }
521 }
522 }
523 Iterator tabIt = tables.iterator();
524 while (tabIt.hasNext())
525 {
526 String tab = (String) tabIt.next();
527 KeyDef kd = new KeyDef();
528 HashSet whereClause = new HashSet();
529
530 ColumnMap[] columnMaps = dbMap.getTable(tab).getColumns();
531 for (int j = 0; j < columnMaps.length; j++)
532 {
533 ColumnMap colMap = columnMaps[j];
534 if (colMap.isPrimaryKey())
535 {
536 kd.addAttrib(colMap.getColumnName());
537 }
538 String key = new StringBuffer(colMap.getTableName())
539 .append('.')
540 .append(colMap.getColumnName())
541 .toString();
542 if (criteria.containsKey(key))
543 {
544 if (criteria.getComparison(key).equals(Criteria.CUSTOM))
545 {
546 whereClause.add(criteria.getString(key));
547 }
548 else
549 {
550 whereClause.add(SqlExpression.build(
551 colMap.getColumnName(),
552 criteria.getValue(key),
553 criteria.getComparison(key),
554 criteria.isIgnoreCase(),
555 db));
556 }
557 }
558 }
559
560 // Execute the statement.
561 TableDataSet tds = null;
562 try
563 {
564 tds = new TableDataSet(con, tab, kd);
565 String sqlSnippet = StringUtils.join(whereClause.iterator(), " AND ");
566
567 if (log.isDebugEnabled())
568 {
569 log.debug("BasePeer.doDelete: whereClause=" + sqlSnippet);
570 }
571
572 tds.where(sqlSnippet);
573 tds.fetchRecords();
574 if (tds.size() > 1 && criteria.isSingleRecord())
575 {
576 handleMultipleRecords(tds);
577 }
578 for (int j = 0; j < tds.size(); j++)
579 {
580 Record rec = tds.getRecord(j);
581 rec.markToBeDeleted();
582 rec.save();
583 }
584 }
585 catch (Exception e)
586 {
587 throwTorqueException(e);
588 }
589 finally
590 {
591 if (tds != null)
592 {
593 try
594 {
595 tds.close();
596 }
597 catch (Exception ignored)
598 {
599 }
600 }
601 }
602 }
603 }
604
605 /***
606 * Method to perform inserts based on values and keys in a
607 * Criteria.
608 * <p>
609 * If the primary key is auto incremented the data in Criteria
610 * will be inserted and the auto increment value will be returned.
611 * <p>
612 * If the primary key is included in Criteria then that value will
613 * be used to insert the row.
614 * <p>
615 * If no primary key is included in Criteria then we will try to
616 * figure out the primary key from the database map and insert the
617 * row with the next available id using util.db.IDBroker.
618 * <p>
619 * If no primary key is defined for the table the values will be
620 * inserted as specified in Criteria and -1 will be returned.
621 *
622 * @param criteria Object containing values to insert.
623 * @return An Object which is the id of the row that was inserted
624 * (if the table has a primary key) or null (if the table does not
625 * have a primary key).
626 * @throws TorqueException Any exceptions caught during processing will be
627 * rethrown wrapped into a TorqueException.
628 */
629 public static ObjectKey doInsert(Criteria criteria) throws TorqueException
630 {
631 Connection con = null;
632 ObjectKey id = null;
633
634 try
635 {
636 con = Transaction.beginOptional(
637 criteria.getDbName(),
638 criteria.isUseTransaction());
639 id = doInsert(criteria, con);
640 Transaction.commit(con);
641 }
642 catch (TorqueException e)
643 {
644 Transaction.safeRollback(con);
645 throw e;
646 }
647
648 return id;
649 }
650
651 /***
652 * Method to perform inserts based on values and keys in a
653 * Criteria.
654 * <p>
655 * If the primary key is auto incremented the data in Criteria
656 * will be inserted and the auto increment value will be returned.
657 * <p>
658 * If the primary key is included in Criteria then that value will
659 * be used to insert the row.
660 * <p>
661 * If no primary key is included in Criteria then we will try to
662 * figure out the primary key from the database map and insert the
663 * row with the next available id using util.db.IDBroker.
664 * <p>
665 * If no primary key is defined for the table the values will be
666 * inserted as specified in Criteria and null will be returned.
667 *
668 * @param criteria Object containing values to insert.
669 * @param con A Connection.
670 * @return An Object which is the id of the row that was inserted
671 * (if the table has a primary key) or null (if the table does not
672 * have a primary key).
673 * @throws TorqueException Any exceptions caught during processing will be
674 * rethrown wrapped into a TorqueException.
675 */
676 public static ObjectKey doInsert(Criteria criteria, Connection con)
677 throws TorqueException
678 {
679 SimpleKey id = null;
680
681 // Get the table name and method for determining the primary
682 // key value.
683 String tableName = null;
684 Iterator keys = criteria.keySet().iterator();
685 if (keys.hasNext())
686 {
687 tableName = criteria.getTableName((String) keys.next());
688 }
689 else
690 {
691 throw new TorqueException("Database insert attempted without "
692 + "anything specified to insert");
693 }
694
695 DatabaseMap dbMap = Torque.getDatabaseMap(criteria.getDbName());
696 TableMap tableMap = dbMap.getTable(tableName);
697 Object keyInfo = tableMap.getPrimaryKeyMethodInfo();
698 IdGenerator keyGen = tableMap.getIdGenerator();
699
700 ColumnMap pk = getPrimaryKey(criteria);
701
702 // pk will be null if there is no primary key defined for the table
703 // we're inserting into.
704 if (pk != null && !criteria.containsKey(pk.getFullyQualifiedName()))
705 {
706 if (keyGen == null)
707 {
708 throw new TorqueException(
709 "IdGenerator for table '" + tableName + "' is null");
710 }
711 // If the keyMethod is SEQUENCE or IDBROKERTABLE, get the id
712 // before the insert.
713
714 if (keyGen.isPriorToInsert())
715 {
716 try
717 {
718 if (pk.getType() instanceof Number)
719 {
720 id = new NumberKey(
721 keyGen.getIdAsBigDecimal(con, keyInfo));
722 }
723 else
724 {
725 id = new StringKey(keyGen.getIdAsString(con, keyInfo));
726 }
727 }
728 catch (Exception e)
729 {
730 throwTorqueException(e);
731 }
732 criteria.add(pk.getFullyQualifiedName(), id);
733 }
734 }
735
736 // Use Village to perform the insert.
737 TableDataSet tds = null;
738 try
739 {
740 tds = new TableDataSet(con, tableName);
741 Record rec = tds.addRecord();
742 BasePeer.insertOrUpdateRecord(rec, tableName, criteria);
743 }
744 catch (Exception e)
745 {
746 throwTorqueException(e);
747 }
748 finally
749 {
750 if (tds != null)
751 {
752 try
753 {
754 tds.close();
755 }
756 catch (Exception e)
757 {
758 throwTorqueException(e);
759 }
760 }
761 }
762
763 // If the primary key column is auto-incremented, get the id
764 // now.
765 if (pk != null && keyGen != null && keyGen.isPostInsert())
766 {
767 try
768 {
769 if (pk.getType() instanceof Number)
770 {
771 id = new NumberKey(keyGen.getIdAsBigDecimal(con, keyInfo));
772 }
773 else
774 {
775 id = new StringKey(keyGen.getIdAsString(con, keyInfo));
776 }
777 }
778 catch (Exception e)
779 {
780 throwTorqueException(e);
781 }
782 }
783
784 return id;
785 }
786
787 /***
788 * Grouping of code used in both doInsert() and doUpdate()
789 * methods. Sets up a Record for saving.
790 *
791 * @param rec A Record.
792 * @param tableName Name of table.
793 * @param criteria A Criteria.
794 * @throws TorqueException Any exceptions caught during processing will be
795 * rethrown wrapped into a TorqueException.
796 */
797 private static void insertOrUpdateRecord(
798 Record rec,
799 String tableName,
800 Criteria criteria)
801 throws TorqueException
802 {
803 DatabaseMap dbMap = Torque.getDatabaseMap(criteria.getDbName());
804
805 ColumnMap[] columnMaps = dbMap.getTable(tableName).getColumns();
806 boolean shouldSave = false;
807 for (int j = 0; j < columnMaps.length; j++)
808 {
809 ColumnMap colMap = columnMaps[j];
810 String key = new StringBuffer(colMap.getTableName())
811 .append('.')
812 .append(colMap.getColumnName())
813 .toString();
814 if (criteria.containsKey(key))
815 {
816 // A village Record.setValue( String, Object ) would
817 // be nice here.
818 Object obj = criteria.getValue(key);
819 if (obj instanceof SimpleKey)
820 {
821 obj = ((SimpleKey) obj).getValue();
822 }
823 try
824 {
825 if (obj == null)
826 {
827 rec.setValueNull(colMap.getColumnName());
828 }
829 else if (obj instanceof String)
830 {
831 rec.setValue(colMap.getColumnName(), (String) obj);
832 }
833 else if (obj instanceof Integer)
834 {
835 rec.setValue(colMap.getColumnName(),
836 criteria.getInt(key));
837 }
838 else if (obj instanceof BigDecimal)
839 {
840 rec.setValue(colMap.getColumnName(), (BigDecimal) obj);
841 }
842 else if (obj instanceof Boolean)
843 {
844 rec.setValue(colMap.getColumnName(),
845 criteria.getBoolean(key) ? 1 : 0);
846 }
847 else if (obj instanceof java.util.Date)
848 {
849 rec.setValue(colMap.getColumnName(),
850 (java.util.Date) obj);
851 }
852 else if (obj instanceof Float)
853 {
854 rec.setValue(colMap.getColumnName(),
855 criteria.getFloat(key));
856 }
857 else if (obj instanceof Double)
858 {
859 rec.setValue(colMap.getColumnName(),
860 criteria.getDouble(key));
861 }
862 else if (obj instanceof Byte)
863 {
864 rec.setValue(colMap.getColumnName(),
865 ((Byte) obj).byteValue());
866 }
867 else if (obj instanceof Long)
868 {
869 rec.setValue(colMap.getColumnName(),
870 criteria.getLong(key));
871 }
872 else if (obj instanceof Short)
873 {
874 rec.setValue(colMap.getColumnName(),
875 ((Short) obj).shortValue());
876 }
877 else if (obj instanceof Hashtable)
878 {
879 rec.setValue(colMap.getColumnName(),
880 hashtableToByteArray((Hashtable) obj));
881 }
882 else if (obj instanceof byte[])
883 {
884 rec.setValue(colMap.getColumnName(), (byte[]) obj);
885 }
886 }
887 catch (Exception e)
888 {
889 throwTorqueException(e);
890 }
891 shouldSave = true;
892 }
893 }
894
895 if (shouldSave)
896 {
897 try
898 {
899 rec.save();
900 }
901 catch (Exception e)
902 {
903 throwTorqueException(e);
904 }
905 }
906 else
907 {
908 throw new TorqueException("No changes to save");
909 }
910 }
911
912 /***
913 * Method to create an SQL query for display only based on values in a
914 * Criteria.
915 *
916 * @param criteria A Criteria.
917 * @return the SQL query for display
918 * @exception TorqueException Trouble creating the query string.
919 */
920 static String createQueryDisplayString(Criteria criteria)
921 throws TorqueException
922 {
923 return createQuery(criteria).toString();
924 }
925
926 /***
927 * Build Oracle-style query with limit or offset.
928 * If the original SQL is in variable: query then the requlting
929 * SQL looks like this:
930 * <pre>
931 * SELECT B.* FROM (
932 * SELECT A.*, rownum as TORQUE$ROWNUM FROM (
933 * query
934 * ) A
935 * ) B WHERE B.TORQUE$ROWNUM > offset AND B.TORQUE$ROWNUM
936 * <= offset + limit
937 * </pre>
938 *
939 * @param query the query
940 * @param limit
941 * @param offset
942 * @return oracle-style query
943 */
944 private static String createOracleLimitOffsetQuery(Query query,
945 int limit, int offset)
946 {
947 StringBuffer buf = new StringBuffer();
948 buf.append("SELECT B.* FROM ( ");
949 buf.append("SELECT A.*, rownum AS TORQUE$ROWNUM FROM ( ");
950
951 buf.append(query.toString());
952 buf.append(" ) A ");
953 buf.append(" ) B WHERE ");
954
955 if (offset > 0)
956 {
957 buf.append(" B.TORQUE$ROWNUM > ");
958 buf.append(offset);
959 if (limit > 0)
960 {
961 buf.append(" AND B.TORQUE$ROWNUM <= ");
962 buf.append(offset + limit);
963 }
964 }
965 else
966 {
967 buf.append(" B.TORQUE$ROWNUM <= ");
968 buf.append(limit);
969 }
970 return buf.toString();
971 }
972
973 /***
974 * Method to create an SQL query for actual execution based on values in a
975 * Criteria.
976 *
977 * @param criteria A Criteria.
978 * @return the SQL query for actual execution
979 * @exception TorqueException Trouble creating the query string.
980 */
981 public static String createQueryString(Criteria criteria)
982 throws TorqueException
983 {
984 Query query = createQuery(criteria);
985 DB db = Torque.getDB(criteria.getDbName());
986
987 // Limit the number of rows returned.
988 int limit = criteria.getLimit();
989 int offset = criteria.getOffset();
990
991 String sql;
992 if ((limit > 0 || offset > 0)
993 && db.getLimitStyle() == DB.LIMIT_STYLE_ORACLE)
994 {
995 sql = createOracleLimitOffsetQuery(query, limit, offset);
996 criteria.setLimit(-1);
997 criteria.setOffset(0);
998 }
999 else
1000 {
1001 if (offset > 0 && db.supportsNativeOffset())
1002 {
1003 // Now set the criteria's limit and offset to return the
1004 // full resultset since the results are limited on the
1005 // server.
1006 criteria.setLimit(-1);
1007 criteria.setOffset(0);
1008 }
1009 else if (limit > 0 && db.supportsNativeLimit())
1010 {
1011 // Now set the criteria's limit to return the full
1012 // resultset since the results are limited on the server.
1013 criteria.setLimit(-1);
1014 }
1015 sql = query.toString();
1016 }
1017 if (log.isDebugEnabled())
1018 {
1019 log.debug(sql);
1020 }
1021 return sql;
1022 }
1023
1024 /***
1025 * Method to create an SQL query based on values in a Criteria. Note that
1026 * final manipulation of the limit and offset are performed when the query
1027 * is actually executed.
1028 *
1029 * @param criteria A Criteria.
1030 * @return the sql query
1031 * @exception TorqueException Trouble creating the query string.
1032 */
1033 static Query createQuery(Criteria criteria)
1034 throws TorqueException
1035 {
1036 Query query = new Query();
1037 DB db = Torque.getDB(criteria.getDbName());
1038 DatabaseMap dbMap = Torque.getDatabaseMap(criteria.getDbName());
1039
1040 UniqueList selectModifiers = query.getSelectModifiers();
1041 UniqueList selectClause = query.getSelectClause();
1042 UniqueList fromClause = query.getFromClause();
1043 UniqueList whereClause = query.getWhereClause();
1044 UniqueList orderByClause = query.getOrderByClause();
1045 UniqueList groupByClause = query.getGroupByClause();
1046
1047 UniqueList orderBy = criteria.getOrderByColumns();
1048 UniqueList groupBy = criteria.getGroupByColumns();
1049 UniqueList select = criteria.getSelectColumns();
1050 Hashtable aliases = criteria.getAsColumns();
1051 UniqueList modifiers = criteria.getSelectModifiers();
1052
1053 for (int i = 0; i < modifiers.size(); i++)
1054 {
1055 selectModifiers.add(modifiers.get(i));
1056 }
1057
1058 for (int i = 0; i < select.size(); i++)
1059 {
1060 String columnName = (String) select.get(i);
1061 if (columnName.indexOf('.') == -1 && columnName.indexOf('*') == -1)
1062 {
1063 throwMalformedColumnNameException("select", columnName);
1064 }
1065 String tableName = null;
1066 selectClause.add(columnName);
1067 int parenPos = columnName.indexOf('(');
1068 if (parenPos == -1)
1069 {
1070 tableName = columnName.substring(0, columnName.indexOf('.'));
1071 }
1072 else if (columnName.indexOf('.') > -1)
1073 {
1074 tableName =
1075 columnName.substring(parenPos + 1, columnName.indexOf('.'));
1076 // functions may contain qualifiers so only take the last
1077 // word as the table name.
1078 int lastSpace = tableName.lastIndexOf(' ');
1079 if (lastSpace != -1)
1080 {
1081 tableName = tableName.substring(lastSpace + 1);
1082 }
1083 }
1084 String tableName2 = criteria.getTableForAlias(tableName);
1085 if (tableName2 != null)
1086 {
1087 fromClause.add(new StringBuffer(
1088 tableName.length() + tableName2.length() + 1)
1089 .append(tableName2)
1090 .append(' ')
1091 .append(tableName)
1092 .toString());
1093 }
1094 else
1095 {
1096 fromClause.add(tableName);
1097 }
1098 }
1099
1100 Iterator it = aliases.keySet().iterator();
1101 while (it.hasNext())
1102 {
1103 String key = (String) it.next();
1104 selectClause.add((String) aliases.get(key) + " AS " + key);
1105 }
1106
1107 Iterator critKeys = criteria.keySet().iterator();
1108 while (critKeys.hasNext())
1109 {
1110 String key = (String) critKeys.next();
1111 Criteria.Criterion criterion = criteria.getCriterion(key);
1112 Criteria.Criterion[] someCriteria =
1113 criterion.getAttachedCriterion();
1114 String table = null;
1115 for (int i = 0; i < someCriteria.length; i++)
1116 {
1117 String tableName = someCriteria[i].getTable();
1118 table = criteria.getTableForAlias(tableName);
1119 if (table != null)
1120 {
1121 fromClause.add(new StringBuffer(
1122 tableName.length() + table.length() + 1)
1123 .append(table)
1124 .append(' ')
1125 .append(tableName)
1126 .toString());
1127 }
1128 else
1129 {
1130 fromClause.add(tableName);
1131 table = tableName;
1132 }
1133
1134 boolean ignorCase = ((criteria.isIgnoreCase()
1135 || someCriteria[i].isIgnoreCase())
1136 && (dbMap
1137 .getTable(table)
1138 .getColumn(someCriteria[i].getColumn())
1139 .getType()
1140 instanceof String));
1141
1142 someCriteria[i].setIgnoreCase(ignorCase);
1143 }
1144
1145 criterion.setDB(db);
1146 whereClause.add(criterion.toString());
1147 }
1148
1149 List join = criteria.getJoinL();
1150 if (join != null)
1151 {
1152 for (int i = 0; i < join.size(); i++)
1153 {
1154 String join1 = (String) join.get(i);
1155 String join2 = (String) criteria.getJoinR().get(i);
1156 if (join1.indexOf('.') == -1)
1157 {
1158 throwMalformedColumnNameException("join", join1);
1159 }
1160 if (join2.indexOf('.') == -1)
1161 {
1162 throwMalformedColumnNameException("join", join2);
1163 }
1164
1165 String tableName = join1.substring(0, join1.indexOf('.'));
1166 String table = criteria.getTableForAlias(tableName);
1167 if (table != null)
1168 {
1169 fromClause.add(new StringBuffer(
1170 tableName.length() + table.length() + 1)
1171 .append(table)
1172 .append(' ')
1173 .append(tableName)
1174 .toString());
1175 }
1176 else
1177 {
1178 fromClause.add(tableName);
1179 }
1180
1181 int dot = join2.indexOf('.');
1182 tableName = join2.substring(0, dot);
1183 table = criteria.getTableForAlias(tableName);
1184 if (table != null)
1185 {
1186 fromClause.add(new StringBuffer(
1187 tableName.length() + table.length() + 1)
1188 .append(table)
1189 .append(' ')
1190 .append(tableName)
1191 .toString());
1192 }
1193 else
1194 {
1195 fromClause.add(tableName);
1196 table = tableName;
1197 }
1198
1199 boolean ignorCase = (criteria.isIgnoreCase()
1200 && (dbMap
1201 .getTable(table)
1202 .getColumn(join2.substring(dot + 1, join2.length()))
1203 .getType()
1204 instanceof String));
1205
1206 whereClause.add(
1207 SqlExpression.buildInnerJoin(join1, join2, ignorCase, db));
1208 }
1209 }
1210
1211 // need to allow for multiple group bys
1212 if (groupBy != null && groupBy.size() > 0)
1213 {
1214 for (int i = 0; i < groupBy.size(); i++)
1215 {
1216 String groupByColumn = (String) groupBy.get(i);
1217 if (groupByColumn.indexOf('.') == -1)
1218 {
1219 throwMalformedColumnNameException("group by",
1220 groupByColumn);
1221 }
1222 groupByClause.add(groupByColumn);
1223 }
1224 }
1225
1226 Criteria.Criterion having = criteria.getHaving();
1227 if (having != null)
1228 {
1229 //String groupByString = null;
1230 query.setHaving(having.toString());
1231 }
1232
1233 if (orderBy != null && orderBy.size() > 0)
1234 {
1235 // Check for each String/Character column and apply
1236 // toUpperCase().
1237 for (int i = 0; i < orderBy.size(); i++)
1238 {
1239 String orderByColumn = (String) orderBy.get(i);
1240 if (orderByColumn.indexOf('.') == -1)
1241 {
1242 throwMalformedColumnNameException("order by",
1243 orderByColumn);
1244 }
1245 String tableName =
1246 orderByColumn.substring(0, orderByColumn.indexOf('.'));
1247 String table = criteria.getTableForAlias(tableName);
1248 if (table == null)
1249 {
1250 table = tableName;
1251 }
1252
1253 // See if there's a space (between the column list and sort
1254 // order in ORDER BY table.column DESC).
1255 int spacePos = orderByColumn.indexOf(' ');
1256 String columnName;
1257 if (spacePos == -1)
1258 {
1259 columnName =
1260 orderByColumn.substring(orderByColumn.indexOf('.') + 1);
1261 }
1262 else
1263 {
1264 columnName = orderByColumn.substring(
1265 orderByColumn.indexOf('.') + 1, spacePos);
1266 }
1267 ColumnMap column = dbMap.getTable(table).getColumn(columnName);
1268 if (column.getType() instanceof String)
1269 {
1270 if (spacePos == -1)
1271 {
1272 orderByClause.add(
1273 db.ignoreCaseInOrderBy(orderByColumn));
1274 }
1275 else
1276 {
1277 orderByClause.add(db.ignoreCaseInOrderBy(
1278 orderByColumn.substring(0, spacePos))
1279 + orderByColumn.substring(spacePos));
1280 }
1281 selectClause.add(
1282 db.ignoreCaseInOrderBy(table + '.' + columnName));
1283 }
1284 else
1285 {
1286 orderByClause.add(orderByColumn);
1287 }
1288 }
1289 }
1290
1291 // Limit the number of rows returned.
1292 int limit = criteria.getLimit();
1293 int offset = criteria.getOffset();
1294 String limitString = null;
1295 if (offset > 0 && db.supportsNativeOffset())
1296 {
1297 switch (db.getLimitStyle())
1298 {
1299 case DB.LIMIT_STYLE_MYSQL :
1300 limitString = new StringBuffer()
1301 .append(offset)
1302 .append(", ")
1303 .append(limit)
1304 .toString();
1305 break;
1306 case DB.LIMIT_STYLE_POSTGRES :
1307 limitString = new StringBuffer()
1308 .append(limit)
1309 .append(" offset ")
1310 .append(offset)
1311 .toString();
1312 break;
1313 }
1314
1315 // The following is now done in createQueryString() to enable this
1316 // method to be used as part of Criteria.toString() without altering
1317 // the criteria itself. The commented code is retained here to
1318 // make it easier to understand how the criteria is built into a
1319 // query.
1320
1321 // Now set the criteria's limit and offset to return the
1322 // full resultset since the results are limited on the
1323 // server.
1324 //criteria.setLimit(-1);
1325 //criteria.setOffset(0);
1326 }
1327 else if (limit > 0 && db.supportsNativeLimit()
1328 && db.getLimitStyle() != DB.LIMIT_STYLE_ORACLE)
1329 {
1330 limitString = String.valueOf(limit);
1331
1332 // The following is now done in createQueryString() to enable this
1333 // method to be used as part of Criteria.toString() without altering
1334 // the criteria itself. The commented code is retained here to
1335 // make it easier to understand how the criteria is built into a
1336 // query.
1337
1338 // Now set the criteria's limit to return the full
1339 // resultset since the results are limited on the server.
1340 //criteria.setLimit(-1);
1341 }
1342
1343 if (limitString != null)
1344 {
1345 query.setLimit(limitString);
1346 }
1347 return query;
1348 }
1349
1350 /***
1351 * Returns all results.
1352 *
1353 * @param criteria A Criteria.
1354 * @return List of Record objects.
1355 * @throws TorqueException Any exceptions caught during processing will be
1356 * rethrown wrapped into a TorqueException.
1357 */
1358 public static List doSelect(Criteria criteria) throws TorqueException
1359 {
1360 Connection con = null;
1361 List results = null;
1362
1363 try
1364 {
1365 con = Transaction.beginOptional(
1366 criteria.getDbName(),
1367 criteria.isUseTransaction());
1368 results = doSelect(criteria, con);
1369 Transaction.commit(con);
1370 }
1371 catch (Exception e)
1372 {
1373 Transaction.safeRollback(con);
1374 throwTorqueException(e);
1375 }
1376 return results;
1377 }
1378
1379 /***
1380 * Returns all results.
1381 *
1382 * @param criteria A Criteria.
1383 * @param con A Connection.
1384 * @return List of Record objects.
1385 * @throws TorqueException Any exceptions caught during processing will be
1386 * rethrown wrapped into a TorqueException.
1387 */
1388 public static List doSelect(Criteria criteria, Connection con)
1389 throws TorqueException
1390 {
1391 return executeQuery(
1392 createQueryString(criteria),
1393 criteria.getOffset(),
1394 criteria.getLimit(),
1395 criteria.isSingleRecord(),
1396 con);
1397 }
1398
1399 /***
1400 * Utility method which executes a given sql statement. This
1401 * method should be used for select statements only. Use
1402 * executeStatement for update, insert, and delete operations.
1403 *
1404 * @param queryString A String with the sql statement to execute.
1405 * @return List of Record objects.
1406 * @throws TorqueException Any exceptions caught during processing will be
1407 * rethrown wrapped into a TorqueException.
1408 */
1409 public static List executeQuery(String queryString) throws TorqueException
1410 {
1411 return executeQuery(queryString, Torque.getDefaultDB(), false);
1412 }
1413
1414 /***
1415 * Utility method which executes a given sql statement. This
1416 * method should be used for select statements only. Use
1417 * executeStatement for update, insert, and delete operations.
1418 *
1419 * @param queryString A String with the sql statement to execute.
1420 * @param dbName The database to connect to.
1421 * @return List of Record objects.
1422 * @throws TorqueException Any exceptions caught during processing will be
1423 * rethrown wrapped into a TorqueException.
1424 */
1425 public static List executeQuery(String queryString, String dbName)
1426 throws TorqueException
1427 {
1428 return executeQuery(queryString, dbName, false);
1429 }
1430
1431 /***
1432 * Method for performing a SELECT. Returns all results.
1433 *
1434 * @param queryString A String with the sql statement to execute.
1435 * @param dbName The database to connect to.
1436 * @param singleRecord Whether or not we want to select only a
1437 * single record.
1438 * @return List of Record objects.
1439 * @throws TorqueException Any exceptions caught during processing will be
1440 * rethrown wrapped into a TorqueException.
1441 */
1442 public static List executeQuery(
1443 String queryString,
1444 String dbName,
1445 boolean singleRecord)
1446 throws TorqueException
1447 {
1448 return executeQuery(queryString, 0, -1, dbName, singleRecord);
1449 }
1450
1451 /***
1452 * Method for performing a SELECT. Returns all results.
1453 *
1454 * @param queryString A String with the sql statement to execute.
1455 * @param singleRecord Whether or not we want to select only a
1456 * single record.
1457 * @param con A Connection.
1458 * @return List of Record objects.
1459 * @throws TorqueException Any exceptions caught during processing will be
1460 * rethrown wrapped into a TorqueException.
1461 */
1462 public static List executeQuery(
1463 String queryString,
1464 boolean singleRecord,
1465 Connection con)
1466 throws TorqueException
1467 {
1468 return executeQuery(queryString, 0, -1, singleRecord, con);
1469 }
1470
1471 /***
1472 * Method for performing a SELECT.
1473 *
1474 * @param queryString A String with the sql statement to execute.
1475 * @param start The first row to return.
1476 * @param numberOfResults The number of rows to return.
1477 * @param dbName The database to connect to.
1478 * @param singleRecord Whether or not we want to select only a
1479 * single record.
1480 * @return List of Record objects.
1481 * @throws TorqueException Any exceptions caught during processing will be
1482 * rethrown wrapped into a TorqueException.
1483 */
1484 public static List executeQuery(
1485 String queryString,
1486 int start,
1487 int numberOfResults,
1488 String dbName,
1489 boolean singleRecord)
1490 throws TorqueException
1491 {
1492 Connection db = null;
1493 List results = null;
1494 try
1495 {
1496 db = Torque.getConnection(dbName);
1497 // execute the query
1498 results = executeQuery(
1499 queryString,
1500 start,
1501 numberOfResults,
1502 singleRecord,
1503 db);
1504 }
1505 finally
1506 {
1507 Torque.closeConnection(db);
1508 }
1509 return results;
1510 }
1511
1512 /***
1513 * Method for performing a SELECT. Returns all results.
1514 *
1515 * @param queryString A String with the sql statement to execute.
1516 * @param start The first row to return.
1517 * @param numberOfResults The number of rows to return.
1518 * @param singleRecord Whether or not we want to select only a
1519 * single record.
1520 * @param con A Connection.
1521 * @return List of Record objects.
1522 * @throws TorqueException Any exceptions caught during processing will be
1523 * rethrown wrapped into a TorqueException.
1524 */
1525 public static List executeQuery(
1526 String queryString,
1527 int start,
1528 int numberOfResults,
1529 boolean singleRecord,
1530 Connection con)
1531 throws TorqueException
1532 {
1533 QueryDataSet qds = null;
1534 List results = Collections.EMPTY_LIST;
1535 try
1536 {
1537 // execute the query
1538 long startTime = System.currentTimeMillis();
1539 qds = new QueryDataSet(con, queryString);
1540 if (log.isDebugEnabled())
1541 {
1542 log.debug("Elapsed time="
1543 + (System.currentTimeMillis() - startTime) + " ms");
1544 }
1545 results = getSelectResults(
1546 qds, start, numberOfResults, singleRecord);
1547 }
1548 catch (Exception e)
1549 {
1550 throwTorqueException(e);
1551 }
1552 finally
1553 {
1554 if (qds != null)
1555 {
1556 try
1557 {
1558 qds.close();
1559 }
1560 catch (Exception ignored)
1561 {
1562 }
1563 }
1564 }
1565 return results;
1566 }
1567
1568 /***
1569 * Returns all records in a QueryDataSet as a List of Record
1570 * objects. Used for functionality like util.LargeSelect.
1571 *
1572 * @see #getSelectResults(QueryDataSet, int, int, boolean)
1573 * @param qds the QueryDataSet
1574 * @return a List of Record objects
1575 * @throws TorqueException Any exceptions caught during processing will be
1576 * rethrown wrapped into a TorqueException.
1577 */
1578 public static List getSelectResults(QueryDataSet qds)
1579 throws TorqueException
1580 {
1581 return getSelectResults(qds, 0, -1, false);
1582 }
1583
1584 /***
1585 * Returns all records in a QueryDataSet as a List of Record
1586 * objects. Used for functionality like util.LargeSelect.
1587 *
1588 * @see #getSelectResults(QueryDataSet, int, int, boolean)
1589 * @param qds the QueryDataSet
1590 * @param singleRecord
1591 * @return a List of Record objects
1592 * @throws TorqueException Any exceptions caught during processing will be
1593 * rethrown wrapped into a TorqueException.
1594 */
1595 public static List getSelectResults(QueryDataSet qds, boolean singleRecord)
1596 throws TorqueException
1597 {
1598 return getSelectResults(qds, 0, -1, singleRecord);
1599 }
1600
1601 /***
1602 * Returns numberOfResults records in a QueryDataSet as a List
1603 * of Record objects. Starting at record 0. Used for
1604 * functionality like util.LargeSelect.
1605 *
1606 * @see #getSelectResults(QueryDataSet, int, int, boolean)
1607 * @param qds the QueryDataSet
1608 * @param numberOfResults
1609 * @param singleRecord
1610 * @return a List of Record objects
1611 * @throws TorqueException Any exceptions caught during processing will be
1612 * rethrown wrapped into a TorqueException.
1613 */
1614 public static List getSelectResults(
1615 QueryDataSet qds,
1616 int numberOfResults,
1617 boolean singleRecord)
1618 throws TorqueException
1619 {
1620 List results = null;
1621 if (numberOfResults != 0)
1622 {
1623 results = getSelectResults(qds, 0, numberOfResults, singleRecord);
1624 }
1625 return results;
1626 }
1627
1628 /***
1629 * Returns numberOfResults records in a QueryDataSet as a List
1630 * of Record objects. Starting at record start. Used for
1631 * functionality like util.LargeSelect.
1632 *
1633 * @param qds The <code>QueryDataSet</code> to extract results
1634 * from.
1635 * @param start The index from which to start retrieving
1636 * <code>Record</code> objects from the data set.
1637 * @param numberOfResults The number of results to return (or
1638 * <code> -1</code> for all results).
1639 * @param singleRecord Whether or not we want to select only a
1640 * single record.
1641 * @return A <code>List</code> of <code>Record</code> objects.
1642 * @exception TorqueException If any <code>Exception</code> occurs.
1643 */
1644 public static List getSelectResults(
1645 QueryDataSet qds,
1646 int start,
1647 int numberOfResults,
1648 boolean singleRecord)
1649 throws TorqueException
1650 {
1651 List results = null;
1652 try
1653 {
1654 if (numberOfResults <= 0)
1655 {
1656 results = new ArrayList();
1657 qds.fetchRecords();
1658 }
1659 else
1660 {
1661 results = new ArrayList(numberOfResults);
1662 qds.fetchRecords(start, numberOfResults);
1663 }
1664 if (qds.size() > 1 && singleRecord)
1665 {
1666 handleMultipleRecords(qds);
1667 }
1668
1669 int startRecord = 0;
1670
1671 //Offset the correct number of people
1672 if (start > 0 && numberOfResults <= 0)
1673 {
1674 startRecord = start;
1675 }
1676
1677 // Return a List of Record objects.
1678 for (int i = startRecord; i < qds.size(); i++)
1679 {
1680 Record rec = qds.getRecord(i);
1681 results.add(rec);
1682 }
1683 }
1684 catch (Exception e)
1685 {
1686 throwTorqueException(e);
1687 }
1688 return results;
1689 }
1690
1691 /***
1692 * Helper method which returns the primary key contained
1693 * in the given Criteria object.
1694 *
1695 * @param criteria A Criteria.
1696 * @return ColumnMap if the Criteria object contains a primary
1697 * key, or null if it doesn't.
1698 * @throws TorqueException Any exceptions caught during processing will be
1699 * rethrown wrapped into a TorqueException.
1700 */
1701 private static ColumnMap getPrimaryKey(Criteria criteria)
1702 throws TorqueException
1703 {
1704 // Assume all the keys are for the same table.
1705 String key = (String) criteria.keys().nextElement();
1706
1707 String table = criteria.getTableName(key);
1708 ColumnMap pk = null;
1709
1710 if (!table.equals(""))
1711 {
1712 DatabaseMap dbMap = Torque.getDatabaseMap(criteria.getDbName());
1713 if (dbMap == null)
1714 {
1715 throw new TorqueException("dbMap is null");
1716 }
1717 if (dbMap.getTable(table) == null)
1718 {
1719 throw new TorqueException("dbMap.getTable() is null");
1720 }
1721
1722 ColumnMap[] columns = dbMap.getTable(table).getColumns();
1723
1724 for (int i = 0; i < columns.length; i++)
1725 {
1726 if (columns[i].isPrimaryKey())
1727 {
1728 pk = columns[i];
1729 break;
1730 }
1731 }
1732 }
1733 return pk;
1734 }
1735
1736 /***
1737 * Convenience method used to update rows in the DB. Checks if a
1738 * <i>single</i> int primary key is specified in the Criteria
1739 * object and uses it to perform the udpate. If no primary key is
1740 * specified an Exception will be thrown.
1741 * <p>
1742 * Use this method for performing an update of the kind:
1743 * <p>
1744 * "WHERE primary_key_id = an int"
1745 * <p>
1746 * To perform an update with non-primary key fields in the WHERE
1747 * clause use doUpdate(criteria, criteria).
1748 *
1749 * @param updateValues A Criteria object containing values used in
1750 * set clause.
1751 * @throws TorqueException Any exceptions caught during processing will be
1752 * rethrown wrapped into a TorqueException.
1753 */
1754 public static void doUpdate(Criteria updateValues) throws TorqueException
1755 {
1756 Connection con = null;
1757 try
1758 {
1759 con = Transaction.beginOptional(
1760 updateValues.getDbName(),
1761 updateValues.isUseTransaction());
1762 doUpdate(updateValues, con);
1763 Transaction.commit(con);
1764 }
1765 catch (TorqueException e)
1766 {
1767 Transaction.safeRollback(con);
1768 throw e;
1769 }
1770 }
1771
1772 /***
1773 * Convenience method used to update rows in the DB. Checks if a
1774 * <i>single</i> int primary key is specified in the Criteria
1775 * object and uses it to perform the udpate. If no primary key is
1776 * specified an Exception will be thrown.
1777 * <p>
1778 * Use this method for performing an update of the kind:
1779 * <p>
1780 * "WHERE primary_key_id = an int"
1781 * <p>
1782 * To perform an update with non-primary key fields in the WHERE
1783 * clause use doUpdate(criteria, criteria).
1784 *
1785 * @param updateValues A Criteria object containing values used in
1786 * set clause.
1787 * @param con A Connection.
1788 * @throws TorqueException Any exceptions caught during processing will be
1789 * rethrown wrapped into a TorqueException.
1790 */
1791 public static void doUpdate(Criteria updateValues, Connection con)
1792 throws TorqueException
1793 {
1794 ColumnMap pk = getPrimaryKey(updateValues);
1795 Criteria selectCriteria = null;
1796
1797 if (pk != null && updateValues.containsKey(pk.getFullyQualifiedName()))
1798 {
1799 selectCriteria = new Criteria(2);
1800 selectCriteria.put(pk.getFullyQualifiedName(),
1801 updateValues.remove(pk.getFullyQualifiedName()));
1802 }
1803 else
1804 {
1805 throw new TorqueException("No PK specified for database update");
1806 }
1807
1808 doUpdate(selectCriteria, updateValues, con);
1809 }
1810
1811 /***
1812 * Method used to update rows in the DB. Rows are selected based
1813 * on selectCriteria and updated using values in updateValues.
1814 * <p>
1815 * Use this method for performing an update of the kind:
1816 * <p>
1817 * WHERE some_column = some value AND could_have_another_column =
1818 * another value AND so on...
1819 *
1820 * @param selectCriteria A Criteria object containing values used in where
1821 * clause.
1822 * @param updateValues A Criteria object containing values used in set
1823 * clause.
1824 * @throws TorqueException Any exceptions caught during processing will be
1825 * rethrown wrapped into a TorqueException.
1826 */
1827 public static void doUpdate(Criteria selectCriteria, Criteria updateValues)
1828 throws TorqueException
1829 {
1830 Connection con = null;
1831 try
1832 {
1833 con = Transaction.beginOptional(selectCriteria.getDbName(),
1834 updateValues.isUseTransaction());
1835 doUpdate(selectCriteria, updateValues, con);
1836 Transaction.commit(con);
1837 }
1838 catch (TorqueException e)
1839 {
1840 Transaction.safeRollback(con);
1841 throw e;
1842 }
1843 }
1844
1845 /***
1846 * Method used to update rows in the DB. Rows are selected based
1847 * on selectCriteria and updated using values in updateValues.
1848 * <p>
1849 * Use this method for performing an update of the kind:
1850 * <p>
1851 * WHERE some_column = some value AND could_have_another_column =
1852 * another value AND so on.
1853 *
1854 * @param selectCriteria A Criteria object containing values used in where
1855 * clause.
1856 * @param updateValues A Criteria object containing values used in set
1857 * clause.
1858 * @param con A Connection.
1859 * @throws TorqueException Any exceptions caught during processing will be
1860 * rethrown wrapped into a TorqueException.
1861 */
1862 public static void doUpdate(
1863 Criteria selectCriteria,
1864 Criteria updateValues,
1865 Connection con)
1866 throws TorqueException
1867 {
1868 DB db = Torque.getDB(selectCriteria.getDbName());
1869 DatabaseMap dbMap = Torque.getDatabaseMap(selectCriteria.getDbName());
1870
1871 // Set up a list of required tables. StringStack.add()
1872 // only adds element if it is unique.
1873 HashSet tables = new HashSet();
1874 Iterator it = selectCriteria.keySet().iterator();
1875 while (it.hasNext())
1876 {
1877 tables.add(selectCriteria.getTableName((String) it.next()));
1878 }
1879
1880 Iterator tabIt = tables.iterator();
1881 while (tabIt.hasNext())
1882 {
1883 String tab = (String) tabIt.next();
1884 KeyDef kd = new KeyDef();
1885 HashSet whereClause = new HashSet();
1886 DatabaseMap tempDbMap = dbMap;
1887
1888 ColumnMap[] columnMaps = tempDbMap.getTable(tab).getColumns();
1889 for (int j = 0; j < columnMaps.length; j++)
1890 {
1891 ColumnMap colMap = columnMaps[j];
1892 if (colMap.isPrimaryKey())
1893 {
1894 kd.addAttrib(colMap.getColumnName());
1895 }
1896 String key = new StringBuffer(colMap.getTableName())
1897 .append('.')
1898 .append(colMap.getColumnName())
1899 .toString();
1900 if (selectCriteria.containsKey(key))
1901 {
1902 if (selectCriteria
1903 .getComparison(key)
1904 .equals(Criteria.CUSTOM))
1905 {
1906 whereClause.add(selectCriteria.getString(key));
1907 }
1908 else
1909 {
1910 whereClause.add(
1911 SqlExpression.build(
1912 colMap.getColumnName(),
1913 selectCriteria.getValue(key),
1914 selectCriteria.getComparison(key),
1915 selectCriteria.isIgnoreCase(),
1916 db));
1917 }
1918 }
1919 }
1920 TableDataSet tds = null;
1921 try
1922 {
1923 // Get affected records.
1924 tds = new TableDataSet(con, tab, kd);
1925 String sqlSnippet = StringUtils.join(whereClause.iterator(), " AND ");
1926 if (log.isDebugEnabled())
1927 {
1928 log.debug("BasePeer.doUpdate: whereClause=" + sqlSnippet);
1929 }
1930 tds.where(sqlSnippet);
1931 tds.fetchRecords();
1932
1933 if (tds.size() > 1 && selectCriteria.isSingleRecord())
1934 {
1935 handleMultipleRecords(tds);
1936 }
1937 for (int j = 0; j < tds.size(); j++)
1938 {
1939 Record rec = tds.getRecord(j);
1940 BasePeer.insertOrUpdateRecord(rec, tab, updateValues);
1941 }
1942 }
1943 catch (Exception e)
1944 {
1945 throwTorqueException(e);
1946 }
1947 finally
1948 {
1949 if (tds != null)
1950 {
1951 try
1952 {
1953 tds.close();
1954 }
1955 catch (Exception e)
1956 {
1957 throwTorqueException(e);
1958 }
1959 }
1960 }
1961 }
1962 }
1963
1964 /***
1965 * Utility method which executes a given sql statement. This
1966 * method should be used for update, insert, and delete
1967 * statements. Use executeQuery() for selects.
1968 *
1969 * @param stmt A String with the sql statement to execute.
1970 * @return The number of rows affected.
1971 * @throws TorqueException Any exceptions caught during processing will be
1972 * rethrown wrapped into a TorqueException.
1973 */
1974 public static int executeStatement(String stmt) throws TorqueException
1975 {
1976 return executeStatement(stmt, Torque.getDefaultDB());
1977 }
1978
1979 /***
1980 * Utility method which executes a given sql statement. This
1981 * method should be used for update, insert, and delete
1982 * statements. Use executeQuery() for selects.
1983 *
1984 * @param stmt A String with the sql statement to execute.
1985 * @param dbName Name of database to connect to.
1986 * @return The number of rows affected.
1987 * @throws TorqueException Any exceptions caught during processing will be
1988 * rethrown wrapped into a TorqueException.
1989 */
1990 public static int executeStatement(String stmt, String dbName)
1991 throws TorqueException
1992 {
1993 Connection db = null;
1994 int rowCount = -1;
1995 try
1996 {
1997 db = Torque.getConnection(dbName);
1998 rowCount = executeStatement(stmt, db);
1999 }
2000 finally
2001 {
2002 Torque.closeConnection(db);
2003 }
2004 return rowCount;
2005 }
2006
2007 /***
2008 * Utility method which executes a given sql statement. This
2009 * method should be used for update, insert, and delete
2010 * statements. Use executeQuery() for selects.
2011 *
2012 * @param stmt A String with the sql statement to execute.
2013 * @param con A Connection.
2014 * @return The number of rows affected.
2015 * @throws TorqueException Any exceptions caught during processing will be
2016 * rethrown wrapped into a TorqueException.
2017 */
2018 public static int executeStatement(String stmt, Connection con)
2019 throws TorqueException
2020 {
2021 int rowCount = -1;
2022 Statement statement = null;
2023 try
2024 {
2025 statement = con.createStatement();
2026 rowCount = statement.executeUpdate(stmt);
2027 }
2028 catch (SQLException e)
2029 {
2030 throw new TorqueException(e);
2031 }
2032 finally
2033 {
2034 if (statement != null)
2035 {
2036 try
2037 {
2038 statement.close();
2039 }
2040 catch (SQLException e)
2041 {
2042 throw new TorqueException(e);
2043 }
2044 }
2045 }
2046 return rowCount;
2047 }
2048
2049 /***
2050 * If the user specified that (s)he only wants to retrieve a
2051 * single record and multiple records are retrieved, this method
2052 * is called to handle the situation. The default behavior is to
2053 * throw an exception, but subclasses can override this method as
2054 * needed.
2055 *
2056 * @param ds The DataSet which contains multiple records.
2057 * @exception TorqueException Couldn't handle multiple records.
2058 */
2059 protected static void handleMultipleRecords(DataSet ds)
2060 throws TorqueException
2061 {
2062 throw new TorqueException("Criteria expected single Record and "
2063 + "Multiple Records were selected");
2064 }
2065
2066 /***
2067 * This method returns the MapBuilder specified in the
2068 * configuration file. By default, this is
2069 * org.apache.torque.util.db.map.TurbineMapBuilder.
2070 * FIXME! With the decoupled Torque there seem to be no
2071 * default map builder anymore.
2072 *
2073 * @return A MapBuilder.
2074 * @throws TorqueException Any exceptions caught during processing will be
2075 * rethrown wrapped into a TorqueException.
2076 * @deprecated you have to specify the name of the map builder!
2077 */
2078 public static MapBuilder getMapBuilder() throws TorqueException
2079 {
2080 return getMapBuilder(DEFAULT_MAP_BUILDER.trim());
2081 }
2082
2083 /***
2084 * This method returns the MapBuilder specified in the name
2085 * parameter. You should pass in the full path to the class, ie:
2086 * org.apache.torque.util.db.map.TurbineMapBuilder. The
2087 * MapBuilder instances are cached in this class for speed.
2088 *
2089 * @param name name of the MapBuilder
2090 * @return A MapBuilder, or null (and logs the error) if the
2091 * MapBuilder was not found.
2092 */
2093 public static MapBuilder getMapBuilder(String name)
2094 {
2095 try
2096 {
2097 MapBuilder mb = (MapBuilder) mapBuilders.get(name);
2098 // Use the 'double-check pattern' for syncing
2099 // caching of the MapBuilder.
2100 if (mb == null)
2101 {
2102 synchronized (mapBuilders)
2103 {
2104 mb = (MapBuilder) mapBuilders.get(name);
2105 if (mb == null)
2106 {
2107 mb = (MapBuilder) Class.forName(name).newInstance();
2108 // Cache the MapBuilder before it is built.
2109 mapBuilders.put(name, mb);
2110 }
2111 }
2112 }
2113
2114 // Build the MapBuilder in its own synchronized block to
2115 // avoid locking up the whole Hashtable while doing so.
2116 // Note that *all* threads need to do a sync check on isBuilt()
2117 // to avoid grabing an uninitialized MapBuilder. This, however,
2118 // is a relatively fast operation.
2119 synchronized (mb)
2120 {
2121 if (!mb.isBuilt())
2122 {
2123 try
2124 {
2125 mb.doBuild();
2126 }
2127 catch (Exception e)
2128 {
2129 // need to think about whether we'd want to remove
2130 // the MapBuilder from the cache if it can't be
2131 // built correctly...? pgo
2132 throw e;
2133 }
2134 }
2135 }
2136 return mb;
2137 }
2138 catch (Exception e)
2139 {
2140 // Have to catch possible exceptions because method is
2141 // used in initialization of Peers. Log the exception and
2142 // return null.
2143 String message =
2144 "BasePeer.MapBuilder failed trying to instantiate: " + name;
2145 log.error(message, e);
2146 }
2147 return null;
2148 }
2149
2150 /***
2151 * Performs a SQL <code>select</code> using a PreparedStatement.
2152 * Note: this method does not handle null criteria values.
2153 *
2154 * @param criteria
2155 * @param con
2156 * @return
2157 * @throws TorqueException Error performing database query.
2158 */
2159 public static List doPSSelect(Criteria criteria, Connection con)
2160 throws TorqueException
2161 {
2162 List v = null;
2163
2164 StringBuffer qry = new StringBuffer();
2165 List params = new ArrayList(criteria.size());
2166
2167 createPreparedStatement(criteria, qry, params);
2168
2169 PreparedStatement stmt = null;
2170 try
2171 {
2172 stmt = con.prepareStatement(qry.toString());
2173
2174 for (int i = 0; i < params.size(); i++)
2175 {
2176 Object param = params.get(i);
2177 if (param instanceof java.sql.Date)
2178 {
2179 stmt.setDate(i + 1, (java.sql.Date) param);
2180 }
2181 else if (param instanceof NumberKey)
2182 {
2183 stmt.setBigDecimal(i + 1,
2184 ((NumberKey) param).getBigDecimal());
2185 }
2186 else
2187 {
2188 stmt.setString(i + 1, param.toString());
2189 }
2190 }
2191
2192 QueryDataSet qds = null;
2193 try
2194 {
2195 qds = new QueryDataSet(stmt.executeQuery());
2196 v = getSelectResults(qds);
2197 }
2198 finally
2199 {
2200 if (qds != null)
2201 {
2202 qds.close();
2203 }
2204 }
2205 }
2206 catch (Exception e)
2207 {
2208 throwTorqueException(e);
2209 }
2210 finally
2211 {
2212 if (stmt != null)
2213 {
2214 try
2215 {
2216 stmt.close();
2217 }
2218 catch (SQLException e)
2219 {
2220 throw new TorqueException(e);
2221 }
2222 }
2223 }
2224 return v;
2225 }
2226
2227 /***
2228 * Do a Prepared Statement select according to the given criteria
2229 *
2230 * @param criteria
2231 * @return
2232 * @throws TorqueException Any exceptions caught during processing will be
2233 * rethrown wrapped into a TorqueException.
2234 */
2235 public static List doPSSelect(Criteria criteria) throws TorqueException
2236 {
2237 Connection con = Torque.getConnection(criteria.getDbName());
2238 List v = null;
2239
2240 try
2241 {
2242 v = doPSSelect(criteria, con);
2243 }
2244 finally
2245 {
2246 Torque.closeConnection(con);
2247 }
2248 return v;
2249 }
2250
2251 /***
2252 * Create a new PreparedStatement. It builds a string representation
2253 * of a query and a list of PreparedStatement parameters.
2254 *
2255 * @param criteria
2256 * @param queryString
2257 * @param params
2258 * @throws TorqueException Any exceptions caught during processing will be
2259 * rethrown wrapped into a TorqueException.
2260 */
2261 public static void createPreparedStatement(
2262 Criteria criteria,
2263 StringBuffer queryString,
2264 List params)
2265 throws TorqueException
2266 {
2267 DB db = Torque.getDB(criteria.getDbName());
2268 DatabaseMap dbMap = Torque.getDatabaseMap(criteria.getDbName());
2269
2270 Query query = new Query();
2271
2272 UniqueList selectModifiers = query.getSelectModifiers();
2273 UniqueList selectClause = query.getSelectClause();
2274 UniqueList fromClause = query.getFromClause();
2275 UniqueList whereClause = query.getWhereClause();
2276 UniqueList orderByClause = query.getOrderByClause();
2277
2278 UniqueList orderBy = criteria.getOrderByColumns();
2279 UniqueList select = criteria.getSelectColumns();
2280 Hashtable aliases = criteria.getAsColumns();
2281 UniqueList modifiers = criteria.getSelectModifiers();
2282
2283 for (int i = 0; i < modifiers.size(); i++)
2284 {
2285 selectModifiers.add(modifiers.get(i));
2286 }
2287
2288 for (int i = 0; i < select.size(); i++)
2289 {
2290 String columnName = (String) select.get(i);
2291 if (columnName.indexOf('.') == -1)
2292 {
2293 throwMalformedColumnNameException("select", columnName);
2294 }
2295 String tableName = null;
2296 selectClause.add(columnName);
2297 int parenPos = columnName.indexOf('(');
2298 if (parenPos == -1)
2299 {
2300 tableName = columnName.substring(0, columnName.indexOf('.'));
2301 }
2302 else
2303 {
2304 tableName =
2305 columnName.substring(parenPos + 1, columnName.indexOf('.'));
2306 // functions may contain qualifiers so only take the last
2307 // word as the table name.
2308 int lastSpace = tableName.lastIndexOf(' ');
2309 if (lastSpace != -1)
2310 {
2311 tableName = tableName.substring(lastSpace + 1);
2312 }
2313 }
2314 String tableName2 = criteria.getTableForAlias(tableName);
2315 if (tableName2 != null)
2316 {
2317 fromClause.add(new StringBuffer(tableName.length()
2318 + tableName2.length() + 1)
2319 .append(tableName2)
2320 .append(' ')
2321 .append(tableName)
2322 .toString());
2323 }
2324 else
2325 {
2326 fromClause.add(tableName);
2327 }
2328 }
2329
2330 Iterator it = aliases.keySet().iterator();
2331 while (it.hasNext())
2332 {
2333 String key = (String) it.next();
2334 selectClause.add((String) aliases.get(key) + " AS " + key);
2335 }
2336
2337 Iterator critKeys = criteria.keySet().iterator();
2338 while (critKeys.hasNext())
2339 {
2340 String key = (String) critKeys.next();
2341 Criteria.Criterion criterion = criteria.getCriterion(key);
2342 Criteria.Criterion[] someCriteria =
2343 criterion.getAttachedCriterion();
2344
2345 String table = null;
2346 for (int i = 0; i < someCriteria.length; i++)
2347 {
2348 String tableName = someCriteria[i].getTable();
2349 table = criteria.getTableForAlias(tableName);
2350 if (table != null)
2351 {
2352 fromClause.add(new StringBuffer(tableName.length()
2353 + table.length() + 1)
2354 .append(table)
2355 .append(' ')
2356 .append(tableName)
2357 .toString());
2358 }
2359 else
2360 {
2361 fromClause.add(tableName);
2362 table = tableName;
2363 }
2364
2365 boolean ignorCase = ((criteria.isIgnoreCase()
2366 || someCriteria[i].isIgnoreCase())
2367 && (dbMap
2368 .getTable(table)
2369 .getColumn(someCriteria[i].getColumn())
2370 .getType()
2371 instanceof String));
2372
2373 someCriteria[i].setIgnoreCase(ignorCase);
2374 }
2375
2376 criterion.setDB(db);
2377 StringBuffer sb = new StringBuffer();
2378 criterion.appendPsTo(sb, params);
2379 whereClause.add(sb.toString());
2380 }
2381
2382 List join = criteria.getJoinL();
2383 if (join != null)
2384 {
2385 for (int i = 0; i < join.size(); i++)
2386 {
2387 String join1 = (String) join.get(i);
2388 String join2 = (String) criteria.getJoinR().get(i);
2389 if (join1.indexOf('.') == -1)
2390 {
2391 throwMalformedColumnNameException("join", join1);
2392 }
2393 if (join2.indexOf('.') == -1)
2394 {
2395 throwMalformedColumnNameException("join", join2);
2396 }
2397
2398 String tableName = join1.substring(0, join1.indexOf('.'));
2399 String table = criteria.getTableForAlias(tableName);
2400 if (table != null)
2401 {
2402 fromClause.add(new StringBuffer(tableName.length()
2403 + table.length() + 1)
2404 .append(table)
2405 .append(' ')
2406 .append(tableName)
2407 .toString());
2408 }
2409 else
2410 {
2411 fromClause.add(tableName);
2412 }
2413
2414 int dot = join2.indexOf('.');
2415 tableName = join2.substring(0, dot);
2416 table = criteria.getTableForAlias(tableName);
2417 if (table != null)
2418 {
2419 fromClause.add(new StringBuffer(tableName.length()
2420 + table.length() + 1)
2421 .append(table)
2422 .append(' ')
2423 .append(tableName)
2424 .toString());
2425 }
2426 else
2427 {
2428 fromClause.add(tableName);
2429 table = tableName;
2430 }
2431
2432 boolean ignorCase = (criteria.isIgnoreCase()
2433 && (dbMap
2434 .getTable(table)
2435 .getColumn(join2.substring(dot + 1, join2.length()))
2436 .getType()
2437 instanceof String));
2438
2439 whereClause.add(
2440 SqlExpression.buildInnerJoin(join1, join2, ignorCase, db));
2441 }
2442 }
2443
2444 if (orderBy != null && orderBy.size() > 0)
2445 {
2446 // Check for each String/Character column and apply
2447 // toUpperCase().
2448 for (int i = 0; i < orderBy.size(); i++)
2449 {
2450 String orderByColumn = (String) orderBy.get(i);
2451 if (orderByColumn.indexOf('.') == -1)
2452 {
2453 throwMalformedColumnNameException("order by",
2454 orderByColumn);
2455 }
2456 String table =
2457 orderByColumn.substring(0, orderByColumn.indexOf('.'));
2458 // See if there's a space (between the column list and sort
2459 // order in ORDER BY table.column DESC).
2460 int spacePos = orderByColumn.indexOf(' ');
2461 String columnName;
2462 if (spacePos == -1)
2463 {
2464 columnName =
2465 orderByColumn.substring(orderByColumn.indexOf('.') + 1);
2466 }
2467 else
2468 {
2469 columnName = orderByColumn.substring(
2470 orderByColumn.indexOf('.') + 1,
2471 spacePos);
2472 }
2473 ColumnMap column = dbMap.getTable(table).getColumn(columnName);
2474 if (column.getType() instanceof String)
2475 {
2476 if (spacePos == -1)
2477 {
2478 orderByClause.add(
2479 db.ignoreCaseInOrderBy(orderByColumn));
2480 }
2481 else
2482 {
2483 orderByClause.add(db.ignoreCaseInOrderBy(
2484 orderByColumn.substring(0, spacePos))
2485 + orderByColumn.substring(spacePos));
2486 }
2487 selectClause.add(
2488 db.ignoreCaseInOrderBy(table + '.' + columnName));
2489 }
2490 else
2491 {
2492 orderByClause.add(orderByColumn);
2493 }
2494 }
2495 }
2496
2497 // Limit the number of rows returned.
2498 int limit = criteria.getLimit();
2499 int offset = criteria.getOffset();
2500 String limitString = null;
2501 if (offset > 0 && db.supportsNativeOffset()
2502 && db.getLimitStyle() != DB.LIMIT_STYLE_ORACLE)
2503 {
2504 switch (db.getLimitStyle())
2505 {
2506 case DB.LIMIT_STYLE_MYSQL :
2507 limitString = new StringBuffer()
2508 .append(offset)
2509 .append(", ")
2510 .append(limit)
2511 .toString();
2512 break;
2513 case DB.LIMIT_STYLE_POSTGRES :
2514 limitString = new StringBuffer()
2515 .append(limit)
2516 .append(" offset ")
2517 .append(offset)
2518 .toString();
2519 break;
2520 }
2521
2522 // Now set the criteria's limit and offset to return the
2523 // full resultset since the results are limited on the
2524 // server.
2525 criteria.setLimit(-1);
2526 criteria.setOffset(0);
2527 }
2528 else if (limit > 0 && db.supportsNativeLimit()
2529 && db.getLimitStyle() != DB.LIMIT_STYLE_ORACLE)
2530 {
2531 limitString = String.valueOf(limit);
2532
2533 // Now set the criteria's limit to return the full
2534 // resultset since the results are limited on the server.
2535 criteria.setLimit(-1);
2536 }
2537
2538 if (limitString != null)
2539 {
2540 switch (db.getLimitStyle())
2541 {
2542 /* Don't have a Sybase install to validate this against(dlr)
2543 case DB.LIMIT_STYLE_SYBASE:
2544 query.setRowcount(limitString);
2545 break;
2546 */
2547 default :
2548 query.setLimit(limitString);
2549 }
2550 }
2551
2552 String sql;
2553 if ((limit > 0 || offset > 0)
2554 && db.getLimitStyle() == DB.LIMIT_STYLE_ORACLE)
2555 {
2556 sql = createOracleLimitOffsetQuery(query, limit, offset);
2557 criteria.setLimit(-1);
2558 criteria.setOffset(0);
2559 }
2560 else
2561 {
2562 sql = query.toString();
2563 }
2564
2565 if (log.isDebugEnabled())
2566 {
2567 log.debug(sql);
2568 }
2569 queryString.append(sql);
2570 }
2571
2572 /***
2573 * Throws a TorqueException with the malformed column name error
2574 * message. The error message looks like this:<p>
2575 *
2576 * <code>
2577 * Malformed column name in Criteria [criteriaPhrase]:
2578 * '[columnName]' is not of the form 'table.column'
2579 * </code>
2580 *
2581 * @param criteriaPhrase a String, one of "select", "join", or "order by"
2582 * @param columnName a String containing the offending column name
2583 * @throws TorqueException Any exceptions caught during processing will be
2584 * rethrown wrapped into a TorqueException.
2585 */
2586 private static void throwMalformedColumnNameException(
2587 String criteriaPhrase,
2588 String columnName)
2589 throws TorqueException
2590 {
2591 throw new TorqueException("Malformed column name in Criteria "
2592 + criteriaPhrase
2593 + ": '"
2594 + columnName
2595 + "' is not of the form 'table.column'");
2596 }
2597 }
This page was automatically generated by Maven