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