View Javadoc

1   package org.apache.torque.util;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.Serializable;
23  import java.sql.Connection;
24  import java.sql.PreparedStatement;
25  import java.sql.SQLException;
26  import java.sql.Statement;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.HashSet;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  
35  import org.apache.commons.lang.StringUtils;
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  import org.apache.torque.Database;
39  import org.apache.torque.TooManyRowsException;
40  import org.apache.torque.Torque;
41  import org.apache.torque.TorqueException;
42  import org.apache.torque.adapter.DB;
43  import org.apache.torque.map.ColumnMap;
44  import org.apache.torque.map.DatabaseMap;
45  import org.apache.torque.map.MapBuilder;
46  import org.apache.torque.map.TableMap;
47  import org.apache.torque.oid.IdGenerator;
48  import org.apache.torque.om.NumberKey;
49  import org.apache.torque.om.ObjectKey;
50  import org.apache.torque.om.SimpleKey;
51  import org.apache.torque.om.StringKey;
52  
53  import com.workingdogs.village.Column;
54  import com.workingdogs.village.DataSet;
55  import com.workingdogs.village.DataSetException;
56  import com.workingdogs.village.KeyDef;
57  import com.workingdogs.village.QueryDataSet;
58  import com.workingdogs.village.Record;
59  import com.workingdogs.village.Schema;
60  import com.workingdogs.village.TableDataSet;
61  
62  /***
63   * This is the base class for all Peer classes in the system.  Peer
64   * classes are responsible for isolating all of the database access
65   * for a specific business object.  They execute all of the SQL
66   * against the database.  Over time this class has grown to include
67   * utility methods which ease execution of cross-database queries and
68   * the implementation of concrete Peers.
69   *
70   * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
71   * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
72   * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
73   * @author <a href="mailto:stephenh@chase3000.com">Stephen Haberman</a>
74   * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
75   * @author <a href="mailto:vido@ldh.org">Augustin Vidovic</a>
76   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
77   * @version $Id: BasePeer.java 750198 2009-03-04 22:34:43Z gmonroe $
78   */
79  public abstract class BasePeer
80          implements Serializable
81  {
82      /*** Constant criteria key to reference ORDER BY columns. */
83      public static final String ORDER_BY = "ORDER BY";
84  
85      /***
86       * Constant criteria key to remove Case Information from
87       * search/ordering criteria.
88       */
89      public static final String IGNORE_CASE = "IgNOrE cAsE";
90  
91      /*** Classes that implement this class should override this value. */
92      public static final String TABLE_NAME = "TABLE_NAME";
93  
94      /*** the log */
95      protected static final Log log = LogFactory.getLog(BasePeer.class);
96  
97      private static void throwTorqueException(Exception e)
98          throws TorqueException
99      {
100         if (e instanceof TorqueException)
101         {
102             throw (TorqueException) e;
103         }
104         else
105         {
106             throw new TorqueException(e);
107         }
108     }
109 
110     /***
111      * Sets up a Schema for a table.  This schema is then normally
112      * used as the argument for initTableColumns().
113      *
114      * @param tableName The name of the table.
115      * @return A Schema.
116      */
117     public static Schema initTableSchema(String tableName)
118     {
119         return initTableSchema(tableName, Torque.getDefaultDB());
120     }
121 
122     /***
123      * Sets up a Schema for a table.  This schema is then normally
124      * used as the argument for initTableColumns
125      *
126      * @param tableName The propery name for the database in the
127      * configuration file.
128      * @param dbName The name of the database.
129      * @return A Schema.
130      */
131     public static Schema initTableSchema(String tableName, String dbName)
132     {
133         Schema schema = null;
134         Connection con = null;
135 
136         try
137         {
138             con = Torque.getConnection(dbName);
139             schema = new Schema().schema(con, tableName);
140         }
141         catch (Exception e)
142         {
143             log.error(e);
144             throw new Error("Error in BasePeer.initTableSchema("
145                     + tableName
146                     + "): "
147                     + e.getMessage());
148         }
149         finally
150         {
151             Torque.closeConnection(con);
152         }
153         return schema;
154     }
155 
156     /***
157      * Creates a Column array for a table based on its Schema.
158      *
159      * @param schema A Schema object.
160      * @return A Column[].
161      */
162     public static Column[] initTableColumns(Schema schema)
163     {
164         Column[] columns = null;
165         try
166         {
167             int numberOfColumns = schema.numberOfColumns();
168             columns = new Column[numberOfColumns];
169             for (int i = 0; i < numberOfColumns; i++)
170             {
171                 columns[i] = schema.column(i + 1);
172             }
173         }
174         catch (Exception e)
175         {
176             log.error(e);
177             throw new Error(
178                 "Error in BasePeer.initTableColumns(): " + e.getMessage());
179         }
180         return columns;
181     }
182 
183     /***
184      * Convenience method to create a String array of column names.
185      *
186      * @param columns A Column[].
187      * @return A String[].
188      */
189     public static String[] initColumnNames(Column[] columns)
190     {
191         String[] columnNames = new String[columns.length];
192         for (int i = 0; i < columns.length; i++)
193         {
194             columnNames[i] = columns[i].name().toUpperCase();
195         }
196         return columnNames;
197     }
198 
199     /***
200      * Convenience method to create a String array of criteria keys.
201      *
202      * @param tableName Name of table.
203      * @param columnNames A String[].
204      * @return A String[].
205      */
206     public static String[] initCriteriaKeys(
207         String tableName,
208         String[] columnNames)
209     {
210         String[] keys = new String[columnNames.length];
211         for (int i = 0; i < columnNames.length; i++)
212         {
213             keys[i] = tableName + "." + columnNames[i].toUpperCase();
214         }
215         return keys;
216     }
217 
218     /***
219      * Convenience method that uses straight JDBC to delete multiple
220      * rows.  Village throws an Exception when multiple rows are
221      * deleted.
222      *
223      * @param con A Connection.
224      * @param table The table to delete records from.
225      * @param column The column in the where clause.
226      * @param value The value of the column.
227      * @throws TorqueException Any exceptions caught during processing will be
228      *         rethrown wrapped into a TorqueException.
229      */
230     public static void deleteAll(
231         Connection con,
232         String table,
233         String column,
234         int value)
235         throws TorqueException
236     {
237         Statement statement = null;
238         try
239         {
240             statement = con.createStatement();
241 
242             StringBuffer query = new StringBuffer();
243             query.append("DELETE FROM ")
244                 .append(table)
245                 .append(" WHERE ")
246                 .append(column)
247                 .append(" = ")
248                 .append(value);
249 
250             statement.executeUpdate(query.toString());
251         }
252         catch (SQLException e)
253         {
254             throw new TorqueException(e);
255         }
256         finally
257         {
258             if (statement != null)
259             {
260                 try
261                 {
262                     statement.close();
263                 }
264                 catch (SQLException e)
265                 {
266                     throw new TorqueException(e);
267                 }
268             }
269         }
270     }
271 
272     /***
273      * Convenience method that uses straight JDBC to delete multiple
274      * rows.  Village throws an Exception when multiple rows are
275      * deleted.  This method attempts to get the default database from
276      * the pool.
277      *
278      * @param table The table to delete records from.
279      * @param column The column in the where clause.
280      * @param value The value of the column.
281      * @throws TorqueException Any exceptions caught during processing will be
282      *         rethrown wrapped into a TorqueException.
283      */
284     public static void deleteAll(String table, String column, int value)
285         throws TorqueException
286     {
287         Connection con = null;
288         try
289         {
290             // Get a connection to the db.
291             con = Torque.getConnection(Torque.getDefaultDB());
292             deleteAll(con, table, column, value);
293         }
294         finally
295         {
296             Torque.closeConnection(con);
297         }
298     }
299 
300     /***
301      * Method to perform deletes based on values and keys in a
302      * Criteria.
303      *
304      * @param criteria The criteria to use.
305      * @throws TorqueException Any exceptions caught during processing will be
306      *         rethrown wrapped into a TorqueException.
307      * @deprecated This method causes unexpected results when joins are used.
308      *              Please use doDelete(Criteria, String).
309      */
310     public static void doDelete(Criteria criteria) throws TorqueException
311     {
312         doDelete(criteria, (String) null);
313     }
314 
315     /***
316      * Method to perform deletes based on values and keys in a
317      * Criteria.
318      * This method is protected because it may cause ambiguity between
319      * doDelete(Criteria,Connection) and this method. It will be made public
320      * once doDelete(Criteria, Connection) is removed.
321      *
322      * @param criteria The criteria to use.
323      * @param tableName the name of the table to delete records from.
324      *         If set to null, the name of the table(s) can be extracted from
325      *         the criteria, but this can cause unexpected results.
326      * @throws TorqueException Any exceptions caught during processing will be
327      *         rethrown wrapped into a TorqueException.
328      */
329     protected static void doDelete(Criteria criteria, String tableName) throws TorqueException
330     {
331         Connection con = null;
332         try
333         {
334             con = Transaction.beginOptional(
335                     criteria.getDbName(),
336                     criteria.isUseTransaction());
337             doDelete(criteria, tableName, con);
338             Transaction.commit(con);
339         }
340         catch (TorqueException e)
341         {
342             Transaction.safeRollback(con);
343             throw e;
344         }
345     }
346 
347     /***
348      * Method to perform deletes based on values and keys in a Criteria.
349      *
350      * @param criteria The criteria to use.
351      * @param con A Connection.
352      * @throws TorqueException Any exceptions caught during processing will be
353      *         rethrown wrapped into a TorqueException.
354      * @deprecated This method causes unexpected results when joins are used.
355      *              Please use doDelete(Criteria, String, Connection).
356      */
357     public static void doDelete(Criteria criteria, Connection con)
358         throws TorqueException
359     {
360         doDelete(criteria, null, con);
361     }
362 
363     /***
364      * Method to perform deletes based on values and keys in a Criteria.
365      *
366      * @param criteria The criteria to use.
367      * @param tableName the name of the table to delete records from.
368      *         If set to null, the name of the table(s) can be extracted from
369      *         the criteria, but this can cause unexpected results.
370      * @param con A Connection.
371      * @throws TorqueException Any exceptions caught during processing will be
372      *         rethrown wrapped into a TorqueException.
373      */
374     public static void doDelete(Criteria criteria, String tableName, Connection con)
375         throws TorqueException
376     {
377         String dbName = criteria.getDbName();
378         final DatabaseMap dbMap = Torque.getDatabaseMap(dbName);
379 
380         // This Callback adds all tables to the Table set which
381         // are referenced from a cascading criteria. As a result, all
382         // data that is referenced through foreign keys will also be
383         // deleted.
384         SQLBuilder.TableCallback tc = new SQLBuilder.TableCallback() {
385                 public void process (Set tables, String key, Criteria crit)
386                 {
387                     if (crit.isCascade())
388                     {
389                         // This steps thru all the columns in the database.
390                         TableMap[] tableMaps = dbMap.getTables();
391                         for (int i = 0; i < tableMaps.length; i++)
392                         {
393                             ColumnMap[] columnMaps = tableMaps[i].getColumns();
394 
395                             for (int j = 0; j < columnMaps.length; j++)
396                             {
397                                 // Only delete rows where the foreign key is
398                                 // also a primary key.  Other rows need
399                                 // updating, but that is not implemented.
400                                 if (columnMaps[j].isForeignKey()
401                                         && columnMaps[j].isPrimaryKey()
402                                         && key.equals(columnMaps[j].getRelatedName()))
403                                 {
404                                     tables.add(tableMaps[i].getName());
405                                     crit.add(columnMaps[j].getFullyQualifiedName(),
406                                             crit.getValue(key));
407                                 }
408                             }
409                         }
410                     }
411                 }
412             };
413 
414         Set tables;
415         if (tableName == null)
416         {
417             tables = SQLBuilder.getTableSet(criteria, tc);
418         }
419         else
420         {
421             tables = new HashSet(1);
422             tables.add(tableName);
423         }
424 
425         try
426         {
427             processTables(criteria, tables, con, new ProcessCallback() {
428                     public void process(String table, String dbName, Record rec)
429                         throws Exception
430                     {
431                         rec.markToBeDeleted();
432                         rec.save();
433                     }
434                 });
435         }
436         catch (Exception e)
437         {
438             throwTorqueException(e);
439         }
440     }
441 
442     /***
443      * Method to perform inserts based on values and keys in a
444      * Criteria.
445      * <p>
446      * If the primary key is auto incremented the data in Criteria
447      * will be inserted and the auto increment value will be returned.
448      * <p>
449      * If the primary key is included in Criteria then that value will
450      * be used to insert the row.
451      * <p>
452      * If no primary key is included in Criteria then we will try to
453      * figure out the primary key from the database map and insert the
454      * row with the next available id using util.db.IDBroker.
455      * <p>
456      * If no primary key is defined for the table the values will be
457      * inserted as specified in Criteria and -1 will be returned.
458      *
459      * @param criteria Object containing values to insert.
460      * @return An Object which is the id of the row that was inserted
461      * (if the table has a primary key) or null (if the table does not
462      * have a primary key).
463      * @throws TorqueException Any exceptions caught during processing will be
464      *         rethrown wrapped into a TorqueException.
465      */
466     public static ObjectKey doInsert(Criteria criteria) throws TorqueException
467     {
468         Connection con = null;
469         ObjectKey id = null;
470 
471         try
472         {
473             con = Transaction.beginOptional(
474                     criteria.getDbName(),
475                     criteria.isUseTransaction());
476             id = doInsert(criteria, con);
477             Transaction.commit(con);
478         }
479         catch (TorqueException e)
480         {
481             Transaction.safeRollback(con);
482             throw e;
483         }
484 
485         return id;
486     }
487 
488     /***
489      * Method to perform inserts based on values and keys in a
490      * Criteria.
491      * <p>
492      * If the primary key is auto incremented the data in Criteria
493      * will be inserted and the auto increment value will be returned.
494      * <p>
495      * If the primary key is included in Criteria then that value will
496      * be used to insert the row.
497      * <p>
498      * If no primary key is included in Criteria then we will try to
499      * figure out the primary key from the database map and insert the
500      * row with the next available id using util.db.IDBroker.
501      * <p>
502      * If no primary key is defined for the table the values will be
503      * inserted as specified in Criteria and null will be returned.
504      *
505      * @param criteria Object containing values to insert.
506      * @param con A Connection.
507      * @return An Object which is the id of the row that was inserted
508      * (if the table has a primary key) or null (if the table does not
509      * have a primary key).
510      * @throws TorqueException Any exceptions caught during processing will be
511      *         rethrown wrapped into a TorqueException.
512      */
513     public static ObjectKey doInsert(Criteria criteria, Connection con)
514         throws TorqueException
515     {
516         SimpleKey id = null;
517 
518         // Get the table name and method for determining the primary
519         // key value.
520         String table = null;
521         Iterator keys = criteria.keySet().iterator();
522         if (keys.hasNext())
523         {
524             table = criteria.getTableName((String) keys.next());
525         }
526         else
527         {
528             throw new TorqueException("Database insert attempted without "
529                     + "anything specified to insert");
530         }
531 
532         String dbName = criteria.getDbName();
533         Database database = Torque.getDatabase(dbName);
534         DatabaseMap dbMap = database.getDatabaseMap();
535         TableMap tableMap = dbMap.getTable(table);
536         Object keyInfo = tableMap.getPrimaryKeyMethodInfo();
537         IdGenerator keyGen
538                 = database.getIdGenerator(tableMap.getPrimaryKeyMethod());
539 
540         ColumnMap pk = getPrimaryKey(criteria);
541 
542         // If the keyMethod is SEQUENCE or IDBROKERTABLE, get the id
543         // before the insert.
544         if (keyGen != null && keyGen.isPriorToInsert())
545         {
546             // pk will be null if there is no primary key defined for the table
547             // we're inserting into.
548             if (pk != null && !criteria.containsKey(pk.getFullyQualifiedName()))
549             {
550                 id = getId(pk, keyGen, con, keyInfo);
551                 criteria.add(pk.getFullyQualifiedName(), id);
552             }
553         }
554 
555         // Use Village to perform the insert.
556         TableDataSet tds = null;
557         try
558         {
559             String tableName = SQLBuilder.getFullTableName(table, dbName);
560             tds = new TableDataSet(con, tableName);
561             Record rec = tds.addRecord();
562             // not the fully qualified name, insertOrUpdateRecord wants to use table as an index...
563             BasePeer.insertOrUpdateRecord(rec, table, dbName, criteria);
564         }
565         catch (DataSetException e)
566         {
567             throwTorqueException(e);
568         }
569         catch (SQLException e)
570         {
571             throwTorqueException(e);
572         }
573         catch (TorqueException e)
574         {
575             throwTorqueException(e);
576         }
577         finally
578         {
579             VillageUtils.close(tds);
580         }
581 
582         // If the primary key column is auto-incremented, get the id
583         // now.
584         if (keyGen != null && keyGen.isPostInsert())
585         {
586             id = getId(pk, keyGen, con, keyInfo);
587         }
588 
589         return id;
590     }
591 
592     /***
593      * Create an Id for insertion in the Criteria
594      *
595      * @param pk ColumnMap for the Primary key
596      * @param keyGen The Id Generator object
597      * @param con The SQL Connection to run the id generation under
598      * @param keyInfo KeyInfo Parameter from the Table map
599      *
600      * @return A simple Key representing the new Id value
601      * @throws TorqueException Possible errors get wrapped in here.
602      */
603     private static SimpleKey getId(ColumnMap pk, IdGenerator keyGen, Connection con, Object keyInfo)
604             throws TorqueException
605     {
606         SimpleKey id = null;
607 
608         try
609         {
610             if (pk != null && keyGen != null)
611             {
612                 if (pk.getType() instanceof Number)
613                 {
614                     id = new NumberKey(
615                             keyGen.getIdAsBigDecimal(con, keyInfo));
616                 }
617                 else
618                 {
619                     id = new StringKey(keyGen.getIdAsString(con, keyInfo));
620                 }
621             }
622         }
623         catch (Exception e)
624         {
625             throwTorqueException(e);
626         }
627         return id;
628     }
629 
630     /***
631      * Grouping of code used in both doInsert() and doUpdate()
632      * methods.  Sets up a Record for saving.
633      *
634      * @param rec A Record.
635      * @param table Name of table.
636      * @param criteria A Criteria.
637      * @throws TorqueException Any exceptions caught during processing will be
638      *         rethrown wrapped into a TorqueException.
639      */
640     private static void insertOrUpdateRecord(
641         Record rec,
642         String table,
643         String dbName,
644         Criteria criteria)
645         throws TorqueException
646     {
647         DatabaseMap dbMap = Torque.getDatabaseMap(dbName);
648 
649         ColumnMap[] columnMaps = dbMap.getTable(table).getColumns();
650         boolean shouldSave = false;
651         for (int j = 0; j < columnMaps.length; j++)
652         {
653             ColumnMap colMap = columnMaps[j];
654             String colName = colMap.getColumnName();
655             String key = new StringBuffer(colMap.getTableName())
656                     .append('.')
657                     .append(colName)
658                     .toString();
659             if (criteria.containsKey(key))
660             {
661                 try
662                 {
663                     VillageUtils.setVillageValue(criteria, key, rec, colName);
664                     shouldSave = true;
665                 }
666                 catch (Exception e)
667                 {
668                     throwTorqueException(e);
669                 }
670             }
671         }
672 
673         if (shouldSave)
674         {
675             try
676             {
677                 rec.save();
678             }
679             catch (Exception e)
680             {
681                 throwTorqueException(e);
682             }
683         }
684         else
685         {
686             throw new TorqueException("No changes to save");
687         }
688     }
689 
690     /***
691      * Method to create an SQL query for display only based on values in a
692      * Criteria.
693      *
694      * @param criteria A Criteria.
695      * @return the SQL query for display
696      * @exception TorqueException Trouble creating the query string.
697      */
698     static String createQueryDisplayString(Criteria criteria)
699         throws TorqueException
700     {
701         return createQuery(criteria).toString();
702     }
703 
704     /***
705      * Method to create an SQL query for actual execution based on values in a
706      * Criteria.
707      *
708      * @param criteria A Criteria.
709      * @return the SQL query for actual execution
710      * @exception TorqueException Trouble creating the query string.
711      */
712     public static String createQueryString(Criteria criteria)
713         throws TorqueException
714     {
715         Query query = createQuery(criteria);
716         return query.toString();
717     }
718 
719     /***
720      * Method to create an SQL query based on values in a Criteria.  Note that
721      * final manipulation of the limit and offset are performed when the query
722      * is actually executed.
723      *
724      * @param criteria A Criteria.
725      * @return the sql query
726      * @exception TorqueException Trouble creating the query string.
727      */
728     static Query createQuery(Criteria criteria)
729         throws TorqueException
730     {
731         return SQLBuilder.buildQueryClause(criteria, null, new SQLBuilder.QueryCallback() {
732                 public String process(Criteria.Criterion criterion, List params)
733                 {
734                     return criterion.toString();
735                 }
736             });
737     }
738 
739     /***
740      * Returns all results.
741      *
742      * @param criteria A Criteria.
743      * @return List of Record objects.
744      * @throws TorqueException Any exceptions caught during processing will be
745      *         rethrown wrapped into a TorqueException.
746      */
747     public static List doSelect(Criteria criteria) throws TorqueException
748     {
749         Connection con = null;
750         List results = null;
751 
752         try
753         {
754             con = Transaction.beginOptional(
755                     criteria.getDbName(),
756                     criteria.isUseTransaction());
757             results = doSelect(criteria, con);
758             Transaction.commit(con);
759         }
760         catch (TorqueException e)
761         {
762             Transaction.safeRollback(con);
763             throw e;
764         }
765         return results;
766     }
767 
768     /***
769      * Returns all results.
770      *
771      * @param criteria A Criteria.
772      * @param con A Connection.
773      * @return List of Record objects.
774      * @throws TorqueException Any exceptions caught during processing will be
775      *         rethrown wrapped into a TorqueException.
776      */
777     public static List doSelect(Criteria criteria, Connection con)
778         throws TorqueException
779     {
780         Query query = createQuery(criteria);
781         DB dbadapter = Torque.getDB(criteria.getDbName());
782 
783         // Call Village depending on the capabilities of the DB
784         return executeQuery(query.toString(),
785                 dbadapter.supportsNativeOffset() ? 0 : criteria.getOffset(),
786                 dbadapter.supportsNativeLimit() ? -1 : criteria.getLimit(),
787                 criteria.isSingleRecord(),
788                 con);
789     }
790 
791     /***
792      * Utility method which executes a given sql statement.  This
793      * method should be used for select statements only.  Use
794      * executeStatement for update, insert, and delete operations.
795      *
796      * @param queryString A String with the sql statement to execute.
797      * @return List of Record objects.
798      * @throws TorqueException Any exceptions caught during processing will be
799      *         rethrown wrapped into a TorqueException.
800      */
801     public static List executeQuery(String queryString) throws TorqueException
802     {
803         return executeQuery(queryString, Torque.getDefaultDB(), false);
804     }
805 
806     /***
807      * Utility method which executes a given sql statement.  This
808      * method should be used for select statements only.  Use
809      * executeStatement for update, insert, and delete operations.
810      *
811      * @param queryString A String with the sql statement to execute.
812      * @param dbName The database to connect to.
813      * @return List of Record objects.
814      * @throws TorqueException Any exceptions caught during processing will be
815      *         rethrown wrapped into a TorqueException.
816      */
817     public static List executeQuery(String queryString, String dbName)
818         throws TorqueException
819     {
820         return executeQuery(queryString, dbName, false);
821     }
822 
823     /***
824      * Method for performing a SELECT.  Returns all results.
825      *
826      * @param queryString A String with the sql statement to execute.
827      * @param dbName The database to connect to.
828      * @param singleRecord Whether or not we want to select only a
829      * single record.
830      * @return List of Record objects.
831      * @throws TorqueException Any exceptions caught during processing will be
832      *         rethrown wrapped into a TorqueException.
833      */
834     public static List executeQuery(
835         String queryString,
836         String dbName,
837         boolean singleRecord)
838         throws TorqueException
839     {
840         return executeQuery(queryString, 0, -1, dbName, singleRecord);
841     }
842 
843     /***
844      * Method for performing a SELECT.  Returns all results.
845      *
846      * @param queryString A String with the sql statement to execute.
847      * @param singleRecord Whether or not we want to select only a
848      * single record.
849      * @param con A Connection.
850      * @return List of Record objects.
851      * @throws TorqueException Any exceptions caught during processing will be
852      *         rethrown wrapped into a TorqueException.
853      */
854     public static List executeQuery(
855         String queryString,
856         boolean singleRecord,
857         Connection con)
858         throws TorqueException
859     {
860         return executeQuery(queryString, 0, -1, singleRecord, con);
861     }
862 
863     /***
864      * Method for performing a SELECT.
865      *
866      * @param queryString A String with the sql statement to execute.
867      * @param start The first row to return.
868      * @param numberOfResults The number of rows to return.
869      * @param dbName The database to connect to.
870      * @param singleRecord Whether or not we want to select only a
871      * single record.
872      * @return List of Record objects.
873      * @throws TorqueException Any exceptions caught during processing will be
874      *         rethrown wrapped into a TorqueException.
875      */
876     public static List executeQuery(
877         String queryString,
878         int start,
879         int numberOfResults,
880         String dbName,
881         boolean singleRecord)
882         throws TorqueException
883     {
884         Connection con = null;
885         List results = null;
886         try
887         {
888             con = Torque.getConnection(dbName);
889             // execute the query
890             results = executeQuery(
891                     queryString,
892                     start,
893                     numberOfResults,
894                     singleRecord,
895                     con);
896         }
897         finally
898         {
899             Torque.closeConnection(con);
900         }
901         return results;
902     }
903 
904     /***
905      * Method for performing a SELECT.  Returns all results.
906      *
907      * @param queryString A String with the sql statement to execute.
908      * @param start The first row to return.
909      * @param numberOfResults The number of rows to return.
910      * @param singleRecord Whether or not we want to select only a
911      * single record.
912      * @param con A Connection.
913      * @return List of Record objects.
914      * @throws TorqueException Any exceptions caught during processing will be
915      *         rethrown wrapped into a TorqueException.
916      */
917     public static List executeQuery(
918         String queryString,
919         int start,
920         int numberOfResults,
921         boolean singleRecord,
922         Connection con)
923         throws TorqueException
924     {
925         QueryDataSet qds = null;
926         List results = Collections.EMPTY_LIST;
927         try
928         {
929             // execute the query
930             long startTime = System.currentTimeMillis();
931             qds = new QueryDataSet(con, queryString);
932             if (log.isDebugEnabled())
933             {
934                 log.debug("Elapsed time="
935                         + (System.currentTimeMillis() - startTime) + " ms");
936             }
937             results = getSelectResults(
938                     qds, start, numberOfResults, singleRecord);
939         }
940         catch (DataSetException e)
941         {
942             throwTorqueException(e);
943         }
944         catch (SQLException e)
945         {
946             throwTorqueException(e);
947         }
948         finally
949         {
950             VillageUtils.close(qds);
951         }
952         return results;
953     }
954 
955     /***
956      * Returns all records in a QueryDataSet as a List of Record
957      * objects.  Used for functionality like util.LargeSelect.
958      *
959      * @see #getSelectResults(QueryDataSet, int, int, boolean)
960      * @param qds the QueryDataSet
961      * @return a List of Record objects
962      * @throws TorqueException Any exceptions caught during processing will be
963      *         rethrown wrapped into a TorqueException.
964      */
965     public static List getSelectResults(QueryDataSet qds)
966         throws TorqueException
967     {
968         return getSelectResults(qds, 0, -1, false);
969     }
970 
971     /***
972      * Returns all records in a QueryDataSet as a List of Record
973      * objects.  Used for functionality like util.LargeSelect.
974      *
975      * @see #getSelectResults(QueryDataSet, int, int, boolean)
976      * @param qds the QueryDataSet
977      * @param singleRecord
978      * @return a List of Record objects
979      * @throws TorqueException Any exceptions caught during processing will be
980      *         rethrown wrapped into a TorqueException.
981      */
982     public static List getSelectResults(QueryDataSet qds, boolean singleRecord)
983         throws TorqueException
984     {
985         return getSelectResults(qds, 0, -1, singleRecord);
986     }
987 
988     /***
989      * Returns numberOfResults records in a QueryDataSet as a List
990      * of Record objects.  Starting at record 0.  Used for
991      * functionality like util.LargeSelect.
992      *
993      * @see #getSelectResults(QueryDataSet, int, int, boolean)
994      * @param qds the QueryDataSet
995      * @param numberOfResults
996      * @param singleRecord
997      * @return a List of Record objects
998      * @throws TorqueException Any exceptions caught during processing will be
999      *         rethrown wrapped into a TorqueException.
1000      */
1001     public static List getSelectResults(
1002         QueryDataSet qds,
1003         int numberOfResults,
1004         boolean singleRecord)
1005         throws TorqueException
1006     {
1007         List results = null;
1008         if (numberOfResults != 0)
1009         {
1010             results = getSelectResults(qds, 0, numberOfResults, singleRecord);
1011         }
1012         return results;
1013     }
1014 
1015     /***
1016      * Returns numberOfResults records in a QueryDataSet as a List
1017      * of Record objects.  Starting at record start.  Used for
1018      * functionality like util.LargeSelect.
1019      *
1020      * @param qds The <code>QueryDataSet</code> to extract results
1021      * from.
1022      * @param start The index from which to start retrieving
1023      * <code>Record</code> objects from the data set.
1024      * @param numberOfResults The number of results to return (or
1025      * <code> -1</code> for all results).
1026      * @param singleRecord Whether or not we want to select only a
1027      * single record.
1028      * @return A <code>List</code> of <code>Record</code> objects.
1029      * @exception TorqueException If any <code>Exception</code> occurs.
1030      */
1031     public static List getSelectResults(
1032         QueryDataSet qds,
1033         int start,
1034         int numberOfResults,
1035         boolean singleRecord)
1036         throws TorqueException
1037     {
1038         List results = null;
1039         try
1040         {
1041             if (numberOfResults < 0)
1042             {
1043                 results = new ArrayList();
1044                 qds.fetchRecords();
1045             }
1046             else
1047             {
1048                 results = new ArrayList(numberOfResults);
1049                 qds.fetchRecords(start, numberOfResults);
1050             }
1051 
1052             int startRecord = 0;
1053 
1054             //Offset the correct number of records
1055             if (start > 0 && numberOfResults <= 0)
1056             {
1057                 startRecord = start;
1058             }
1059 
1060             // Return a List of Record objects.
1061             for (int i = startRecord; i < qds.size(); i++)
1062             {
1063                 Record rec = qds.getRecord(i);
1064                 results.add(rec);
1065             }
1066 
1067             if (results.size() > 1 && singleRecord)
1068             {
1069                 handleMultipleRecords(qds);
1070             }
1071         }
1072         catch (Exception e)
1073         {
1074             throwTorqueException(e);
1075         }
1076         return results;
1077     }
1078 
1079     /***
1080      * Helper method which returns the primary key contained
1081      * in the given Criteria object.
1082      *
1083      * @param criteria A Criteria.
1084      * @return ColumnMap if the Criteria object contains a primary
1085      *          key, or null if it doesn't.
1086      * @throws TorqueException Any exceptions caught during processing will be
1087      *         rethrown wrapped into a TorqueException.
1088      */
1089     private static ColumnMap getPrimaryKey(Criteria criteria)
1090         throws TorqueException
1091     {
1092         // Assume all the keys are for the same table.
1093         String key = (String) criteria.keys().nextElement();
1094 
1095         String table = criteria.getTableName(key);
1096         ColumnMap pk = null;
1097 
1098         if (!table.equals(""))
1099         {
1100             DatabaseMap dbMap = Torque.getDatabaseMap(criteria.getDbName());
1101             if (dbMap == null)
1102             {
1103                 throw new TorqueException("dbMap is null");
1104             }
1105             if (dbMap.getTable(table) == null)
1106             {
1107                 throw new TorqueException("dbMap.getTable() is null");
1108             }
1109 
1110             ColumnMap[] columns = dbMap.getTable(table).getColumns();
1111 
1112             for (int i = 0; i < columns.length; i++)
1113             {
1114                 if (columns[i].isPrimaryKey())
1115                 {
1116                     pk = columns[i];
1117                     break;
1118                 }
1119             }
1120         }
1121         return pk;
1122     }
1123 
1124     /***
1125      * Convenience method used to update rows in the DB.  Checks if a
1126      * <i>single</i> int primary key is specified in the Criteria
1127      * object and uses it to perform the udpate.  If no primary key is
1128      * specified an Exception will be thrown.
1129      * <p>
1130      * Use this method for performing an update of the kind:
1131      * <p>
1132      * "WHERE primary_key_id = an int"
1133      * <p>
1134      * To perform an update with non-primary key fields in the WHERE
1135      * clause use doUpdate(criteria, criteria).
1136      *
1137      * @param updateValues A Criteria object containing values used in
1138      *        set clause.
1139      * @throws TorqueException Any exceptions caught during processing will be
1140      *         rethrown wrapped into a TorqueException.
1141      */
1142     public static void doUpdate(Criteria updateValues) throws TorqueException
1143     {
1144         Connection con = null;
1145         try
1146         {
1147             con = Transaction.beginOptional(
1148                     updateValues.getDbName(),
1149                     updateValues.isUseTransaction());
1150             doUpdate(updateValues, con);
1151             Transaction.commit(con);
1152         }
1153         catch (TorqueException e)
1154         {
1155             Transaction.safeRollback(con);
1156             throw e;
1157         }
1158     }
1159 
1160     /***
1161      * Convenience method used to update rows in the DB.  Checks if a
1162      * <i>single</i> int primary key is specified in the Criteria
1163      * object and uses it to perform the udpate.  If no primary key is
1164      * specified an Exception will be thrown.
1165      * <p>
1166      * Use this method for performing an update of the kind:
1167      * <p>
1168      * "WHERE primary_key_id = an int"
1169      * <p>
1170      * To perform an update with non-primary key fields in the WHERE
1171      * clause use doUpdate(criteria, criteria).
1172      *
1173      * @param updateValues A Criteria object containing values used in
1174      * set clause.
1175      * @param con A Connection.
1176      * @throws TorqueException Any exceptions caught during processing will be
1177      *         rethrown wrapped into a TorqueException.
1178      */
1179     public static void doUpdate(Criteria updateValues, Connection con)
1180         throws TorqueException
1181     {
1182         ColumnMap pk = getPrimaryKey(updateValues);
1183         Criteria selectCriteria = null;
1184 
1185         if (pk != null && updateValues.containsKey(pk.getFullyQualifiedName()))
1186         {
1187             selectCriteria = new Criteria(2);
1188             selectCriteria.put(pk.getFullyQualifiedName(),
1189                 updateValues.remove(pk.getFullyQualifiedName()));
1190         }
1191         else
1192         {
1193             throw new TorqueException("No PK specified for database update");
1194         }
1195 
1196         doUpdate(selectCriteria, updateValues, con);
1197     }
1198 
1199     /***
1200      * Method used to update rows in the DB.  Rows are selected based
1201      * on selectCriteria and updated using values in updateValues.
1202      * <p>
1203      * Use this method for performing an update of the kind:
1204      * <p>
1205      * WHERE some_column = some value AND could_have_another_column =
1206      * another value AND so on...
1207      *
1208      * @param selectCriteria A Criteria object containing values used in where
1209      *        clause.
1210      * @param updateValues A Criteria object containing values used in set
1211      *        clause.
1212      * @throws TorqueException Any exceptions caught during processing will be
1213      *         rethrown wrapped into a TorqueException.
1214      */
1215     public static void doUpdate(Criteria selectCriteria, Criteria updateValues)
1216         throws TorqueException
1217     {
1218         Connection con = null;
1219         try
1220         {
1221             con = Transaction.beginOptional(
1222                     selectCriteria.getDbName(),
1223                     updateValues.isUseTransaction());
1224             doUpdate(selectCriteria, updateValues, con);
1225             Transaction.commit(con);
1226         }
1227         catch (TorqueException e)
1228         {
1229             Transaction.safeRollback(con);
1230             throw e;
1231         }
1232     }
1233 
1234     /***
1235      * Method used to update rows in the DB.  Rows are selected based
1236      * on criteria and updated using values in updateValues.
1237      * <p>
1238      * Use this method for performing an update of the kind:
1239      * <p>
1240      * WHERE some_column = some value AND could_have_another_column =
1241      * another value AND so on.
1242      *
1243      * @param criteria A Criteria object containing values used in where
1244      *        clause.
1245      * @param updateValues A Criteria object containing values used in set
1246      *        clause.
1247      * @param con A Connection.
1248      * @throws TorqueException Any exceptions caught during processing will be
1249      *         rethrown wrapped into a TorqueException.
1250      */
1251     public static void doUpdate(
1252         Criteria criteria,
1253         final Criteria updateValues,
1254         Connection con)
1255         throws TorqueException
1256     {
1257         Set tables = SQLBuilder.getTableSet(criteria, null);
1258 
1259         try
1260         {
1261             processTables(criteria, tables, con, new ProcessCallback() {
1262                     public void process (String table, String dbName, Record rec)
1263                         throws Exception
1264                     {
1265                         // Callback must be called with table name without Schema!
1266                         BasePeer.insertOrUpdateRecord(rec, table, dbName, updateValues);
1267                     }
1268                 });
1269         }
1270         catch (Exception e)
1271         {
1272             throwTorqueException(e);
1273         }
1274     }
1275 
1276     /***
1277      * Utility method which executes a given sql statement.  This
1278      * method should be used for update, insert, and delete
1279      * statements.  Use executeQuery() for selects.
1280      *
1281      * @param statementString A String with the sql statement to execute.
1282      * @return The number of rows affected.
1283      * @throws TorqueException Any exceptions caught during processing will be
1284      *         rethrown wrapped into a TorqueException.
1285      */
1286     public static int executeStatement(String statementString) throws TorqueException
1287     {
1288         return executeStatement(statementString, Torque.getDefaultDB());
1289     }
1290 
1291     /***
1292      * Utility method which executes a given sql statement.  This
1293      * method should be used for update, insert, and delete
1294      * statements.  Use executeQuery() for selects.
1295      *
1296      * @param statementString A String with the sql statement to execute.
1297      * @param dbName Name of database to connect to.
1298      * @return The number of rows affected.
1299      * @throws TorqueException Any exceptions caught during processing will be
1300      *         rethrown wrapped into a TorqueException.
1301      */
1302     public static int executeStatement(String statementString, String dbName)
1303         throws TorqueException
1304     {
1305         Connection con = null;
1306         int rowCount = -1;
1307         try
1308         {
1309             con = Torque.getConnection(dbName);
1310             rowCount = executeStatement(statementString, con);
1311         }
1312         finally
1313         {
1314             Torque.closeConnection(con);
1315         }
1316         return rowCount;
1317     }
1318 
1319     /***
1320      * Utility method which executes a given sql statement.  This
1321      * method should be used for update, insert, and delete
1322      * statements.  Use executeQuery() for selects.
1323      *
1324      * @param statementString A String with the sql statement to execute.
1325      * @param con A Connection.
1326      * @return The number of rows affected.
1327      * @throws TorqueException Any exceptions caught during processing will be
1328      *         rethrown wrapped into a TorqueException.
1329      */
1330     public static int executeStatement(String statementString, Connection con)
1331         throws TorqueException
1332     {
1333         int rowCount = -1;
1334         Statement statement = null;
1335         try
1336         {
1337             statement = con.createStatement();
1338             rowCount = statement.executeUpdate(statementString);
1339         }
1340         catch (SQLException e)
1341         {
1342             throw new TorqueException(e);
1343         }
1344         finally
1345         {
1346             if (statement != null)
1347             {
1348                 try
1349                 {
1350                     statement.close();
1351                 }
1352                 catch (SQLException e)
1353                 {
1354                     throw new TorqueException(e);
1355                 }
1356             }
1357         }
1358         return rowCount;
1359     }
1360 
1361     /***
1362      * If the user specified that (s)he only wants to retrieve a
1363      * single record and multiple records are retrieved, this method
1364      * is called to handle the situation.  The default behavior is to
1365      * throw an exception, but subclasses can override this method as
1366      * needed.
1367      *
1368      * @param ds The DataSet which contains multiple records.
1369      * @exception TooManyRowsException Couldn't handle multiple records.
1370      */
1371     protected static void handleMultipleRecords(DataSet ds)
1372         throws TooManyRowsException
1373     {
1374         throw new TooManyRowsException("Criteria expected single Record and "
1375                 + "Multiple Records were selected");
1376     }
1377 
1378     /***
1379      * This method returns the MapBuilder specified in the name
1380      * parameter.  You should pass in the full path to the class, ie:
1381      * org.apache.torque.util.db.map.TurbineMapBuilder.  The
1382      * MapBuilder instances are cached in the TorqueInstance for speed.
1383      *
1384      * @param name name of the MapBuilder
1385      * @return A MapBuilder, not null
1386      * @throws TorqueException if the Map Builder cannot be instantiated
1387      * @deprecated Use Torque.getMapBuilder(name) instead
1388      */
1389     public static MapBuilder getMapBuilder(String name)
1390         throws TorqueException
1391     {
1392         return Torque.getMapBuilder(name);
1393     }
1394 
1395     /***
1396      * Performs a SQL <code>select</code> using a PreparedStatement.
1397      * Note: this method does not handle null criteria values.
1398      *
1399      * @param criteria
1400      * @param con
1401      * @return a List of Record objects.
1402      * @throws TorqueException Error performing database query.
1403      */
1404     public static List doPSSelect(Criteria criteria, Connection con)
1405         throws TorqueException
1406     {
1407         List v = null;
1408 
1409         StringBuffer qry = new StringBuffer();
1410         List params = new ArrayList(criteria.size());
1411 
1412         createPreparedStatement(criteria, qry, params);
1413 
1414         PreparedStatement statement = null;
1415         try
1416         {
1417             statement = con.prepareStatement(qry.toString());
1418 
1419             for (int i = 0; i < params.size(); i++)
1420             {
1421                 Object param = params.get(i);
1422                 if (param instanceof java.sql.Date)
1423                 {
1424                     statement.setDate(i + 1, (java.sql.Date) param);
1425                 }
1426                 else if (param instanceof NumberKey)
1427                 {
1428                     statement.setBigDecimal(i + 1,
1429                         ((NumberKey) param).getBigDecimal());
1430                 }
1431                 else if (param instanceof Integer)
1432                 {
1433                     statement.setInt(i + 1, ((Integer) param).intValue());
1434                 }
1435                 else
1436                 {
1437                     statement.setString(i + 1, param.toString());
1438                 }
1439             }
1440 
1441             QueryDataSet qds = null;
1442             try
1443             {
1444                 qds = new QueryDataSet(statement.executeQuery());
1445                 v = getSelectResults(qds);
1446             }
1447             finally
1448             {
1449                 VillageUtils.close(qds);
1450             }
1451         }
1452         catch (DataSetException e)
1453         {
1454             throwTorqueException(e);
1455         }
1456         catch (SQLException e)
1457         {
1458             throwTorqueException(e);
1459         }
1460         finally
1461         {
1462             if (statement != null)
1463             {
1464                 try
1465                 {
1466                     statement.close();
1467                 }
1468                 catch (SQLException e)
1469                 {
1470                     throw new TorqueException(e);
1471                 }
1472             }
1473         }
1474         return v;
1475     }
1476 
1477     /***
1478      * Do a Prepared Statement select according to the given criteria
1479      *
1480      * @param criteria
1481      * @return a List of Record objects.
1482      * @throws TorqueException Any exceptions caught during processing will be
1483      *         rethrown wrapped into a TorqueException.
1484      */
1485     public static List doPSSelect(Criteria criteria) throws TorqueException
1486     {
1487         Connection con = Torque.getConnection(criteria.getDbName());
1488         List v = null;
1489 
1490         try
1491         {
1492             v = doPSSelect(criteria, con);
1493         }
1494         finally
1495         {
1496             Torque.closeConnection(con);
1497         }
1498         return v;
1499     }
1500 
1501     /***
1502      * Create a new PreparedStatement.  It builds a string representation
1503      * of a query and a list of PreparedStatement parameters.
1504      *
1505      * @param criteria
1506      * @param queryString
1507      * @param params
1508      * @throws TorqueException Any exceptions caught during processing will be
1509      *         rethrown wrapped into a TorqueException.
1510      */
1511     public static void createPreparedStatement(
1512         Criteria criteria,
1513         StringBuffer queryString,
1514         List params)
1515         throws TorqueException
1516     {
1517         Query query = SQLBuilder.buildQueryClause(criteria, params, new SQLBuilder.QueryCallback() {
1518                 public String process(Criteria.Criterion criterion, List params)
1519                 {
1520                     StringBuffer sb = new StringBuffer();
1521                     criterion.appendPsTo(sb, params);
1522                     return sb.toString();
1523                 }
1524             });
1525 
1526         String sql = query.toString();
1527         log.debug(sql);
1528 
1529         queryString.append(sql);
1530     }
1531 
1532     /***
1533      * Checks all columns in the criteria to see whether
1534      * booleanchar and booleanint columns are queried with a boolean.
1535      * If yes, the query values are mapped onto values the database
1536      * does understand, i.e. 0 and 1 for booleanints and N and Y for
1537      * booleanchar columns.
1538      *
1539      * @param criteria The criteria to be checked for booleanint and booleanchar
1540      *        columns.
1541      * @param defaultTableMap the table map to be used if the table name is
1542      *        not given in a column.
1543      * @throws TorqueException if the database map for the criteria cannot be
1544      *         retrieved.
1545      */
1546     public static void correctBooleans(
1547             Criteria criteria,
1548             TableMap defaultTableMap)
1549         throws TorqueException
1550     {
1551         Iterator keyIt = criteria.keySet().iterator();
1552         while (keyIt.hasNext())
1553         {
1554             String key = (String) keyIt.next();
1555             String columnName;
1556             TableMap tableMap = null;
1557             int dotPosition = key.lastIndexOf(".");
1558             if (dotPosition == -1)
1559             {
1560                 columnName = key;
1561                 tableMap = defaultTableMap;
1562             }
1563             else
1564             {
1565                 columnName = key.substring(dotPosition + 1);
1566                 String tableName = key.substring(0, dotPosition);
1567                 String databaseName = criteria.getDbName();
1568                 if (databaseName == null)
1569                 {
1570                     databaseName = Torque.getDefaultDB();
1571                 }
1572                 DatabaseMap databaseMap = Torque.getDatabaseMap(databaseName);
1573                 if (databaseMap != null)
1574                 {
1575                     tableMap = databaseMap.getTable(tableName);
1576                 }
1577                 if (tableMap == null)
1578                 {
1579                     // try aliases
1580                     Map aliases = criteria.getAliases();
1581                     if (aliases.get(tableName) != null)
1582                     {
1583                         tableName = (String) aliases.get(tableName);
1584                         tableMap = databaseMap.getTable(tableName);
1585                     }
1586                 }
1587             }
1588             if (tableMap == null)
1589             {
1590                 // no description of table available, do not modify anything
1591                 continue;
1592             }
1593 
1594             ColumnMap columnMap = tableMap.getColumn(columnName);
1595             if (columnMap != null)
1596             {
1597                 if ("BOOLEANINT".equals(columnMap.getTorqueType()))
1598                 {
1599                     Criteria.Criterion criterion = criteria.getCriterion(key);
1600                     replaceBooleanValues(
1601                             criterion,
1602                             new Integer(1),
1603                             new Integer(0));
1604                 }
1605                 else if ("BOOLEANCHAR".equals(columnMap.getTorqueType()))
1606                 {
1607                     Criteria.Criterion criterion = criteria.getCriterion(key);
1608                     replaceBooleanValues(criterion, "Y", "N");
1609                  }
1610             }
1611         }
1612     }
1613 
1614     /***
1615      * Replaces any Boolean value in the criterion and its attached Criterions
1616      * by trueValue if the Boolean equals <code>Boolean.TRUE</code>
1617      * and falseValue if the Boolean equals <code>Boolean.FALSE</code>.
1618      *
1619      * @param criterion the criterion to replace Boolean values in.
1620      * @param trueValue the value by which Boolean.TRUE should be replaced.
1621      * @param falseValue the value by which Boolean.FALSE should be replaced.
1622      */
1623     private static void replaceBooleanValues(
1624             Criteria.Criterion criterion,
1625             Object trueValue,
1626             Object falseValue)
1627     {
1628         // attachedCriterions also contains the criterion itself,
1629         // so no additional treatment is needed for the criterion itself.
1630         Criteria.Criterion[] attachedCriterions
1631             = criterion.getAttachedCriterion();
1632         for (int i = 0; i < attachedCriterions.length; ++i)
1633         {
1634             Object criterionValue
1635                     = attachedCriterions[i].getValue();
1636             if (criterionValue instanceof Boolean)
1637             {
1638                 Boolean booleanValue = (Boolean) criterionValue;
1639                 attachedCriterions[i].setValue(
1640                         Boolean.TRUE.equals(booleanValue)
1641                                 ? trueValue
1642                                 : falseValue);
1643             }
1644 
1645         }
1646 
1647     }
1648 
1649     /***
1650      * Process the result of a Table list generation.
1651      * This runs the statements onto the list of tables and
1652      * provides a callback hook to add functionality.
1653      *
1654      * This method should've been in SQLBuilder, but is uses the handleMultipleRecords callback thingie..
1655      *
1656      * @param crit The criteria
1657      * @param tables A set of Tables to run on
1658      * @param con The SQL Connection to run the statements on
1659      * @param pc A ProcessCallback object
1660      *
1661      * @throws Exception An Error occured (should be wrapped into TorqueException)
1662      */
1663     private static void processTables(Criteria crit, Set tables, Connection con, ProcessCallback pc)
1664             throws Exception
1665     {
1666         String dbName = crit.getDbName();
1667         DB db = Torque.getDB(dbName);
1668         DatabaseMap dbMap = Torque.getDatabaseMap(dbName);
1669 
1670         // create the statements for the tables
1671         for (Iterator it = tables.iterator(); it.hasNext();)
1672         {
1673             String table = (String) it.next();
1674             KeyDef kd = new KeyDef();
1675             Set whereClause = new HashSet();
1676 
1677             ColumnMap[] columnMaps = dbMap.getTable(table).getColumns();
1678 
1679             for (int j = 0; j < columnMaps.length; j++)
1680             {
1681                 ColumnMap colMap = columnMaps[j];
1682                 if (colMap.isPrimaryKey())
1683                 {
1684                     kd.addAttrib(colMap.getColumnName());
1685                 }
1686 
1687                 String key = new StringBuffer(colMap.getTableName())
1688                         .append('.')
1689                         .append(colMap.getColumnName())
1690                         .toString();
1691 
1692                 if (crit.containsKey(key))
1693                 {
1694                     if (crit
1695                             .getComparison(key)
1696                             .equals(Criteria.CUSTOM))
1697                     {
1698                         whereClause.add(crit.getString(key));
1699                     }
1700                     else
1701                     {
1702                         whereClause.add(
1703                                 SqlExpression.build(
1704                                         colMap.getColumnName(),
1705                                         crit.getValue(key),
1706                                         crit.getComparison(key),
1707                                         crit.isIgnoreCase(),
1708                                         db));
1709                     }
1710                 }
1711             }
1712 
1713             // Execute the statement for each table
1714             TableDataSet tds = null;
1715             try
1716             {
1717                 String tableName = SQLBuilder.getFullTableName(table, dbName);
1718 
1719                 // Get affected records.
1720                 tds = new TableDataSet(con, tableName, kd);
1721                 String sqlSnippet = StringUtils.join(whereClause.iterator(), " AND ");
1722 
1723                 if (log.isDebugEnabled())
1724                 {
1725                     log.debug("BasePeer: whereClause=" + sqlSnippet);
1726                 }
1727 
1728                 tds.where(sqlSnippet);
1729                 tds.fetchRecords();
1730 
1731                 if (tds.size() > 1 && crit.isSingleRecord())
1732                 {
1733                     handleMultipleRecords(tds);
1734                 }
1735 
1736                 for (int j = 0; j < tds.size(); j++)
1737                 {
1738                     Record rec = tds.getRecord(j);
1739 
1740                     if (pc != null)
1741                     {
1742                         // Table name _without_ schema!
1743                         pc.process(table, dbName, rec);
1744                     }
1745                 }
1746             }
1747             finally
1748             {
1749                 VillageUtils.close(tds);
1750             }
1751         }
1752     }
1753 
1754     /***
1755      * Inner Interface that defines the Callback method for
1756      * the Record Processing
1757      */
1758     protected interface ProcessCallback
1759     {
1760         void process (String table, String dbName, Record rec)
1761                 throws Exception;
1762     }
1763 }