EMMA Coverage Report (generated Sun May 02 20:42:29 CEST 2010)
[all classes][hu.netmind.beankeeper.db.impl]

COVERAGE SUMMARY FOR SOURCE FILE [GenericDatabase.java]

nameclass, %method, %block, %line, %
GenericDatabase.java100% (1/1)98%  (42/43)83%  (3112/3733)85%  (567.4/669)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class GenericDatabase100% (1/1)98%  (42/43)83%  (3112/3733)85%  (567.4/669)
getLimitStatement (String, Limits, List): String 0%   (0/1)0%   (0/2)0%   (0/1)
getCreateIndexName (Connection, String, String): String 100% (1/1)21%  (25/120)24%  (6/25)
checkAttribute (Set, String): void 100% (1/1)24%  (5/21)67%  (2/3)
executeUpdate (Connection, String): void 100% (1/1)33%  (23/69)41%  (6.1/15)
getSQLTypeName (int): String 100% (1/1)56%  (18/32)82%  (9/11)
remove (Connection, String, Map): TransactionStatistics 100% (1/1)71%  (91/129)70%  (19.7/28)
insert (Connection, String, Map): TransactionStatistics 100% (1/1)72%  (121/169)72%  (23.7/33)
save (Connection, String, Map, Map): TransactionStatistics 100% (1/1)78%  (169/217)76%  (29.7/39)
getExpression (Expression, List): String 100% (1/1)78%  (179/229)88%  (43/49)
getQuerySource (TableTerm, Set, List): String 100% (1/1)81%  (82/101)94%  (17/18)
search (Connection, QueryStatement, Limits, SearchResult): TransactionStatistics 100% (1/1)84%  (860/1023)85%  (139.2/164)
getSaveStatement (String, List, List, Map): String 100% (1/1)86%  (103/120)81%  (13/16)
getSQLType (Class): int 100% (1/1)88%  (112/128)94%  (29/31)
alterTable (Connection, String, List, List, Map, List): TransactionStatistics 100% (1/1)89%  (112/126)89%  (16/18)
getQuerySource (QueryStatement, List): String 100% (1/1)93%  (268/289)96%  (47/49)
ensureTable (Connection, String, Map, List, boolean): TransactionStatistics 100% (1/1)95%  (254/268)95%  (41/43)
<static initializer> 100% (1/1)100% (7/7)100% (2/2)
GenericDatabase (): void 100% (1/1)100% (12/12)100% (3/3)
addColumn (Connection, String, String, String): TransactionStatistics 100% (1/1)100% (26/26)100% (7/7)
containsReferenceTerm (List, ReferenceTerm): boolean 100% (1/1)100% (33/33)100% (7/7)
createIndexes (Connection, String, Map): TransactionStatistics 100% (1/1)100% (61/61)100% (14/14)
createTable (Connection, String, Map, List): TransactionStatistics 100% (1/1)100% (148/148)100% (23/23)
dropColumn (Connection, String, String): TransactionStatistics 100% (1/1)100% (25/25)100% (7/7)
dropTable (Connection, String): TransactionStatistics 100% (1/1)100% (31/31)100% (8/8)
getAddColumnStatement (String, String, String): String 100% (1/1)100% (17/17)100% (1/1)
getAttributeType (String, String): Class 100% (1/1)100% (8/8)100% (1/1)
getAttributes (String): Map 100% (1/1)100% (7/7)100% (1/1)
getCountStatement (String): String 100% (1/1)100% (11/11)100% (1/1)
getCreateIndexStatement (String, String, String, Class): String 100% (1/1)100% (25/25)100% (3/3)
getCreateTableStatement (Connection, String): String 100% (1/1)100% (9/9)100% (1/1)
getDropColumnStatement (String, String): String 100% (1/1)100% (13/13)100% (1/1)
getInsertStatement (String, List): String 100% (1/1)100% (88/88)100% (11/11)
getJavaValue (Object, int, Class): Object 100% (1/1)100% (12/12)100% (3/3)
getRemoveStatement (String, List): String 100% (1/1)100% (67/67)100% (9/9)
getSQLValue (Object): Object 100% (1/1)100% (14/14)100% (3/3)
getTableAttributeType (ResultSet): int 100% (1/1)100% (4/4)100% (1/1)
getTableAttributeTypes (Connection, String): HashMap 100% (1/1)100% (44/44)100% (10/10)
getTableDeclaration (String, String): String 100% (1/1)100% (15/15)100% (3/3)
prepareResultSet (ResultSet, Limits): void 100% (1/1)100% (1/1)100% (1/1)
prepareStatement (PreparedStatement, Limits): void 100% (1/1)100% (1/1)100% (1/1)
setAttributes (String, Map): void 100% (1/1)100% (7/7)100% (2/2)
transformExpression (Expression): Expression 100% (1/1)100% (2/2)100% (1/1)
transformTerms (List): List 100% (1/1)100% (2/2)100% (1/1)

