001    package hep.aida.ref.sql;
002    
003    import static hep.aida.ref.sql.Implementation.*;
004    
005    // Java
006    import java.util.ArrayList;
007    import java.util.Map;
008    import java.util.HashMap;
009    import java.io.InputStream;
010    import java.io.File;
011    import java.io.FileOutputStream;
012    import java.io.FileInputStream;
013    import java.io.ObjectOutputStream;
014    import java.io.ObjectInputStream;
015    import java.io.FileNotFoundException;
016    import java.io.IOException;
017    import java.security.AccessControlException;
018    
019    // AIDA
020    import hep.aida.ITree;
021    import hep.aida.ITuple;
022    import hep.aida.IFilter;
023    import hep.aida.IEvaluator;
024    import hep.aida.ref.AidaUtils;
025    import hep.aida.ref.tree.Tree;
026    import hep.aida.ref.tuple.Tuple;
027    import hep.aida.ref.tuple.TupleFactory;
028    
029    // Log4J
030    import org.apache.log4j.Logger;
031    
032    // SQL
033    import java.sql.ResultSet;
034    import java.sql.DatabaseMetaData;
035    import java.sql.SQLException;
036    
037    /** <code>SQLTupleFactory</code> creates {@link SQLTuple}s. It creates SQL tables
038      * if neccessary. It reuses some funtionality from
039      * {@link hep.aida.ref.tuple.TupleFactory}.
040      * <br>
041      * <code>SQLTupleFactory</code> contains several extensions to
042      * {@link hep.aida.ITupleFactory}:
043      * <ul>
044      * <li><code>void delete(String path);</code></li>
045      * <p><font color="#880088">
046      * <pre>
047      * $Log: SQLTupleFactory.java,v $
048      * Revision 1.79  2008/07/25 13:25:03  hrivnac
049      * oracle schema for tags supported improved
050      *
051      * Revision 1.78  2007/09/26 07:58:51  hrivnac
052      * cache savable fromjas menu
053      *
054      * Revision 1.77  2007/06/11 14:02:29  hrivnac
055      * better support for tags in Oracle
056      *
057      * Revision 1.76  2007/05/23 16:38:44  hrivnac
058      * logical connections for Plotter; better UML
059      *
060      * Revision 1.75  2007/05/22 23:33:20  hrivnac
061      * Plotter uses real arguments parsing
062      *
063      * Revision 1.74  2006/12/12 15:21:43  hrivnac
064      * McKoi replaced with Derby; uppercase-only dbs handled better; moving to JAS 0.8.3
065      *
066      * Revision 1.73  2006/11/22 23:55:36  hrivnac
067      * fixed non-double values, added intr database
068      *
069      * Revision 1.72  2006/11/15 16:16:01  hrivnac
070      * Oracle work fine in CERN, cache enabled
071      *
072      * Revision 1.71  2006/11/14 16:06:18  hrivnac
073      * improving Web Service
074      *
075      * Revision 1.70  2005/09/02 14:10:31  hrivnac
076      * moved to JAS3 0.8.2
077      *
078      * Revision 1.69  2005/02/10 20:07:52  hrivnac
079      * bug fixes to support new ColMan
080      *
081      * Revision 1.68  2004/11/22 18:09:53  hrivnac
082      * Oracle treatment
083      *
084      * Revision 1.67  2004/10/29 22:27:25  hrivnac
085      * imports corrected
086      *
087      * Revision 1.66  2004/10/28 15:25:40  hrivnac
088      * *** empty log message ***
089      *
090      * Revision 1.65  2004/10/22 15:32:59  hrivnac
091      * cleaned
092      *
093      * Revision 1.64  2004/10/21 15:46:49  hrivnac
094      * doc improved
095      *
096      * Revision 1.63  2004/10/21 10:39:32  hrivnac
097      * works wel with ColMan
098      *
099      * Revision 1.62  2004/10/20 23:02:29  hrivnac
100      * Types mapping simplified
101      *
102      * Revision 1.61  2004/10/14 13:39:33  hrivnac
103      * unknown types are mapped to Strings
104      *
105      * Revision 1.60  2004/10/13 12:25:39  hrivnac
106      * defaults work fine
107      *
108      * Revision 1.59  2004/09/02 16:13:25  hrivnac
109      * support for ColMan Replicator
110      *
111      * Revision 1.58  2004/09/02 12:16:37  hrivnac
112      * Filter works correctly between different dbs
113      *
114      * Revision 1.57  2004/07/12 10:47:16  hrivnac
115      * Bugs/ToDos
116      *
117      * Revision 1.56  2004/06/30 13:01:05  hrivnac
118      * oracle sceleton
119      *
120      * Revision 1.55  2004/06/23 08:04:53  hrivnac
121      * documentation fixes
122      *
123      * Revision 1.54  2004/06/22 08:52:32  hrivnac
124      * allow to skip compatibility test
125      *
126      * Revision 1.53  2004/06/16 21:56:43  hrivnac
127      * StringBuffer in SQLTuple.addRow() -> speed
128      *
129      * Revision 1.52  2004/05/22 15:12:59  hrivnac
130      * class id reformated
131      *
132      * Revision 1.51  2004/04/21 22:04:03  hrivnac
133      * Implementation.properties to configure Inplementation.java, no SQL dependency in code
134      *
135      * Revision 1.50  2004/04/21 13:41:32  hrivnac
136      * explicit dependency on MySQL removed
137      *
138      * Revision 1.49  2004/04/21 08:24:50  hrivnac
139      * some explicit MySQL dependencies removed
140      *
141      * Revision 1.48  2004/04/20 13:42:26  hrivnac
142      * logs updated
143      *
144      * Revision 1.47  2004/04/20 12:46:49  hrivnac
145      * tuples use always primitive types
146      *
147      * Revision 1.46  2004/04/20 09:28:08  hrivnac
148      * bug fixes
149      *
150      * Revision 1.45  2004/04/16 15:32:57  hrivnac
151      * Type.properties to setup Type.class
152      *
153      * Revision 1.44  2004/04/15 16:25:04  hrivnac
154      * type mapping improved
155      *
156      * Revision 1.43  2004/04/14 13:39:47  hrivnac
157      * 1.5 warnings fixed
158      *
159      * Revision 1.42  2004/04/13 15:45:54  hrivnac
160      * AIDA URL introduced
161      *
162      * Revision 1.41  2004/03/23 23:41:15  hrivnac
163      * new AidaUtils
164      *
165      * Revision 1.40  2004/02/10 14:50:58  hrivnac
166      * JavaDoc tags completed
167      *
168      * Revision 1.39  2004/02/04 17:02:14  hrivnac
169      * deleting of non-existing ntuple
170      *
171      * Revision 1.38  2003/12/01 13:18:20  hrivnac
172      * Prepared for beta3
173      *
174      * Revision 1.37  2003/11/26 16:09:46  hrivnac
175      * Functional EventSelector WebService
176      *
177      * Revision 1.36  2003/11/24 15:13:22  hrivnac
178      * Logging improved.
179      *
180      * Revision 1.35  2003/11/20 17:21:58  hrivnac
181      * Java 1.5 natively supported, Log4J reporting improved.
182      *
183      * Revision 1.34  2003/11/18 22:03:45  hrivnac
184      * Root benchmarks.
185      *
186      * Revision 1.33  2003/11/12 14:13:48  hrivnac
187      * Access to Pool Collections is provided.
188      *
189      * Revision 1.32  2003/11/10 15:34:08  hrivnac
190      * SQLTupleFactory documented
191      *
192      * Revision 1.31  2003/11/06 22:47:46  hrivnac
193      * Updated documentation.
194      *
195      * Revision 1.30  2003/11/06 16:54:16  hrivnac
196      * Merger works for SQL DBs
197      *
198      * Revision 1.29  2003/11/05 19:46:22  hrivnac
199      * - FreeHEP 1.2.1
200      * - JAIDA 3.2.1
201      *
202      * Revision 1.16  2003/10/22 17:59:39  hrivnac
203      * Refactored.
204      *
205      * Revision 1.15  2003/10/21 13:56:00  hrivnac
206      * Default values supported.
207      *
208      * Revision 1.14  2003/10/21 13:24:52  hrivnac
209      * Constructor from columnString implemented.
210      *
211      * Revision 1.9  2003/10/10 13:21:36  hrivnac
212      * Works between different DB technologies.
213      *
214      * Revision 1.8  2003/10/10 10:12:21  hrivnac
215      * All three testing pgms work.
216      *
217      * Revision 1.4  2003/10/08 09:27:49  hrivnac
218      * createFiltered(...) naive way.
219      *
220      * Revision 1.3  2003/10/02 10:00:40  hrivnac
221      * Cleaning.
222      *
223      * Revision 1.2  2003/09/30 16:46:02  hrivnac
224      * - Writing test in TestWrite
225      * - SQLTuple projections supported
226      *
227      * Revision 1.1  2003/09/30 14:11:48  hrivnac
228      * Can created SQLTuples.
229      *
230      * </pre>
231      * </font></p>
232      * @opt attributes
233      * @opt operations
234      * @opt types
235      * @opt visibility
236      * @version $Id: SQLTupleFactory.java,v 1.79 2008/07/25 13:25:03 hrivnac Exp $
237      * @author <a href="mailto:Julius.Hrivnac@cern.ch">J.Hrivnac</a> */
238    public class SQLTupleFactory extends TupleFactory {
239    
240      /** Call superconstructor and register {@link ITree}.
241        * Read descriptions cache from the file or resource if possible.
242        * @param tree The {@link ITree} to be used with this {@link hep.aida.ITupleFactory}.
243        * @throws SQLTupleException if the {@link hep.aida.ITupleFactory} can't be created. */
244      public SQLTupleFactory(ITree tree) throws SQLTupleException {
245        super(tree);
246        assert !(tree instanceof Tree) : "SQLTuple works only with hep.aida.ref.tree.Tree";
247        this.tree = (Tree)tree;
248        try {
249          //setOptions(AidaUtils.parseOptions(this.tree.getOptions()));
250          setOptions(this.tree.getOptions().toString().replace('{', ' ').replace('}', ' '));
251          }
252        catch (SQLTupleException e) {
253          throw new SQLTupleException("Can't create SQLTupleFactory", e);
254          }
255        if (_descriptions.isEmpty()) {
256          readCache();
257          }
258        }
259    
260      /** Create {@link SQLTuple}. Reuse (append) existing table, if exists,
261        * create new one otherwise.
262        * All other creating methods just re-shuffle arguments and call this method.
263        * @param path       The path to the {@link ITuple}.
264        * @param title      The title of the {@link ITuple}.
265        * @param columnName The names of columns.
266        * @param columnType The types of columns.
267        * @param options    The options to be used to create the ITuple.
268        * @throws IllegalArgumentException if the {@link ITuple} can't be created. */
269      public ITuple create(String path,
270                           String title,
271                           String[] columnName,
272                           Class[] columnType, 
273                           String options) throws IllegalArgumentException {
274        log.debug("Creating SQLTuple " + title + " in " + path);
275        unbox(columnType);
276        SQLTuple tuple = null;
277        try {
278          // NTuple doesn't exist => the table is created in database
279          String[] columnNameSQL = new String[columnName.length];
280          if (!tableExists(title)) {
281            log.debug("SQLTuple doesn't exist => creating");
282            int nColumns = columnName.length;
283            String def;
284            int eq;
285            String values = "";
286            for (int i = 0; i < nColumns; i++) {
287              def = null;
288              columnNameSQL[i] = columnName[i];
289              eq = columnName[i].indexOf("=");
290              if (eq > -1) {
291                def              = columnName[i].substring(eq + 1    ).trim();
292                columnNameSQL[i] = columnName[i].substring(0     , eq).trim();
293                }
294              values += columnNameSQL[i] + " " + _accessor.sqlName(columnType[i]);
295              if (def != null) {
296                values += " default " + def;
297                }
298              if (i < nColumns - 1) {
299                values += ", ";
300                }
301              }
302            String[] args = {title, values};
303            _stmtSrc.update("createTable", args);
304            }
305          // NTuple exists and is incompatible with requested NTuple => nothing can be done 
306          else if (!tableCompatible(title, columnNameSQL, columnType, null)) {
307            throw new IllegalArgumentException("Can't create SQLTuple " + title + " because it already exists in the database " + _accessor.db() + " with a different signature");
308            }
309          // Compatible NTuple exists => it is reused
310          else {
311            log.debug("SQLTuple exists with the same signature => reusing");
312            }
313          // NTuple is created
314          tuple = new SQLTuple(nameInPath(path), title, columnName, columnType, options);
315          // NTuple is added into tree
316          if (tree != null) {
317            tree.add("/" + parentPath(path), tuple);
318            }
319          }
320        catch(SQLTupleException e) {
321          log.error(Util.report("Can't create table " + title + " in " + path, e), e);
322          throw new IllegalArgumentException("Couldn't create table " + title + " in " + path, e);
323          }
324        // Return created NTuple
325        return tuple;
326        }
327    
328      /** Just call the the master creation method.
329        * @param path       The path to the {@link ITuple}.
330        * @param title      The title of the {@link ITuple}.
331        * @param columnName The names of columns.
332        * @param columnType The types of columns.
333        * @throws IllegalArgumentException if the {@link ITuple} can't be created. */
334      public ITuple create(String path,
335                           String title,
336                           String[] columnName,
337                           Class[] columnType) {
338        return create(path, title, columnName, columnType, "");
339        }
340    
341      /** Split columns string into columnName and columnType arrays and call master creation method.
342        * @param path    The path to the {@link ITuple}.
343        * @param title   The title of the {@link ITuple}.
344        * @param columns The columns definition.
345        * @param options The options to be used to create the {@link ITuple}.
346        * @throws IllegalArgumentException if the {@link ITuple} can't be created. */
347      public ITuple create(String path,
348                           String title,
349                           String columns,
350                           String options) throws IllegalArgumentException {
351        String[] col = columns.split(";");
352        String[] columnName = new String[col.length];
353        Class[ ] columnType = new Class[ col.length];
354        int b;
355        String type;
356        for (int i = 0; i < col.length; i++) {
357          col[i] = col[i].trim();
358          b = col[i].indexOf(" ");
359          if (b > -1) {
360            columnName[i] =                 col[i].substring(b + 1   ).trim();
361            columnType[i] = Type.fromString(col[i].substring(0    , b).trim());
362            }
363          else {
364            throw new IllegalArgumentException("Can't create NTuple from columns: " + columns);
365            }
366          }
367        unbox(columnType);
368        return create(path, title, columnName, columnType, options);
369        }
370    
371      /** Just call the master creation method.
372        * @param path    The path to the {@link ITuple}.
373        * @param title   The title of the {@link ITuple}.
374        * @param columns The columns definition.
375        * @throws IllegalArgumentException if the {@link ITuple} can't be created. */
376      public ITuple create(String path,
377                           String title,
378                           String columns) {
379        return create(path, title, columns, "");
380        }
381    
382      /** Filter {@link SQLTuple}. Reuse (append) existing table, if exists,
383        * create new one otherwise.
384        * All other creating methods just re-shuffle arguments and call this method.
385        * @param path    The path to the {@link ITuple}.
386        * @param tuple   The original {@link ITuple}.
387        * @param filter  The {@link IFilter} to be used.
388        * @param columns The columns to be copied into the new {@link ITuple}.
389        * @return        The filtered {@link ITuple} (the result). */
390      public ITuple createFiltered(String path,
391                                   ITuple tuple,
392                                   IFilter filter,
393                                   String[] columns) throws IllegalArgumentException {
394        log.debug("Creating filtered SQLTuple in " + path);
395        String fexpr = "true";
396        if (filter != null &&
397            !filter.expression().trim().equals("")) {
398          fexpr = filter.expression();
399          }
400        SQLTuple newTuple = null;
401        try {
402          // Prepare arrays and values
403          int nColumns = columns.length;
404          int[]   columnId    = new int[nColumns];
405          Class[] columnTypes = new Class[nColumns];
406          for (int i = 0; i < nColumns; i++) {
407            columnId[i]    = tuple.findColumn(columns[i]);
408            columnTypes[i] = tuple.columnType(columnId[i]);
409            }
410          unbox(columnTypes);
411          String title = tuple.title();
412          // Check creatability
413          if (tuple instanceof SQLTuple &&
414              ! tableCreatable(title, columns, columnTypes, ((SQLTuple)(tuple)).accessor())) {
415            throw new IllegalArgumentException("Can't create NTuple " + path + " from existing source NTuple " + tuple.title());
416            }
417          // SQLTuple in the same DB, doesn't exist yet, supports local replication => it will be replicated localy
418          if (tuple instanceof SQLTuple &&
419              _accessor.equals(((SQLTuple)(tuple)).accessor()) &&
420              _accessor.supportsReplication() &&
421              ! tableExists(nameInPath(path))) {
422            // New NTuple
423            log.debug("SQLTuple will be replicated within the same DB");
424            String values = "";
425            for (int i = 0; i < nColumns; i++) {
426              values += columns[i];
427              if (i < nColumns - 1) {
428                values += ", ";
429                }
430              }
431            values += "";
432            String[] args = {nameInPath(path), values, tuple.title(), fexpr};
433            _stmtSrc.update("createTableSelect", args);
434            newTuple = new SQLTuple(nameInPath(path), nameInPath(path), columns, columnTypes, _options);
435            // NTuple is added into tree
436            if (tree != null) {
437              tree.add("/" + parentPath(path), newTuple);
438              }
439            }
440          // Either change of technology, change of DB, doesn't support local replication or new NTuple
441          else {
442            // NTuple already exists => resused and appended
443            if (tableExists(nameInPath(path))) {
444              log.debug("SQLTuple will be appended");
445              log.warn("NTuple " + path + " already exists => reusing");
446              newTuple = new SQLTuple(nameInPath(path), nameInPath(path), columns, columnTypes, _options);
447              }
448            // NTuple will be created
449            else {
450              log.debug("SQLTuple will be created elsewhere");
451              newTuple = (SQLTuple)create(nameInPath(path), nameInPath(path), columns, columnTypes, _options);
452              }
453            if (tuple.rows() > 0) {
454              tuple.start();
455              if (filter != null) {
456                filter.initialize(tuple);
457                }
458              while (tuple.next()) {
459                      if (filter == null || filter.accept()) {
460                        for (int i = 0; i < nColumns; i++) {
461                                int j = columnId[i];
462                                     if (columnTypes[i] == Integer.TYPE)   newTuple.fill(i, tuple.getInt(j));
463                                else if (columnTypes[i] == Short.TYPE)     newTuple.fill(i, tuple.getShort(j));
464                                else if (columnTypes[i] == Long.TYPE)      newTuple.fill(i, tuple.getLong(j));
465                              else if (columnTypes[i] == Float.TYPE)     newTuple.fill(i, tuple.getFloat(j));
466                        else if (columnTypes[i] == Double.TYPE)    newTuple.fill(i, tuple.getDouble(j));
467                              else if (columnTypes[i] == Boolean.TYPE)   newTuple.fill(i, tuple.getBoolean(j));
468                                else if (columnTypes[i] == Byte.TYPE)      newTuple.fill(i, tuple.getByte(j));
469                                else if (columnTypes[i] == Character.TYPE) newTuple.fill(i, tuple.getChar(j));
470                                else if (columnTypes[i] == ITuple.class) {
471                                  Tuple tOld = (Tuple) tuple.getObject(j);
472                                  newTuple.fill(i, tOld);
473                                  }
474                                else newTuple.fill(i, tuple.getObject(j));
475                          }
476                        newTuple.addRow();
477                        }
478                }
479              }
480            }
481          }
482        catch(SQLTupleException e) {
483          log.error(Util.report("Can't create table " + tuple.title() + " in " + path, e), e);
484          throw new IllegalArgumentException("Couldn't create table " + tuple.title() + " in " + path, e);
485          }
486        // Return created NTuple
487        return newTuple;
488        }
489    
490      /** Just extract columnNames and calls the master filtering method.
491        * @param path   The path to the {@link ITuple}.
492        * @param tuple  The original {@link ITuple}.
493        * @param filter The {@link IFilter} to be used.
494        * @return       The filtered {@link ITuple} (the result). */
495      public ITuple createFiltered(String path,
496                                   ITuple tuple,
497                                   IFilter filter) {
498        int nColumns = tuple.columns();
499        String[] columnNames = new String[nColumns];
500        for (int i = 0; i < nColumns; i++) {
501          columnNames[i] = tuple.columnName(i);
502          }
503        return createFiltered(path, tuple, filter, columnNames);
504        }
505    
506      /** Describe NTuple (table) from the SQL database.
507        * Connection to database is only made the first time,
508        * the cached results are used after.
509        * @param  name     The name of the existing {@link ITuple} (within the actual DB).
510        * @param  accessor The {@link Accessor} of existing table to be used. 
511        *                  If <code>null</code> the current {@link Accessor} is used.
512        * @return          Two arrays: <code>String[]</code> containing column names 
513        *                  and <code>Class[]</code> containing column types.
514        * @throws SQLTupleException if the information can't be obtained. */
515      public Object[] tupleDescription(String   name,
516                                       Accessor accessor) throws SQLTupleException {
517        // Initiate
518        if (accessor == null) {
519          accessor = _accessor;
520          }
521        String key = accessor.toString() + "#" + name;
522        int n = 0;
523        String[] names = null;
524        Class[]  types = null;
525        // Is already in cache
526        if (_descriptions.containsKey(key)) {
527          log.debug("Reusing cached description for " + key);
528          return _descriptions.get(key);
529          }
530        // Ask database
531        else {
532          try {
533            int firstDot = name.indexOf(".");
534            if (firstDot >= 0) {
535              name = name.substring(firstDot + 1, name.length());
536              }
537            DatabaseMetaData dbmd = accessor.connection().getMetaData();
538            ResultSet rs = dbmd.getColumns(null, null, name, null);
539            ArrayList<String> names0 = new ArrayList<String>();
540            ArrayList<Class>  types0 = new ArrayList<Class>();
541            String cName;
542            String cTypeName;
543            int    cTypeIdx;
544            Class  cType;
545            log.debug("Table " + name + " :");
546            int iName     = rs.findColumn("COLUMN_NAME");
547            int iTypeIdx  = rs.findColumn("DATA_TYPE");
548            int iTypeName = rs.findColumn("TYPE_NAME");
549            while (rs.next()) {
550              cName     = rs.getString(iName);
551              cTypeIdx  = rs.getInt(   iTypeIdx);
552              cTypeName = rs.getString(iTypeName);
553              cType     = Type.fromSql(cTypeIdx, cTypeName);
554              names0.add(cName);
555              types0.add(cType);
556              n++;
557              log.debug("  " + cName + "[" + cTypeName + "(" + cTypeIdx + ") -> " + cType + "]");
558              }
559            rs.close();
560            names = new String[n];
561            types = new Class[n];
562            names = names0.toArray(names);
563            types = types0.toArray(types);
564                }
565              catch (SQLTupleException e) {
566            throw new SQLTupleException("Can't get table information for table " + name, e); 
567            }
568              catch (SQLException e) {
569            throw new SQLTupleException("Can't get table information for table " + name, e); 
570            }
571          // Put into cache and return
572          Object[] description = new Object[2];
573          description[0] = names;
574          description[1] = unbox(types);
575          _descriptions.put(key, description);
576          return description;
577          }
578        }
579    
580      /** Verify if NTuple (table) in the DB is compatible with provided columns description.
581        * All names and types should be the same.
582        * @param  name     The name of the existing {@link ITuple}.
583        * @param  names0   The column names to be tested.
584        * @param  classes0 The column types to be tested.
585        * @param  accessor The {@link Accessor} of existing table to be used
586        *                  as source (reference). If <code>null</code> the current
587        *                  {@link Accessor} is used.
588        * @return          true if <code>names0</code> and <code>classes0</code>
589        *                  are compatibe with existing {@link ITuple}.
590        * @throws SQLTupleException if the information can't be obtained. */
591      public boolean tableCompatible(String   name,
592                                     String[] names0,
593                                     Class[]  classes0,
594                                     Accessor accessor) throws SQLTupleException  {
595        if (!_test) {
596          log.warn("Compatibility test skipped");
597          return true;
598          }
599        Object[] description = tupleDescription(name, accessor);
600        String[] names = (String[])(description[0]);
601        Class[]  types = (Class[] )(description[1]);
602        if (names.length != names0.length) {
603          log.debug("NTuples are not compatible, they have different lengths: " + names.length + " != " + names0.length);
604          for (int i = 0; i < names.length; i++) {
605            log.debug("Existing  NTuple " + i + " : " + names[i]);
606            }
607          for (int i = 0; i < names0.length; i++) {
608            log.debug("Requested NTuple " + i + " : " + names0[i]);
609            }
610          return false;
611          }
612        for (int i = 0; i < names.length; i++) {
613          if (!names[i].equals(names0[i]) || types[i] != classes0[i]) {
614            log.debug("Columns " + i + " are not compatible: " + names[i] + "(" + types[i] + ") != " + names0[i] + "(" + classes0[i] + ")");
615            return false;
616            }
617          }
618        return true;
619        }
620    
621      /** Verify if new NTuple (table) can be created from the existing one.
622        * New names and types should be the subset of old ones.
623        * @param  name     The name of the existing {@link ITuple}.
624        * @param  names0   The column names to be tested.
625        * @param  classes0 The column types to be tested.
626        * @param  accessor The {@link Accessor} of existing table to be used
627        *                  as source (reference). If <code>null</code> the current
628        *                  {@link Accessor} is used.
629        * @return          <code>true</code> if the {@link ITuple},
630        *                  defined by <code>names0</code> and <code>classes0</code> 
631        *                  can be ceated from the existing {@link ITuple}.
632        * @throws SQLTupleException if the information can't be obtained. */
633      public boolean tableCreatable(String   name,
634                                    String[] names0,
635                                    Class[]  classes0,
636                                    Accessor accessor) throws SQLTupleException {
637        Object[] description = tupleDescription(name, accessor);
638        String[] names = (String[])(description[0]);
639        Class[]  types = (Class[] )(description[1]);
640        if (names0.length > names.length) {
641          return false;
642          }
643        boolean exists;
644        for (int i = 0; i < names0.length; i++) {
645          exists = false;
646          for (int j = 0; j < names.length; j++) {
647            if (!names[j].equals(names0[i]) || types[j] == classes0[i]) {
648              exists = true;
649              }
650            }
651          if (!exists) {
652            return false;
653            }
654          }
655        return true;
656        }
657    
658      /** Verify if NTuple (table) exists in the DB.
659        * @param  name The name of the existing {@link ITuple}.
660        * @return      <code>true</code> if The {@link ITuple} exists.
661        * @throws SQLTupleException if the information can't be obtained. */
662      private boolean tableExists(String name) throws SQLTupleException {
663        if (_accessor.supportsOnlyUpperCase()) {
664          name = name.toUpperCase();
665          }
666        boolean exists = false;
667        try {
668          DatabaseMetaData dbmd = _accessor.connection().getMetaData();
669          ResultSet rs = dbmd.getColumns(null, null, name, null);
670          if (rs != null) {
671            while (rs.next()) {
672              exists = true;
673              break;
674              }
675                  }
676          }
677              catch (SQLTupleException e) {
678          throw new SQLTupleException("Can't get table information for table " + name, e); 
679          }
680              catch (SQLException e) {
681          throw new SQLTupleException("Can't get table information for table " + name, e); 
682          }
683        return exists;
684        }
685    
686      /** Create a filter from a expression. Currently, SQL expresion
687        * should be used within {@link SQLTuple}.
688        * @param  expression The algebraic (or SQL in case of {@link SQLTuple}) expression.
689        * @return            The created {@link IFilter}. */
690      public IFilter createFilter(String expression) {
691        return new SQLFilter(expression);
692        }
693    
694      /** Create a filter from a expression and a number of rows to be processed.
695        * Currently, SQL expresion should be used within {@link SQLTuple} and all rows are
696        * processed in {@link SQLTuple}.
697        * @param expression    The algebraic (or SQL in case of {@link SQLTuple}) expression.
698        * @param rowsToProcess The number of rows to be processed (ignoed for {@link SQLTuple}).
699        * @return              The created {@link IFilter}. */
700      public IFilter createFilter(String expression, int rowsToProcess) {
701        return new SQLFilter(expression, rowsToProcess);
702        }
703    
704      /** Create a filter from a expression and, number of rows to be processed
705        * and thye starting row. Currently, SQL expresion should be used within 
706        * {@link SQLTuple} and all rows are processed in {@link SQLTuple}.
707        * @param expression    The algebraic (or SQL in case of {@link SQLTuple}) expression.
708        * @param rowsToProcess The number of rows to be processed (ignoed for {@link SQLTuple}).
709        * @param startingRow   The starting row to be processed (ignoed for {@link SQLTuple}).
710        * @return              The created {@link IFilter}. */
711      public IFilter createFilter(String expression, int rowsToProcess, int startingRow) {
712        return new SQLFilter(expression, rowsToProcess, startingRow);
713        }
714    
715      /** Create an evaluator from a expression. Currently, SQL expresion
716        * should be used within {@link SQLTuple}.
717        * @param expression The algebraic (or SQL in case of {@link SQLTuple}) expression.
718        * @return           The created {@link IEvaluator}. */
719      public IEvaluator createEvaluator(String expression) {
720        return new SQLEvaluator(expression);
721        }
722    
723      /** Delete NTuple (table) from DB if it exists. Remove it from {@link ITree}.
724        * @param path The path to {@link ITuple} to be deleted. */
725      public void delete(String path) throws SQLTupleException {
726        log.debug("Deleting SQLTuple in " + path);
727        try {
728          if (tableExists(nameInPath(path))) {
729            String[] args = {nameInPath(path)};
730            _stmtSrc.update("dropTable", args);
731            tree.rm(nameInPath(path));
732            }
733          }
734        catch (IllegalArgumentException e) {
735          log.debug("NTuple " + path + " can't be removed as it doesn't exist");
736          }
737        catch (SQLTupleException e) {
738          throw new SQLTupleException("Couldn't delete table " + path, e);
739          }
740        }
741    
742      /** Replace numeric classes with primitive types where possible.
743        * @param cl The array of {@link Class}es representing types,
744        *           numeric {@link Class}es are replaced with prmitive
745        *           types where possible.
746        * @return   The input array with numeric {@link Class}es replaced
747        *           with primitive types where possible. */
748      private Class[] unbox(Class[] cl) {
749        for (int i = 0; i < cl.length; i++) {
750               if (cl[i] == Boolean.class  ) cl[i] = Boolean.TYPE;
751          else if (cl[i] == Byte.class     ) cl[i] = Byte.TYPE;
752          else if (cl[i] == Character.class) cl[i] = Character.TYPE;
753          else if (cl[i] == Short.class    ) cl[i] = Short.TYPE;
754          else if (cl[i] == Integer.class  ) cl[i] = Integer.TYPE;
755          else if (cl[i] == Long.class     ) cl[i] = Long.TYPE;
756          else if (cl[i] == Float.class    ) cl[i] = Float.TYPE;
757          else if (cl[i] == Double.class   ) cl[i] = Double.TYPE;
758          }
759        return cl;
760        }
761    
762      /** Do not test for compatibility if existing table is reused.
763        * This is dangerous. */
764      public void skipCompatibilityTest() {
765        log.warn("Compatibility test will be skipped - it is dangerous");
766        _test = false;
767        }
768    
769      /** Register options. 
770        * @param options The options to be associated with this {@link hep.aida.ITupleFactory}. 
771        * @throws SQLTupleException if anything goes wrong. */
772      public void setOptions(String options) throws SQLTupleException {
773        _options = options;
774        _accessor = Accessor.getAccessor(options);
775        _stmtSrc = new StmtSrc(_accessor, "");
776        }
777    
778      /** Give associated {@link Accessor}. 
779        * @return The associated {@link Accessor}. */
780      public Accessor accessor() {
781        return _accessor;
782        }
783    
784      /** Give associated {@link StmtSrc}. 
785        * @return The associated {@link StmtSrc}. */
786      public StmtSrc stmtSrc() {
787        return _stmtSrc;
788        }
789    
790      private String nameInPath(String path) {
791        int index = path.lastIndexOf(separatorChar);
792        if (index == -1) {
793          return path;
794          }
795        return path.substring(index + 1);
796        }
797    
798      private String parentPath(String path) {
799        int index = path.lastIndexOf(separatorChar);
800        if (index == -1) {
801          return null;
802          }
803        return path.substring(0, index);
804        }
805    
806      // BUG: exceptions are different for file and resource
807      /** Read (meta)data description from cache:
808        * <ul>
809        * <li>It tries first local file cache (default is <code>.SQLTuple.descriptions</code></li>
810        * <li>Then it reads resource <code>hep/aida/ref/sql/SQLTuple.descriptions</code></li>
811        * </ul> */
812      private void readCache() {
813        try {
814          InputStream is;
815          File file = new File(_cacheFile);
816          if (file.canRead()) {
817            is = new FileInputStream(_cacheFile);
818            log.info("Reading cache from " + _cacheFile);
819            }
820          else {
821            is = getClass().getClassLoader().getResourceAsStream(_cacheResource);
822            log.info("Reading cache from " + _cacheResource);
823            }
824          if (is != null) {
825            ObjectInputStream ois = new ObjectInputStream(is);
826            Object o = ois.readObject();
827            if (o instanceof Map) {
828              _descriptions = (Map<String, Object[]>)o;
829              }
830            else {
831            log.warn("Don't understand " + _cacheFile);
832              }
833            }
834          else {
835            log.debug("Can't find " + _cacheFile);
836            }
837          }
838        catch(AccessControlException e) {
839          log.warn("Can't access " + _cacheFile, e);
840          }
841        catch(FileNotFoundException e) {
842          log.debug("Can't find " + _cacheFile, e);
843          }
844        catch(IOException e) {
845          log.warn("Can't read " + _cacheFile, e);
846          }
847        catch(ClassNotFoundException e) {
848          log.warn("Don't understant " + _cacheFile, e);
849          }
850        }
851    
852      /** Save current (meta)data descriptions into file cache 
853        * (default is <code>.SQLTuple.descriptions</code>). */
854      public static void saveCache() {
855        try {
856          log.info("Writing cache to " + _cacheFile);
857          for (String key : _descriptions.keySet()) {
858            log.debug(">   " + key);
859            }
860          FileOutputStream fos = new FileOutputStream(_cacheFile);
861          ObjectOutputStream oos = new ObjectOutputStream(fos);
862          oos.writeObject(_descriptions);
863          }
864        catch(FileNotFoundException e) {
865          log.warn("Can't find " + _cacheFile, e);
866          }
867        catch(IOException e) {
868          log.warn("Can't write " + _cacheFile, e);
869          }
870        }
871    
872      /** Set (meta)data descriptions cache file anme.
873        * @param cacheFile The name of the cache file,
874        *                  default is <code>.SQLTuple.descriptions</code>. */
875      public static void setCache(String cacheFile) {
876        _cacheFile = cacheFile;
877        }
878    
879      private Tree tree;
880    
881      private final static char separatorChar = '/';
882    
883      private StmtSrc _stmtSrc;
884    
885      private Accessor _accessor;
886    
887      private String _options;
888      
889      private boolean _test = true;
890    
891      private static Map<String, Object[]> _descriptions = new HashMap<String, Object[]>();
892    
893      private static String _cacheFile = ".SQLTuple.descriptions";
894    
895      private static String _cacheResource = "hep/aida/ref/sql/SQLTuple.descriptions";
896    
897      /** Logging . */
898      private static Logger log = Logger.getLogger(SQLTupleFactory.class);
899    
900      }