1/**
2 * Copyright (C) 2006 NetMind Consulting Bt.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 3 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 */
18 
19package hu.netmind.beankeeper.db.impl;
20 
21import java.util.Map;
22import java.sql.Connection;
23import java.sql.PreparedStatement;
24import java.sql.SQLException;
25import java.util.Iterator;
26import java.util.ArrayList;
27import java.util.HashSet;
28import java.util.Set;
29import java.util.Map;
30import java.util.ArrayList;
31import java.util.Date;
32import java.util.Collections;
33import java.util.Collection;
34import java.util.List;
35import java.util.HashMap;
36import java.util.Stack;
37import java.util.LinkedList;
38import java.sql.Timestamp;
39import org.apache.log4j.Logger;
40import java.sql.ResultSet;
41import java.sql.DatabaseMetaData;
42import java.sql.Types;
43import java.sql.ResultSetMetaData;
44import hu.netmind.beankeeper.parser.*;
45import hu.netmind.beankeeper.common.StoreException;
46import hu.netmind.beankeeper.transaction.*;
47import hu.netmind.beankeeper.db.*;
48import hu.netmind.beankeeper.logging.AggregatorLogger;
49 
50/**
51 * This is a generic database implementation. It contains no optimization,
52 * and tries to be as generic with types and sql syntax as possible, this also
53 * means, it cannot handle limits and offsets, which causes <strong>all</strong>
54 * results to be returned, even with lazy lists.
55 * @author Brautigam Robert
56 * @version Revision: $Revision$
57 */
58public class GenericDatabase extends DatabaseBase implements Database
59{
60   private static Logger logger = Logger.getLogger(GenericDatabase.class);
61   private static Logger sqlLogger = Logger.getLogger("hu.netmind.beankeeper.sql");
62 
63   private AggregatorLogger aggregatorLogger = null; // Injected
64  
65   /**
66    * Save basic attribute information, so we have it ready when needed.
67    */
68   private Map tableAttributes = Collections.synchronizedMap(new HashMap());
69 
70   protected Map getAttributes(String tableName)
71   {
72      return (Map) tableAttributes.get(tableName.toLowerCase());
73   }
74 
75   protected Class getAttributeType(String tableName, String attributeName)
76   {
77      return (Class) getAttributes(tableName).get(attributeName.toLowerCase());
78   }
79 
80   private void setAttributes(String tableName, Map attributes)
81   {
82      tableAttributes.put(tableName,attributes);
83   }
84 
85   /**
86    * Make a save statement for given table, id and changed attributes.
87    * Override this method in a subclass for a non-generic behaviour.
88    * The attributes' placeholders must be in the same order as given
89    * by the attributeNames list.
90    * @param tableName The table to save attributes to.
91    * @param keyNames The keys of object to save (All object entries have keys).
92    * @param attributeNames The attributes that will change.
93    * the statement.
94    * @param keys Keys.
95    * @return An SQL save/update statement specific to database backend.
96    */
97   protected String getSaveStatement(String tableName, List keyNames, List attributeNames,
98         Map keys)
99   {
100      // Make a generic update statement
101      StringBuffer statement = new StringBuffer("update "+tableName+
102            " set ");
103      for ( int i=0; i<attributeNames.size(); i++ )
104         statement.append(attributeNames.get(i).toString()+"=?,");
105      statement.delete(statement.length()-1,statement.length());
106      if ( keyNames.size() > 0 )
107         statement.append(" where ");
108      for ( int i=0; i<keyNames.size(); i++ )
109      {
110         String keyName = keyNames.get(i).toString();
111         if ( keys.get(keyName) == null )
112         {
113            statement.append(keyName+" is null and ");
114            keyNames.remove(keyName);
115            i--;
116         } else 
117            statement.append(keyName+"=? and ");
118      }
119      statement.delete(statement.length()-5,statement.length());
120      sqlLogger.debug("preparing update statement: "+statement.toString());
121      // Return 
122      return statement.toString();
123   }
124   
125   /**
126    * Modifies an object already in database with given fields.
127    * @param tableName The table to save attributes to.
128    * @param id The id of object to save (All object entries have an id).
129    * @param attributes The attributes in form of name:value pairs.
130    */
131   protected TransactionStatistics save(Connection connection, String tableName, 
132         Map keys, Map attributes)
133   {
134      TransactionStatistics stats = new TransactionStatistics();
135      // Clear null values from attributes
136      attributes = new HashMap(attributes);
137      ArrayList nullArrayList = new ArrayList();
138      nullArrayList.add(null);
139      attributes.values().removeAll(nullArrayList);
140      // Make a generic insert statement
141      ArrayList attributeNames = new ArrayList(attributes.keySet());
142      ArrayList keyNames = new ArrayList(keys.keySet());
143      String statement = getSaveStatement(tableName,keyNames,attributeNames,keys);
144      // Prepare
145      PreparedStatement pstmt;
146      try
147      {
148         pstmt = connection.prepareStatement(statement);
149         int i=0;
150         for ( ; i<attributeNames.size(); i++ )
151         {
152            Object value = getSQLValue(attributes.get(attributeNames.get(i)));
153            Class type = getAttributeType(tableName,(String) attributeNames.get(i));
154            sqlLogger.debug("setting statement parameter #"+i+": "+value);
155            pstmt.setObject(i+1,value,getSQLType(type));
156         }
157         for ( ; i<keyNames.size()+attributeNames.size(); i++ )
158         {
159            Object value = getSQLValue(keys.get(keyNames.get(i-attributeNames.size())));
160            sqlLogger.debug("setting statement parameter #"+i+": "+value);
161            pstmt.setObject(i+1,value);
162         }
163      } catch ( Exception e ) {
164         throw new StoreException("cannot prepare statement: "+statement,e);
165      }
166      // Execute
167      try
168      {
169         if ( logger.isDebugEnabled() )
170            logger.debug("excuting update statement: "+statement);
171         long startTime = System.currentTimeMillis();
172         pstmt.executeUpdate();
173         long endTime = System.currentTimeMillis();
174         aggregatorLogger.log("Update statement execution",new int[] { (int) (endTime-startTime) });
175         stats.setUpdateCount(1);
176         stats.setUpdateTime(endTime-startTime);
177      } catch ( Exception e ) {
178         throw new StoreException("exception while sql update",e);
179      } finally {
180         try
181         {
182            pstmt.close();
183         } catch ( Exception e ) {
184            logger.debug("unable to close statement",e);
185         }
186      }
187      // Return
188      return stats;
189   }
190 
191   /**
192    * Make an insert statement for given table, id and attributes.
193    * Override this method in a subclass for a non-generic behaviour.
194    * The attributes' placeholders must be in the same order as given
195    * by the attributeNames list.
196    * @param tableName The table to insert attributes to.
197    * @param attributeNames The attributes that will be inserted.
198    * @return An SQL insert statement specific to database backend.
199    */
200   protected String getInsertStatement(String tableName, List attributeNames)
201   {
202      // Make a generic insert statement
203      StringBuffer statement = new StringBuffer("insert into "+tableName+
204            " (");
205      for ( int i=0; i<attributeNames.size(); i++ )
206         statement.append(attributeNames.get(i).toString()+",");
207      statement.delete(statement.length()-1,statement.length());
208      statement.append(") values (");
209      for ( int i=0; i<attributeNames.size(); i++ )
210         statement.append("?,");
211      statement.delete(statement.length()-1,statement.length());
212      statement.append(")");
213      sqlLogger.debug("preparing insert statement: "+statement.toString());
214      // Return
215      return statement.toString();
216   }
217 
218   /**
219    * Insert an object into the database.
220    * @param tableName The table to save attributes to.
221    * @param id The id of object to save (All object entries have an id).
222    * @param attributes The attributes in form of name:value pairs.
223    */
224   protected TransactionStatistics insert(Connection connection, String tableName, Map attributes)
225   {
226      TransactionStatistics stats = new TransactionStatistics();
227      // Clear null values from attributes
228      attributes = new HashMap(attributes);
229      ArrayList nullArrayList = new ArrayList();
230      nullArrayList.add(null);
231      attributes.values().removeAll(nullArrayList);
232      // Make a generic insert statement
233      ArrayList attributeNames = new ArrayList(attributes.keySet());
234      String statement = getInsertStatement(tableName, attributeNames);
235      // Execute
236      PreparedStatement pstmt;
237      try
238      {
239         pstmt = connection.prepareStatement(statement);
240         for ( int i=0; i<attributeNames.size(); i++ )
241         {
242            Object value = getSQLValue(attributes.get(attributeNames.get(i)));
243            Class type = getAttributeType(tableName,(String) attributeNames.get(i));
244            sqlLogger.debug("setting statement parameter #"+i+": "+value);
245            pstmt.setObject(i+1,value,getSQLType(type));
246         }
247      } catch ( Exception e ) {
248         throw new StoreException("cannot prepare statement: "+statement,e);
249      }
250      // Execute
251      try
252      {
253         if ( logger.isDebugEnabled() )
254            logger.debug("excuting insert statement: "+statement);
255         long startTime = System.currentTimeMillis();
256         pstmt.executeUpdate();
257         long endTime = System.currentTimeMillis();
258         aggregatorLogger.log("Insert statement execution",new int[] { (int) (endTime-startTime) });
259         stats.setInsertCount(1);
260         stats.setInsertTime(endTime-startTime);
261      } catch ( Exception e ) {
262         throw new StoreException("exception while sql insert",e);
263      } finally {
264         try
265         {
266            pstmt.close();
267         } catch ( Exception e ) {
268            logger.debug("unable to close statement",e);
269         }
270      }
271      // Return
272      return stats;
273   }
274 
275   /**
276    * Make a remove statement for given table and attributes.
277    * Override this method in a subclass for a non-generic behaviour.
278    * The attributes' placeholders must be in the same order as given
279    * by the attributeNames list.
280    * @param tableName The table to remove attributes from.
281    * @param attributeNames The attributes that will be search for by the remove.
282    * @return An SQL insert statement specific to database backend.
283    */
284   protected String getRemoveStatement(String tableName, List attributeNames)
285   {
286      // Make a generic delete statement
287      // Please note the paticularly funny delete-6 to handle
288      // empty lists and list ends.
289      StringBuffer statement = new StringBuffer("delete from "+tableName);
290      if ( !attributeNames.isEmpty() )
291         statement.append(" where ");
292      for ( int i=0; i<attributeNames.size(); i++ )
293         statement.append(attributeNames.get(i).toString()+"=?  and  ");
294      if ( !attributeNames.isEmpty() )
295         statement.delete(statement.length()-6,statement.length());
296      sqlLogger.debug("preparing delete statement: "+statement.toString());
297      // Return
298      return statement.toString();
299   }
300   
301   /**
302    * Remove an entry from database.
303    * @param tableName The table to remove object from.
304    * @param attributes The attributes which identify the object.
305    * Equality is assumed with each attribute and it's value.
306    */
307   protected TransactionStatistics remove(Connection connection, String tableName,
308         Map attributes)
309   {
310      TransactionStatistics stats = new TransactionStatistics();
311      // Clear null values from attributes
312      attributes = new HashMap(attributes);
313      ArrayList nullArrayList = new ArrayList();
314      nullArrayList.add(null);
315      attributes.values().removeAll(nullArrayList);
316      // Make statement
317      ArrayList attributeNames = new ArrayList(attributes.keySet());
318      String statement = getRemoveStatement(tableName, attributeNames);
319      // Prepare
320      PreparedStatement pstmt;
321      try
322      {
323         pstmt = connection.prepareStatement(statement);
324         for ( int i=0; i<attributeNames.size(); i++ )
325            pstmt.setObject(i+1,getSQLValue(attributes.get(attributeNames.get(i))));
326      } catch ( Exception e ) {
327         throw new StoreException("cannot prepare statement: "+statement,e);
328      }
329      // Execute
330      try
331      {
332         long startTime = System.currentTimeMillis();
333         pstmt.executeUpdate();
334         long endTime = System.currentTimeMillis();
335         aggregatorLogger.log("Remove statement execution",new int[] { (int) (endTime-startTime) });
336         stats.setDeleteCount(1);
337         stats.setDeleteTime(endTime-startTime);
338      } catch ( Exception e ) {
339         throw new StoreException("exception while sql delete",e);
340      } finally {
341         try
342         {
343            pstmt.close();
344         } catch ( Exception e ) {
345            logger.debug("unable to close statement",e);
346         }
347      }
348      // Return
349      return stats;
350   }
351 
352   /**
353    * Drop the table with given name.
354    * This method is not called directly, but from <code>ensureTable</code>.
355    * @param tableName The table to drop.
356    */
357   protected TransactionStatistics dropTable(Connection connection, String tableName)
358   {
359      TransactionStatistics stats = new TransactionStatistics();
360      // Create statement
361      String statement = "drop table "+tableName;
362      // Execute statement
363      long startTime = System.currentTimeMillis();
364      executeUpdate(connection,statement);
365      long endTime = System.currentTimeMillis();
366      stats.setSchemaCount(1);
367      stats.setSchemaTime(endTime-startTime);
368      return stats;
369   }
370 
371   /**
372    * Get the create table statement before the attributes part.
373    */
374   protected String getCreateTableStatement(Connection connection, String tableName)
375   {
376      return "create table "+tableName;
377   }
378 
379   /**
380    * Create table with given name, attribute types, and keys.
381    * This method is not called directly, but from <code>ensureTable</code>.
382    * @param tableName The table to create.
383    * @param attributeTypes The attribute names together with which
384    * java class they should hold.
385    */
386   protected TransactionStatistics createTable(Connection connection, String tableName,
387         Map attributeTypes, List keyAttributeNames)
388   {
389      TransactionStatistics stats = new TransactionStatistics();
390      // Create statement
391      StringBuffer statement = new StringBuffer(getCreateTableStatement(connection,tableName)+" (");
392      Iterator iterator = attributeTypes.entrySet().iterator();
393      while ( iterator.hasNext() )
394      {
395         Map.Entry entry = (Map.Entry) iterator.next();
396         statement.append(entry.getKey().toString()+" "+getSQLTypeName(getSQLType(((Class) entry.getValue())))+",");
397      }
398      if ( (keyAttributeNames!=null) && (keyAttributeNames.size() > 0) )
399      {
400         statement.append(" primary key (");
401         for ( int i=0; i<keyAttributeNames.size(); i++ )
402            statement.append(keyAttributeNames.get(i)+",");
403         statement.delete(statement.length()-1,statement.length());
404         statement.append(")  ");
405      }
406      statement.delete(statement.length()-1,statement.length());
407      statement.append(")");
408      // Execute statement
409      long startTime = System.currentTimeMillis();
410      executeUpdate(connection,statement.toString());
411      long endTime = System.currentTimeMillis();
412      stats.setSchemaCount(1);
413      stats.setSchemaTime(endTime-startTime);
414      // Create initial indexes (currently all attributes will be indexed)
415      // Do not create for reserved tables
416      if ( 
417           (!tableName.equalsIgnoreCase("tablemap")) &&
418           (!tableName.equalsIgnoreCase("nodes")) &&
419           (!tableName.equalsIgnoreCase("classes")) )
420         stats.add(createIndexes(connection, tableName, attributeTypes));
421      return stats;
422   }
423 
424   /**
425    * Get the statement to drop a column.
426    */
427   protected String getDropColumnStatement(String tableName, String columnName)
428   {
429      return "alter table "+tableName+" drop column "+columnName;
430   }
431 
432   /**
433    * Get the statement to add a column to a table.
434    */
435   protected String getAddColumnStatement(String tableName, String columnName, String columnType)
436   {
437      return "alter table "+tableName+" add column "+columnName+" "+columnType;
438   }
439 
440   /**
441    * Drop a column from a table.
442    * @param connection The connection object.
443    * @param tableName The table to drop column from.
444    * @param columnName The column to drop.
445    */
446   protected TransactionStatistics dropColumn(Connection connection, String tableName,
447         String columnName)
448   {
449      TransactionStatistics stats = new TransactionStatistics();
450      long startTime = System.currentTimeMillis();
451      executeUpdate(connection,getDropColumnStatement(tableName,columnName));
452      long endTime = System.currentTimeMillis();
453      stats.setSchemaCount(1);
454      stats.setSchemaTime(endTime-startTime);
455      return stats;
456   }
457 
458   /**
459    * Add a column to a table.
460    * @param connection The connection object.
461    * @param tableName The table to drop column from.
462    * @param columnName The column to create.
463    * @param columnType The column type to create.
464    */
465   protected TransactionStatistics addColumn(Connection connection, String tableName,
466         String columnName, String columnType)
467   {
468      TransactionStatistics stats = new TransactionStatistics();
469      long startTime = System.currentTimeMillis();
470      executeUpdate(connection,getAddColumnStatement(tableName,columnName,columnType));
471      long endTime = System.currentTimeMillis();
472      stats.setSchemaCount(1);
473      stats.setSchemaTime(endTime-startTime);
474      return stats;
475   }
476 
477   /**
478    * Alter the table. Implementation: Not all databases support
479    * altering multiple columns, so all columns are separately modified.
480    * @param connection The connection object.
481    * @param tableName The table name.
482    * @param removedAttributes Attribute names which should be removed.
483    * @param addedAttributes Attribute names which should be added.
484    * @param attributeTypes Types in form of Classes to given names.
485    */
486   protected TransactionStatistics alterTable(Connection connection, String tableName,
487         List removedAttributes, List addedAttributes, Map attributeTypes, List keyAttributeNames)
488   {
489      TransactionStatistics stats = new TransactionStatistics();
490      if ( (removedAttributes.size()==0) && (addedAttributes.size()==0) )
491      {
492         logger.debug("table '"+tableName+"' schema matches class, no modification required.");
493         return stats;
494      }
495      logger.debug("table layout mismatch for table '"+tableName+"': removed columns: "+removedAttributes+", added columns: "+addedAttributes);
496      // Assemble statements and execute them
497      for ( int i=0; i<removedAttributes.size(); i++ )
498      {
499         String attributeName = (String) removedAttributes.get(i);
500         stats.add(dropColumn(connection,tableName,attributeName));
501      }
502      for ( int i=0; i<addedAttributes.size(); i++ )
503      {
504         String attributeName = (String) addedAttributes.get(i);
505         String sqlTypeName = getSQLTypeName(getSQLType((Class)attributeTypes.get(addedAttributes.get(i))));
506         stats.add(addColumn(connection,tableName,attributeName,sqlTypeName));
507      }
508      // Create initial indexes (currently all new attributes will be indexed)
509      HashMap addedTypes = new HashMap();
510      for ( int i=0; i<addedAttributes.size(); i++ )
511      {
512         Object name = addedAttributes.get(i);
513         addedTypes.put(name,attributeTypes.get(name));
514      }
515      stats.add(createIndexes(connection, tableName, addedTypes));
516      return stats;
517   }
518 
519   /**
520    * Get index creation statement for a given table and field.
521    * @return The statement to use, or null, of no such index can be
522    * created.
523    */
524   protected String getCreateIndexStatement(String indexName,String tableName, String field,
525         Class fieldClass)
526   {
527      if ( fieldClass.equals(byte[].class) )
528         return null;
529      return "create index "+indexName+" on "+tableName+" ("+field+")";
530   }
531 
532   /**
533    * Get an unused index name.
534    */
535   protected String getCreateIndexName(Connection connection, String tableName,
536         String field)
537   {
538      try
539      {
540         // Get max length
541         DatabaseMetaData dmd = connection.getMetaData();
542         int maxTableNameLength = dmd.getMaxTableNameLength();
543         if ( maxTableNameLength == 0 )
544            maxTableNameLength = Integer.MAX_VALUE;
545         // Check whether trivial name is good enough
546         String result = tableName+"_idx_"+field;
547         if ( result.length() <= maxTableNameLength )
548            return result;
549         // Assemble all indexes to table
550         HashSet indexNames = new HashSet();
551         ResultSet rs = dmd.getIndexInfo(null,null,tableName,false,true);
552         while ( rs.next() )
553         {
554            String indexName = rs.getString("INDEX_NAME");
555            if ( indexName != null )
556               indexNames.add(indexName.toLowerCase());
557         }
558         rs.close();
559         // Create an index name which does not currently exist
560         if ( logger.isDebugEnabled() )
561            logger.debug("creating index name, existing indexes: "+indexNames);
562         result = result.substring(0,maxTableNameLength-4).toLowerCase();
563         int nameIndex = 1;
564         while ( indexNames.contains(result+nameIndex) )
565            nameIndex++;
566         // Return abbr. name
567         logger.debug("new index name: "+result+nameIndex);
568         return result+nameIndex;
569      } catch ( SQLException e ) {
570         throw new StoreException("Could not compute approriate index name.",e);
571      }
572   }
573 
574   /**
575    * Create indexes to the given attributes.
576    * @param connection The SQL connection.
577    * @param tableName The table to create indexes to.
578    * @param attributeTypes The attributes and their types to create indexes to.
579    */
580   protected TransactionStatistics createIndexes(Connection connection, String tableName,
581         Map attributeTypes)
582   {
583      TransactionStatistics stats = new TransactionStatistics();
584      Iterator entries = attributeTypes.entrySet().iterator();
585      while ( entries.hasNext() )
586      {
587         Map.Entry entry = (Map.Entry) entries.next();
588         String indexName = getCreateIndexName(connection,tableName,entry.getKey().toString());
589         String statement = getCreateIndexStatement(indexName,tableName,
590               entry.getKey().toString(), (Class) entry.getValue());
591         if ( statement != null )
592         {
593            long startTime = System.currentTimeMillis();
594            executeUpdate(connection,statement);
595            long endTime = System.currentTimeMillis();
596            stats.setSchemaCount(stats.getSchemaCount()+1);
597            stats.setSchemaTime(stats.getSchemaTime()+(endTime-startTime));
598         }
599      }
600      return stats;
601   }
602 
603   /**
604    * Get the data types of a given table.
605    * @return A map of names with the sql type number as value.
606    */
607   protected HashMap getTableAttributeTypes(Connection connection,
608         String tableName)
609      throws SQLException
610   {
611         DatabaseMetaData dmd = connection.getMetaData();
612         ResultSet rs = dmd.getColumns(null,null,tableName,null);
613         HashMap databaseAttributeTypes = new HashMap();
614         while ( rs.next() )
615         {
616            // The tablename was a wildcard, so make sure that we
617            // get the right table's column (thanks Daniel)
618            if ( rs.getString("TABLE_NAME").equalsIgnoreCase(tableName) )
619            {
620               int type = getTableAttributeType(rs);
621               databaseAttributeTypes.put(rs.getObject("COLUMN_NAME").toString().toLowerCase(),new Integer(type));
622            }
623         }
624         rs.close();
625         return databaseAttributeTypes;
626   }
627 
628   /**
629    * Override this method to get different types for attributes than
630    * the database reports.
631    */
632   protected int getTableAttributeType(ResultSet rs)
633      throws SQLException
634   {
635      return rs.getInt("DATA_TYPE");
636   }
637   
638   /**
639    * Ensure that table exists in database. Implementation does
640    * not check keys. If keys differ, this implementation will not correct
641    * the problem. Column renames are not detected, if a column is renamed,
642    * the old column will be dropped and a new column will be created.
643    * @param tableName The table to check.
644    * @param attributeTypes The attribute names together with which
645    * java class they should hold.
646    * @param keyAttributeNames The keys of table.
647    */
648   protected TransactionStatistics ensureTable(Connection connection, String tableName,
649         Map attributeTypes, List keyAttributeNames, boolean create)
650   {
651      TransactionStatistics stats = new TransactionStatistics();
652      try
653      {
654         if ( create )
655         {
656            // Query whether table exists
657            HashMap databaseAttributeTypes = getTableAttributeTypes(connection,tableName);
658            // Diff the database schema and attribute schema
659            logger.debug("comparing database schema with class, database has: "+databaseAttributeTypes+", class has: "+attributeTypes);
660            if ( databaseAttributeTypes.size() == 0 )
661            {
662               // No columns found, better create that table
663               logger.debug("table '"+tableName+"' did not exist, creating.");
664               stats.add(createTable(connection,tableName,attributeTypes,keyAttributeNames));
665            } else {
666               // Columns found, so make diff
667               // Note, that not only removed and added columns count, but
668               // also columns which types have changed, since that also
669               // means deleting and adding the column
670               ArrayList removedAttributes = new ArrayList(databaseAttributeTypes.keySet()); 
671               removedAttributes.removeAll(attributeTypes.keySet());
672               ArrayList addedAttributes = new ArrayList(attributeTypes.keySet());
673               addedAttributes.removeAll(databaseAttributeTypes.keySet());
674               ArrayList changedAttributes = new ArrayList(databaseAttributeTypes.keySet());
675               changedAttributes.retainAll(attributeTypes.keySet());
676               Iterator changedAttributesIterator = changedAttributes.iterator();
677               while ( changedAttributesIterator.hasNext() )
678               {
679                  String attribute = (String) changedAttributesIterator.next().toString();
680                  if ( getSQLTypeName(((Integer) databaseAttributeTypes.get(attribute)).intValue()).equals(
681                        getSQLTypeName(getSQLType((Class) attributeTypes.get(attribute)))) )
682                     changedAttributesIterator.remove(); // Remove if type matches
683               }
684               removedAttributes.addAll(changedAttributes);
685               addedAttributes.addAll(changedAttributes);
686               // If all columns are to be removed, drop the table and re-create
687               // it, or else first drop the old columns and add the new ones
688               databaseAttributeTypes.remove("persistence_id");
689               databaseAttributeTypes.remove("persistence_start");
690               databaseAttributeTypes.remove("persistence_end");
691               databaseAttributeTypes.remove("persistence_txend");
692               databaseAttributeTypes.remove("persistence_txstart");
693               databaseAttributeTypes.remove("persistence_txstartid");
694               databaseAttributeTypes.remove("persistence_txendid");
695               if ( (removedAttributes.size() == databaseAttributeTypes.size()) &&
696                     (removedAttributes.size()>0) )
697               {
698                  // Uh-oh, all attributes changed, re-create table
699                  logger.debug("table '"+tableName+"' will be re-created, because all "+
700                        "attributes changed apparently, removed: "+removedAttributes+", added: "+addedAttributes);
701                  stats.add(dropTable(connection,tableName));
702                  stats.add(createTable(connection,tableName,attributeTypes,keyAttributeNames));
703               } else if ( (removedAttributes.size()>0) || (addedAttributes.size()>0) ) {
704                  // Partial change, only change what is necessary
705                  logger.debug("table '"+tableName+"' has a partial change, removing: "+removedAttributes+", adding: "+addedAttributes);
706                  stats.add(alterTable(connection,tableName,removedAttributes,addedAttributes,attributeTypes, keyAttributeNames));
707               } else {
708                  logger.debug("nothing to change on table: "+tableName);
709               }
710            }
711         } else {
712            logger.debug("table is not to be created: "+tableName);
713         }
714         // Remember these attributes
715         setAttributes(tableName,attributeTypes);
716      } catch ( Exception e ) {
717         throw new StoreException("could not ensure table exists: "+tableName,e);
718      }
719      // Return
720      return stats;
721   }
722 
723   /**
724    * Get the limit component of statement, if it can be expressed in
725    * the current database with simple statement part.
726    * @param limits The limits to apply.
727    */
728   protected String getLimitStatement(String statement, Limits limits, List types)
729   {
730      return statement;
731   }
732 
733   /**
734    * Get the count statement for the given statement.
735    */
736   protected String getCountStatement(String stmt)
737   {
738      return "select count(*) from ("+stmt+") as cr";
739   }
740 
741   /**
742    * Get the table declaration for a select statment.
743    */
744   protected String getTableDeclaration(String tableName, String alias)
745   {
746      if ( alias == null )
747         return tableName;
748      else
749         return tableName+" as "+alias;
750   }
751 
752   /**
753    * Assemble the query columns of a given term.
754    */
755   protected String getQuerySource(TableTerm term, Set queryColumns, List types)
756   {
757      // Determine name
758      String name = term.getName();
759      // List all attributes one-by-one
760      StringBuffer result = new StringBuffer();
761      Map attributeTypes = getAttributes(term.getTableName());
762      if ( attributeTypes == null )
763         throw new StoreException("attributes types not present for table: "+term.getTableName()+", map: "+tableAttributes.keySet());
764      Iterator attributeEntryIterator =  attributeTypes.entrySet().iterator();
765      while ( attributeEntryIterator.hasNext() )
766      {
767         Map.Entry entry = (Map.Entry) attributeEntryIterator.next();
768         String attributeName = (String) entry.getKey();
769         Class attributeType = (Class) entry.getValue();
770         if ( ! attributeName.startsWith("persistence_") )
771         {
772            result.append(name+"."+attributeName+",");
773            types.add(attributeType);
774            checkAttribute(queryColumns,attributeName);
775         }
776      }
777      if ( (result.length()>0) && (result.charAt(result.length()-1)==',') )
778         result.deleteCharAt(result.length()-1);
779      return result.toString();
780   }
781  
782   /**
783    * Check the attribute whether it's already contained in the
784    * columns.
785    */
786   private void checkAttribute(Set queryColumns, String attributeName)
787   {
788      if ( ! queryColumns.add(attributeName) )
789         throw new StoreException("query has multiple columns with same name, try to add: "+attributeName+", attributes until now: "+queryColumns);
790   }
791   
792   /**
793    * Assemble the query columns of select statement.
794    * @param stmt The statement to get query source from.
795    * @param types The type list to fill in. Each queried column's
796    * type should be inserted into this list in order.
797    * @return The columns part of the select statement.
798    */
799   protected String getQuerySource(QueryStatement stmt, List types)
800   {
801      if ( logger.isDebugEnabled() )
802         logger.debug("computing query source from: "+stmt.getSelectTerms());
803      HashSet queryColumns = new HashSet();
804      StringBuffer querySource = new StringBuffer();
805      for ( int i=0; i<stmt.getSelectTerms().size(); i++ )
806      {
807         if ( querySource.length() > 0 )
808            querySource.append(",");
809         TableTerm term = (TableTerm) stmt.getSelectTerms().get(i);
810         if ( term instanceof ReferenceTerm )
811         {
812            logger.debug("adding reference term: "+term);
813            // This is specifically an attribute
814            ReferenceTerm refTerm = (ReferenceTerm) term;
815            querySource.append(refTerm.getExpression());
816            if ( refTerm.getColumnAlias() != null )
817            {
818               querySource.append(" as "+refTerm.getColumnAlias());
819               checkAttribute(queryColumns,refTerm.getColumnAlias());
820            } else {
821               checkAttribute(queryColumns,refTerm.getColumnName());
822            }
823            Class attributeType = getAttributeType(term.getTableName(),
824                  ((ReferenceTerm)term).getColumnName());
825            types.add(attributeType);
826         } else {
827            SpecifiedTableTerm specifiedTerm = 
828               stmt.getSpecifiedTerm(term); // Switch for specified term
829            if ( logger.isDebugEnabled() )
830               logger.debug("adding table term: "+specifiedTerm);
831            // This is a table
832            querySource.append(getQuerySource(specifiedTerm,queryColumns,types));
833            for ( int o=0; o<specifiedTerm.getRelatedLeftTerms().size(); o++ )
834            {
835               TableTerm leftTableTerm = 
836                  ((SpecifiedTableTerm.LeftjoinEntry) 
837                   specifiedTerm.getRelatedLeftTerms().get(o)).term;
838               String sourcePart = getQuerySource(leftTableTerm,queryColumns,types);
839               if ( sourcePart.length() > 0 )
840               {
841                  if ( querySource.length()>0 )
842                     querySource.append(",");
843                  querySource.append(sourcePart);
844               }
845            }
846         }
847      }
848      // Add persistence id/startserial/endserial
849      TableTerm firstTerm = null;
850      if ( stmt.getSelectTerms().size()>0 )
851         firstTerm = (TableTerm) stmt.getSelectTerms().get(0);
852      if ( (!"tablemap".equalsIgnoreCase(firstTerm.getTableName())) && 
853           (!"classes".equalsIgnoreCase(firstTerm.getTableName())) &&
854           (!"nodes".equalsIgnoreCase(firstTerm.getTableName())) &&
855           (stmt.getMode()!=QueryStatement.MODE_VIEW) )
856      {
857         // Add persistence id
858         if ( querySource.length()>0 )
859            querySource.append(",");
860         querySource.append(firstTerm.getName()+".persistence_id");
861         types.add(Long.class);
862         // Add serials
863         ArrayList terms = new ArrayList();
864         terms.add(firstTerm);
865         terms.addAll(stmt.getSpecifiedTerm(firstTerm).getRelatedLeftTerms());
866         for ( int i=0; i<terms.size(); i++ )
867         {
868            querySource.append(",");
869            querySource.append(firstTerm.getName()+".persistence_start as persistence_start"+i);
870            types.add(Long.class);
871            querySource.append(",");
872            querySource.append(firstTerm.getName()+".persistence_end as persistence_end"+i);
873            types.add(Long.class);
874         }
875      }
876      // Return
877      return querySource.toString();
878   }
879 
880   /**
881    * Transform the select terms.
882    */
883   protected List transformTerms(List terms)
884   {
885      return terms;
886   }
887 
888   /**
889    * Transform the expression. This method is called on each subsequent
890    * expressions too, so implementation does not have to be recursive.
891    * @param expr The expression to possibly transform.
892    * @return A transformed expression.
893    */
894   protected Expression transformExpression(Expression expr)
895   {
896      return expr;
897   }
898 
899   /**
900    * Checks whether a list of table terms contains a given reference term,
901    * but not by using the standard <code>equals()</code> method, but by
902    * comparing a reference term with own equality.
903    */
904   private boolean containsReferenceTerm(List terms, ReferenceTerm term)
905   {
906      for ( int i=0; i<terms.size(); i++ )
907      {
908         TableTerm objRaw = (TableTerm) terms.get(i);
909         if ( objRaw instanceof ReferenceTerm )
910         {
911            ReferenceTerm obj = (ReferenceTerm) objRaw;
912            if ( (obj.equals(term)) && (obj.getColumnName().equals(term.getColumnName())) )
913               return true;
914         }
915      }
916      return false;
917   }
918 
919   /**
920    * Walk the given expression tree, and produce and sql expression.
921    * @param expression The beankeeper expression to process.
922    * @param values All constants which have a placeholder in the sql
923    * expression will be appended to this list in order.
924    * @return An SQL expression matching the given beankeeper expression.
925    */
926   private String getExpression(Expression expression, List values)
927   {
928      if ( logger.isDebugEnabled() )
929         logger.debug("expression, that will be translated to sql: "+expression);
930      StringBuffer conditionPart = new StringBuffer();
931      Stack openNodes = new Stack(); // Used in traversing the expression tree
932      if ( expression != null )
933         openNodes.push(new ArrayList(transformExpression(expression)));
934      while ( ! openNodes.isEmpty() )
935      {
936         // Select the current expression part list
937         ArrayList parts = (ArrayList) openNodes.peek();
938         // If there are no parts, then return
939         if ( parts.size() == 0 )
940         {
941            openNodes.pop();
942            if ( ! openNodes.isEmpty() )
943               conditionPart.append(")"); // Close sub-expression
944            continue; // 20 goto 10
945         }
946         // Process the most left symbol
947         Object nextPart = parts.remove(0);
948         // There are basically 4 cases depending on the class of nextPart
949         if ( nextPart instanceof Expression )
950         {
951            // It is an expression, so start new block and push expression
952            // items
953            conditionPart.append("(");
954            openNodes.push(new ArrayList(transformExpression((Expression) nextPart)));
955         }
956         if ( nextPart instanceof String )
957         {
958            // It is an operator, so just append
959            conditionPart.append(" "+nextPart.toString()+" ");
960         }
961         if ( nextPart instanceof ConstantTerm )
962         {
963            // It is a constant, add ? and remember value
964            Object value = ((ConstantTerm) nextPart).getValue();
965            if ( value instanceof Collection )
966            {
967               if ( logger.isDebugEnabled() )
968                  logger.debug("detected collection constant value: "+value);
969               // Add multiple values as an enumeration
970               Iterator valueIterator = ((Collection) value).iterator();
971               conditionPart.append(" (");
972               while ( valueIterator.hasNext() )
973               {
974                  Object singleValue = valueIterator.next();
975                  if ( singleValue == null )
976                  {
977                     conditionPart.append("null,");
978                  } else {
979                     conditionPart.append("?,");
980                     values.add(singleValue);
981                  }
982               }
983               if ( conditionPart.charAt(conditionPart.length()-1) == ',' )
984                  conditionPart.delete(conditionPart.length()-1,conditionPart.length());
985               conditionPart.append(") ");
986            } else {
987               if ( logger.isDebugEnabled() )
988                  logger.debug("detected single constant value: "+value);
989               // Add a single value
990               if ( value == null )
991               {
992                  conditionPart.append(" null ");
993               } else {
994                  conditionPart.append(" ? ");
995                  values.add(value);
996               }
997            }
998         }
999         if ( nextPart instanceof ReferenceTerm )
1000         {
1001            if ( logger.isDebugEnabled() )
1002               logger.debug("adding reference term: "+nextPart);
1003            // It is a reference type, remember name
1004            ReferenceTerm term = (ReferenceTerm) nextPart;
1005            conditionPart.append(term.getExpression()+" ");
1006         }
1007      }
1008      return conditionPart.toString();
1009   }
1010 
1011   /**
1012    * Select objects from database as ordered list of attribute maps.
1013    * @param connection The connection to run statements in.
1014    * @param stmt The query statement.
1015    * @param limits The limits of the result. (Offset, maximum result count)
1016    * @param result The result object.
1017    */
1018   protected TransactionStatistics search(Connection connection, 
1019         QueryStatement stmt, Limits limits, SearchResult searchResult)
1020   {
1021      TransactionStatistics stats = new TransactionStatistics();
1022      Expression expression = stmt.getQueryExpression();
1023      List orderBys = stmt.getOrderByList();
1024      List<ReferenceTerm> groupBys = stmt.getGroupByList();
1025      Expression havingExpression = stmt.getHavingExpression();
1026      // Assemble statement:
1027      // - Create query source sql
1028      // - Parse expression create sql
1029      // - Create statement without orderbys and limits 
1030      // - Run count statement to determine full count
1031      // - Apply limits and orderbys to statement
1032      // - Run statement to get results
1033     
1034      // Transform statement to fit database
1035      stmt.setSelectTerms(transformTerms(stmt.getSelectTerms()));
1036      // Compute query columns and tables
1037      ArrayList types = new ArrayList();
1038      String querySource = getQuerySource(stmt,types);
1039      if ( logger.isDebugEnabled() )
1040         logger.debug("query source: "+querySource+", type vector: "+types+", select terms: "+stmt.getSelectTerms());
1041      // Determine whether select will be distinct. Currently it is _not_
1042      // distinct if some field returned would be blob type.
1043      boolean isDistinct = ! types.contains(byte[].class);
1044      // Create tables full part (with left joins)
1045      if ( logger.isDebugEnabled() )
1046         logger.debug("creating tables part from specified terms: "+stmt.getSpecifiedTerms());
1047      ArrayList statementValues = new ArrayList(); // Will hold values for '?' signs in-order
1048      StringBuffer tablesPart = new StringBuffer();
1049      Iterator tableIterator = stmt.getSpecifiedTerms().iterator();
1050      while ( tableIterator.hasNext() )
1051      {
1052         SpecifiedTableTerm tableTerm = (SpecifiedTableTerm) tableIterator.next();
1053         tablesPart.append(getTableDeclaration(tableTerm.getTableName(),tableTerm.getAlias()));
1054         // This is a selected table, so include left terms
1055         for ( int i=0; i<tableTerm.getRelatedLeftTerms().size(); i++ )
1056         {
1057            SpecifiedTableTerm.LeftjoinEntry joinEntry = (SpecifiedTableTerm.LeftjoinEntry) 
1058               tableTerm.getRelatedLeftTerms().get(i);
1059            tablesPart.append(" left join ");
1060            tablesPart.append(getTableDeclaration(joinEntry.term.getTableName(),
1061                     joinEntry.term.getAlias()));
1062            tablesPart.append(" on ("+getExpression(joinEntry.expression,statementValues)+")");
1063         }
1064         // Add other left-joined tables on other specific attributes
1065         for ( int i=0; i<tableTerm.getReferencedLeftTerms().size(); i++ )
1066         {
1067            SpecifiedTableTerm.LeftjoinEntry joinEntry = (SpecifiedTableTerm.LeftjoinEntry) 
1068               tableTerm.getReferencedLeftTerms().get(i);
1069            tablesPart.append(" left join ");
1070            tablesPart.append(getTableDeclaration(joinEntry.term.getTableName(),
1071                     joinEntry.term.getAlias()));
1072            tablesPart.append(" on ("+getExpression(joinEntry.expression,statementValues)+")");
1073         }
1074         // Comma
1075         tablesPart.append(",");
1076      }
1077      tablesPart.delete(tablesPart.length()-1,tablesPart.length());
1078      // So first, parse conditions, also compute the conditions string
1079      // in the process. This could be easily written in a recursive
1080      // algorithm, but there are too many dependencies.
1081      StringBuffer conditionPart = new StringBuffer(); // Will hold the where statement
1082      if ( (expression != null) && (expression.size()>0) )
1083      {
1084         conditionPart.append(" where ");
1085         conditionPart.append(getExpression(expression,statementValues));
1086      }
1087      // Calculate group by part
1088      if ( (groupBys!=null) && (!groupBys.isEmpty()) )
1089      {
1090         if ( logger.isDebugEnabled() )
1091            logger.debug("will use group by list: "+groupBys);
1092         conditionPart.append(" group by");
1093         for ( ReferenceTerm term : groupBys )
1094            conditionPart.append(" "+term.getExpression()+",");
1095         conditionPart.delete(conditionPart.length()-1,conditionPart.length());
1096      }
1097      // Calculate 'having' part
1098      if ( (havingExpression!=null) && (!havingExpression.isEmpty()) )
1099      {
1100         conditionPart.append(" having ");
1101         conditionPart.append(getExpression(havingExpression,statementValues));
1102      }
1103      // Calculate order by
1104      StringBuffer orderByTerm = new StringBuffer();
1105      if ( (orderBys!=null) && (orderBys.size() > 0) )
1106      {
1107         if ( logger.isDebugEnabled() )
1108            logger.debug("will use order by list: "+orderBys);
1109         orderByTerm.append(" order by ");
1110         for ( int i=0; i<orderBys.size(); i++ )
1111         {
1112            OrderBy orderBy = (OrderBy) orderBys.get(i);
1113            ReferenceTerm orderReferenceTerm = new ReferenceTerm(orderBy.getReferenceTerm());
1114            if ( 
1115                  ( (stmt.getMode()==QueryStatement.MODE_FIND) &&
1116                    (! stmt.getSelectTerms().contains(orderBy.getReferenceTerm())) ) ||
1117                  ( (stmt.getMode()==QueryStatement.MODE_VIEW) &&
1118                    (! containsReferenceTerm(stmt.getSelectTerms(),orderBy.getReferenceTerm())) )
1119               )
1120            {
1121               // Order by is not referencing the main tables. To maintain
1122               // distinct select, we must add this attribute to result
1123               querySource += ","+orderReferenceTerm.getExpression()+" as ordercol"+i;
1124               orderReferenceTerm.setColumnAlias("ordercol"+i);
1125               Class attributeType = getAttributeType(orderReferenceTerm.getTableName(),
1126                     orderReferenceTerm.getColumnName());
1127               types.add(attributeType);
1128            }
1129            // Add to order term
1130            if ( orderReferenceTerm.getColumnAlias() != null )
1131               orderByTerm.append(orderReferenceTerm.getColumnAlias());
1132            else
1133               orderByTerm.append(orderReferenceTerm.getExpression());
1134            orderByTerm.append(orderBy.getDirection()==OrderBy.ASCENDING?" asc,":" desc,");
1135         }
1136         orderByTerm.delete(orderByTerm.length()-1,orderByTerm.length());
1137      }
1138      if ( logger.isDebugEnabled() )
1139         logger.debug("select types: "+types);
1140      // Create count statement
1141      StringBuffer subStatement = new StringBuffer("select "+(isDistinct?"distinct ":"")+querySource+" from ");
1142      subStatement.append(tablesPart.toString());
1143      subStatement.append(conditionPart.toString());
1144      StringBuffer statement = new StringBuffer(getCountStatement(subStatement.toString()));
1145      logger.debug("preparing counting statement: "+statement.toString());
1146      String countStatement = statement.toString();
1147      // Prepare count statement
1148      PreparedStatement countPstmt;
1149      try
1150      {
1151         countPstmt = connection.prepareStatement(countStatement);
1152         for ( int i=0; i<statementValues.size(); i++ )
1153         {
1154            Object value = getSQLValue(statementValues.get(i));
1155            logger.debug("setting statement parameter #"+i+": "+value);
1156            sqlLogger.debug("setting statement parameter #"+i+": "+value);
1157            countPstmt.setObject(i+1,value);
1158         }
1159      } catch ( Exception e ) {
1160         throw new StoreException("cannot prepare statement: "+statement.toString(),e);
1161      }
1162      // Now create full statement (add order and limit)
1163      statement = new StringBuffer("select "+(isDistinct?"distinct ":"")+querySource+" from ");
1164      statement.append(tablesPart.toString());
1165      statement.append(conditionPart.toString());
1166      statement.append(orderByTerm.toString());
1167      // Add limits
1168      if ( limits != null )
1169         statement = new StringBuffer(getLimitStatement(statement.toString(),limits,types));
1170      // Run statement
1171      ArrayList result = new ArrayList();
1172      PreparedStatement pstmt = null;
1173      if ( (limits==null) || (!limits.isEmpty()) )
1174      {
1175         // Prepare
1176         try
1177         {
1178            pstmt = connection.prepareStatement(statement.toString());
1179            for ( int i=0; i<statementValues.size(); i++ )
1180            {
1181               Object value = getSQLValue(statementValues.get(i));
1182               logger.debug("setting statement parameter #"+i+": "+value);
1183               sqlLogger.debug("setting statement parameter #"+i+": "+value);
1184               pstmt.setObject(i+1,value);
1185            }
1186         } catch ( Exception e ) {
1187            throw new StoreException("cannot prepare statement: "+statement.toString(),e);
1188         }
1189         // Execute
1190         try
1191         {
1192            sqlLogger.debug("running select statement: "+statement.toString());
1193            long startTime = System.currentTimeMillis();
1194            prepareStatement(pstmt,limits);
1195            ResultSet rs = pstmt.executeQuery();
1196            long endTime = System.currentTimeMillis();
1197            aggregatorLogger.log("Query statement execution",new int[] { (int) (endTime-startTime) });
1198            stats.setSelectCount(stats.getSelectCount()+1);
1199            stats.setSelectTime(stats.getSelectTime()+(endTime-startTime));
1200            ResultSetMetaData rsmd = rs.getMetaData();
1201            // Get result and pack the attributes into a map
1202            prepareResultSet(rs,limits);
1203            while ( rs.next() )
1204            {
1205               Map attributes = new HashMap();
1206               for ( int i=0; i<rsmd.getColumnCount(); i++ )
1207               {
1208                  String columnName = rsmd.getColumnName(i+1).toLowerCase();
1209                  Object columnValue = getJavaValue(rs.getObject(i+1),rsmd.getColumnType(i+1),(Class) types.get(i));
1210                  if ( columnName.startsWith("persistence_start") )
1211                  {
1212                     // Handle persistence_starts
1213                     Long previousValue = (Long) attributes.get("persistence_start");
1214                     if ( (previousValue==null) || (previousValue.longValue() < 
1215                              ((Long) columnValue).longValue()) )
1216                        attributes.put("persistence_start",columnValue);
1217                  } else if ( columnName.startsWith("persistence_end") ) {
1218                     // Handle persistence_ends
1219                     Long previousValue = (Long) attributes.get("persistence_end");
1220                     if ( (previousValue==null) || (previousValue.longValue() >
1221                              ((Long) columnValue).longValue()) )
1222                        attributes.put("persistence_end",columnValue);
1223                  } else {
1224                     // Normal attributes
1225                     attributes.put(columnName, columnValue);
1226                  }
1227               }
1228               result.add(attributes);
1229            }
1230            rs.close();
1231         } catch ( Exception e ) {
1232            throw new StoreException("exception while sql select",e);
1233         } finally {
1234            try
1235            {
1236               pstmt.close();
1237            } catch ( Exception e ) {
1238               logger.debug("unable to close statement",e);
1239            }
1240         }
1241      } // End valid limits (running of query)
1242      // Execute count statement if necessary
1243      long resultSize;
1244      if ( limits == null )
1245      {
1246         // If there are no limits, the result is a full result
1247         resultSize = result.size();
1248      } else if ( limits.getSize()>=0 ) {
1249         // Size already known (possibly from previous select)
1250         resultSize = limits.getSize();
1251      } else if ( (limits.getLimit()>0) && (result.size()<limits.getLimit()) ) {
1252         // Count statement not necessary, this is the last page, we can
1253         // compute the size
1254         resultSize = limits.getOffset()+result.size();
1255      } else {
1256         // We must get the full size the hard way, so select
1257         if ( sqlLogger.isDebugEnabled() )
1258            sqlLogger.debug("running count statement: "+countStatement);
1259         try
1260         {
1261            long startTime = System.currentTimeMillis();
1262            ResultSet rs = countPstmt.executeQuery();
1263            long endTime = System.currentTimeMillis();
1264            aggregatorLogger.log("Query count statement execution",new int[] { (int) (endTime-startTime) });
1265            stats.setSelectCount(stats.getSelectCount()+1);
1266            stats.setSelectTime(stats.getSelectTime()+(endTime-startTime));
1267            rs.next();
1268            resultSize = rs.getLong(1);
1269            rs.close();
1270         } catch ( Exception e ) {
1271            throw new StoreException("exception while sql select count",e);
1272         } finally {
1273            try
1274            {
1275               countPstmt.close();
1276            } catch ( Exception e ) {
1277               logger.debug("unable to close statement",e);
1278            }
1279         }
1280      }
1281      // Assemble result and return
1282      searchResult.setResultSize(resultSize);
1283      searchResult.setResult(result);
1284      sqlLogger.debug("returning result, size: "+result.size()+" / "+resultSize);
1285      return stats;
1286   }
1287 
1288   /**
1289    * Convert incoming value from database into java format.
1290    */
1291   protected Object getJavaValue(Object value, int type, Class javaType)
1292   {
1293      if ( value instanceof Timestamp )
1294         return new Date(((Timestamp) value).getTime());
1295      return value;
1296   }
1297 
1298   /**
1299    * Convert incoming values from java into database acceptable format.
1300    */
1301   protected Object getSQLValue(Object value)
1302   {
1303      if ( (value!=null) && (value instanceof Date) )
1304         return new Timestamp(((Date) value).getTime());
1305      return value;
1306   }
1307  
1308   /**
1309    * Get the class for an sql type.
1310    */
1311   protected String getSQLTypeName(int sqltype)
1312   {
1313      switch ( sqltype )
1314      {
1315         case Types.DATE:
1316         case Types.TIME:
1317         case Types.TIMESTAMP:
1318            return "timestamp";
1319         case Types.LONGVARCHAR:
1320         case Types.VARCHAR:
1321            return "text";
1322         case Types.BIT:
1323         case Types.BOOLEAN:
1324            return "boolean";
1325         case Types.INTEGER:
1326         case Types.NUMERIC:
1327         case Types.DECIMAL:
1328            return "integer";
1329         case Types.BIGINT:
1330            return "bigint";
1331         case Types.SMALLINT:
1332         case Types.TINYINT:
1333            return "smallint";
1334         case Types.DOUBLE:
1335         case Types.FLOAT:
1336         case Types.REAL:
1337            return "float";
1338         case Types.CHAR:
1339            return "char";
1340         case Types.BLOB:
1341         case Types.BINARY:
1342         case Types.VARBINARY:
1343         case Types.LONGVARBINARY:
1344            return "blob";
1345         default:
1346      }
1347      throw new StoreException("no sql type definition programmed for type: "+sqltype);
1348   }
1349   
1350   /**
1351    * Get the sql type string for a class.
1352    */
1353   protected int getSQLType(Class type)
1354   {
1355      if ( (Date.class.equals(type)) )
1356         return Types.TIMESTAMP;
1357      if ( String.class.equals(type) )
1358         return Types.VARCHAR;
1359      Class booleanClass = boolean.class;
1360      if ( (booleanClass.equals(type)) || (Boolean.class.equals(type)) )
1361         return Types.BOOLEAN;
1362      Class intClass = int.class;
1363      if ( (intClass.equals(type)) || (Integer.class.equals(type)) )
1364         return Types.INTEGER;
1365      Class longClass = long.class;
1366      if ( (longClass.equals(type)) || (Long.class.equals(type)) )
1367         return Types.BIGINT;
1368      Class byteClass = byte.class;
1369      if ( (byteClass.equals(type)) || (Byte.class.equals(type)) )
1370         return Types.SMALLINT;
1371      if ( byte[].class.equals(type) )
1372         return Types.BLOB;
1373      Class doubleClass = double.class;
1374      if ( (doubleClass.equals(type)) || (Double.class.equals(type)) )
1375         return Types.DOUBLE;
1376      Class floatClass = float.class;
1377      if ( (floatClass.equals(type)) || (Float.class.equals(type)) )
1378         return Types.FLOAT;
1379      Class shortClass = short.class;
1380      if ( (shortClass.equals(type)) || (Short.class.equals(type)) )
1381         return Types.SMALLINT;
1382      Class charClass = char.class;
1383      if ( (charClass.equals(type)) || (Character.class.equals(type)) )
1384         return Types.CHAR;
1385      throw new StoreException("type: "+type+" is not an allowed primitive type.");
1386   }
1387 
1388   /**
1389    * Execute update statement. Simply and safely execute the
1390    * given statement.
1391    * @param connection The connection to execute statement on.
1392    * @param statement The statement to execute.
1393    */
1394   protected void executeUpdate(Connection connection, String statement)
1395   {
1396      sqlLogger.debug("executing update statement: "+statement);
1397      // Prepare
1398      PreparedStatement pstmt;
1399      try
1400      {
1401         pstmt = connection.prepareStatement(statement.toString());
1402      } catch ( Exception e ) {
1403         throw new StoreException("cannot prepare statement: "+statement.toString(),e);
1404      }
1405      // Execute
1406      try
1407      {
1408         pstmt.executeUpdate();
1409      } catch ( Exception e ) {
1410         throw new StoreException("exception while executing statement: "+statement,e);
1411      } finally {
1412         try
1413         {
1414            pstmt.close();
1415         } catch ( Exception e ) {
1416            logger.debug("unable to close statement",e);
1417         }
1418      }
1419   }
1420 
1421   /**
1422    * Prepare the sql statment to be executed.
1423    */
1424   protected void prepareStatement(PreparedStatement pstmt, Limits limits)
1425      throws SQLException
1426   {
1427   }
1428 
1429   /**
1430    * Prepare the result set to be iterated.
1431    */
1432   protected void prepareResultSet(ResultSet rs, Limits limits)
1433      throws SQLException
1434   {
1435   }
1436 
1437}
1438 
1439 

[all classes][hu.netmind.beankeeper.db.impl]
EMMA 2.0.5312debian (C) Vladimir Roubtsov