source: src/main/java/weka/experiment/InstanceQuery.java @ 15

Last change on this file since 15 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 17.0 KB
RevLine 
[4]1/*
2 *    This program is free software; you can redistribute it and/or modify
3 *    it under the terms of the GNU General Public License as published by
4 *    the Free Software Foundation; either version 2 of the License, or
5 *    (at your option) any later version.
6 *
7 *    This program is distributed in the hope that it will be useful,
8 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
9 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 *    GNU General Public License for more details.
11 *
12 *    You should have received a copy of the GNU General Public License
13 *    along with this program; if not, write to the Free Software
14 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
15 */
16
17/*
18 *    InstanceQuery.java
19 *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
20 *
21 */
22
23package weka.experiment;
24
25import weka.core.Attribute;
26import weka.core.FastVector;
27import weka.core.Instance;
28import weka.core.DenseInstance;
29import weka.core.Instances;
30import weka.core.Option;
31import weka.core.OptionHandler;
32import weka.core.RevisionUtils;
33import weka.core.SparseInstance;
34import weka.core.Utils;
35
36import java.sql.ResultSet;
37import java.sql.ResultSetMetaData;
38import java.sql.Time;
39import java.util.Date;
40import java.util.Enumeration;
41import java.util.Hashtable;
42import java.util.Vector;
43
44/**
45 * Convert the results of a database query into instances. The jdbc
46 * driver and database to be used default to "jdbc.idbDriver" and
47 * "jdbc:idb=experiments.prp". These may be changed by creating
48 * a java properties file called DatabaseUtils.props in user.home or
49 * the current directory. eg:<p>
50 *
51 * <code><pre>
52 * jdbcDriver=jdbc.idbDriver
53 * jdbcURL=jdbc:idb=experiments.prp
54 * </pre></code><p>
55 *
56 * Command line use just outputs the instances to System.out. <p/>
57 *
58 <!-- options-start -->
59 * Valid options are: <p/>
60 *
61 * <pre> -Q &lt;query&gt;
62 *  SQL query to execute.</pre>
63 *
64 * <pre> -S
65 *  Return sparse rather than normal instances.</pre>
66 *
67 * <pre> -U &lt;username&gt;
68 *  The username to use for connecting.</pre>
69 *
70 * <pre> -P &lt;password&gt;
71 *  The password to use for connecting.</pre>
72 *
73 * <pre> -D
74 *  Enables debug output.</pre>
75 *
76 <!-- options-end -->
77 *
78 * @author Len Trigg (trigg@cs.waikato.ac.nz)
79 * @version $Revision: 5987 $
80 */
81public class InstanceQuery 
82  extends DatabaseUtils
83  implements OptionHandler {
84 
85  /** for serialization */
86  static final long serialVersionUID = 718158370917782584L;
87
88  /** Determines whether sparse data is created */
89  boolean m_CreateSparseData = false;
90 
91  /** Query to execute */
92  String m_Query = "SELECT * from ?";
93
94  /**
95   * Sets up the database drivers
96   *
97   * @throws Exception if an error occurs
98   */
99  public InstanceQuery() throws Exception {
100
101    super();
102  }
103
104  /**
105   * Returns an enumeration describing the available options <p>
106   *
107   * @return an enumeration of all options
108   */
109   public Enumeration listOptions () {
110     Vector result = new Vector();
111
112     result.addElement(
113         new Option("\tSQL query to execute.",
114                    "Q",1,"-Q <query>"));
115     
116     result.addElement(
117         new Option("\tReturn sparse rather than normal instances.", 
118                    "S", 0, "-S"));
119     
120     result.addElement(
121         new Option("\tThe username to use for connecting.", 
122                    "U", 1, "-U <username>"));
123     
124     result.addElement(
125         new Option("\tThe password to use for connecting.", 
126                    "P", 1, "-P <password>"));
127     
128     result.addElement(
129         new Option("\tEnables debug output.", 
130                    "D", 0, "-D"));
131     
132     return result.elements();
133   }
134
135  /**
136   * Parses a given list of options.
137   *
138   <!-- options-start -->
139   * Valid options are: <p/>
140   *
141   * <pre> -Q &lt;query&gt;
142   *  SQL query to execute.</pre>
143   *
144   * <pre> -S
145   *  Return sparse rather than normal instances.</pre>
146   *
147   * <pre> -U &lt;username&gt;
148   *  The username to use for connecting.</pre>
149   *
150   * <pre> -P &lt;password&gt;
151   *  The password to use for connecting.</pre>
152   *
153   * <pre> -D
154   *  Enables debug output.</pre>
155   *
156   <!-- options-end -->
157   *
158   * @param options the list of options as an array of strings
159   * @throws Exception if an option is not supported
160   */
161  public void setOptions (String[] options)
162    throws Exception {
163
164    String      tmpStr;
165   
166    setSparseData(Utils.getFlag('S',options));
167
168    tmpStr = Utils.getOption('Q',options);
169    if (tmpStr.length() != 0)
170      setQuery(tmpStr);
171
172    tmpStr = Utils.getOption('U',options);
173    if (tmpStr.length() != 0)
174      setUsername(tmpStr);
175
176    tmpStr = Utils.getOption('P',options);
177    if (tmpStr.length() != 0)
178      setPassword(tmpStr);
179
180    setDebug(Utils.getFlag('D',options));
181  }
182
183  /**
184   * Returns the tip text for this property
185   * @return tip text for this property suitable for
186   * displaying in the explorer/experimenter gui
187   */
188  public String queryTipText() {
189    return "The SQL query to execute against the database.";
190  }
191 
192  /**
193   * Set the query to execute against the database
194   * @param q the query to execute
195   */
196  public void setQuery(String q) {
197    m_Query = q;
198  }
199
200  /**
201   * Get the query to execute against the database
202   * @return the query
203   */
204  public String getQuery() {
205    return m_Query;
206  }
207
208  /**
209   * Returns the tip text for this property
210   * @return tip text for this property suitable for
211   * displaying in the explorer/experimenter gui
212   */
213  public String sparseDataTipText() {
214    return "Encode data as sparse instances.";
215  }
216
217  /**
218   * Sets whether data should be encoded as sparse instances
219   * @param s true if data should be encoded as a set of sparse instances
220   */
221  public void setSparseData(boolean s) {
222    m_CreateSparseData = s;
223  }
224
225  /**
226   * Gets whether data is to be returned as a set of sparse instances
227   * @return true if data is to be encoded as sparse instances
228   */
229  public boolean getSparseData() {
230    return m_CreateSparseData;
231  }
232
233  /**
234   * Gets the current settings of InstanceQuery
235   *
236   * @return an array of strings suitable for passing to setOptions()
237   */
238  public String[] getOptions () {
239
240    Vector options = new Vector();
241
242    options.add("-Q"); 
243    options.add(getQuery());
244 
245    if (getSparseData())
246      options.add("-S");
247
248    if (!getUsername().equals("")) {
249      options.add("-U");
250      options.add(getUsername());
251    }
252
253    if (!getPassword().equals("")) {
254      options.add("-P");
255      options.add(getPassword());
256    }
257
258    if (getDebug())
259      options.add("-D");
260
261    return (String[]) options.toArray(new String[options.size()]);
262  }
263
264  /**
265   * Makes a database query using the query set through the -Q option
266   * to convert a table into a set of instances
267   *
268   * @return the instances contained in the result of the query
269   * @throws Exception if an error occurs
270   */
271  public Instances retrieveInstances() throws Exception {
272    return retrieveInstances(m_Query);
273  }
274
275  /**
276   * Makes a database query to convert a table into a set of instances
277   *
278   * @param query the query to convert to instances
279   * @return the instances contained in the result of the query, NULL if the
280   *         SQL query doesn't return a ResultSet, e.g., DELETE/INSERT/UPDATE
281   * @throws Exception if an error occurs
282   */
283  public Instances retrieveInstances(String query) throws Exception {
284
285    if (m_Debug) 
286      System.err.println("Executing query: " + query);
287    connectToDatabase();
288    if (execute(query) == false) {
289      if (m_PreparedStatement.getUpdateCount() == -1) {
290        throw new Exception("Query didn't produce results");
291      }
292      else {
293        if (m_Debug) 
294          System.err.println(m_PreparedStatement.getUpdateCount() 
295              + " rows affected.");
296        close();
297        return null;
298      }
299    }
300    ResultSet rs = getResultSet();
301    if (m_Debug) 
302      System.err.println("Getting metadata...");
303    ResultSetMetaData md = rs.getMetaData();
304    if (m_Debug) 
305      System.err.println("Completed getting metadata...");
306   
307   
308    // Determine structure of the instances
309    int numAttributes = md.getColumnCount();
310    int [] attributeTypes = new int [numAttributes];
311    Hashtable [] nominalIndexes = new Hashtable [numAttributes];
312    FastVector [] nominalStrings = new FastVector [numAttributes];
313    for (int i = 1; i <= numAttributes; i++) {
314      /* switch (md.getColumnType(i)) {
315      case Types.CHAR:
316      case Types.VARCHAR:
317      case Types.LONGVARCHAR:
318      case Types.BINARY:
319      case Types.VARBINARY:
320      case Types.LONGVARBINARY:*/
321     
322      switch (translateDBColumnType(md.getColumnTypeName(i))) {
323       
324      case STRING :
325        //System.err.println("String --> nominal");
326        attributeTypes[i - 1] = Attribute.NOMINAL;
327        nominalIndexes[i - 1] = new Hashtable();
328        nominalStrings[i - 1] = new FastVector();
329        break;
330      case TEXT:
331        //System.err.println("Text --> string");
332        attributeTypes[i - 1] = Attribute.STRING;
333        nominalIndexes[i - 1] = new Hashtable();
334        nominalStrings[i - 1] = new FastVector();
335        break;
336      case BOOL:
337        //System.err.println("boolean --> nominal");
338        attributeTypes[i - 1] = Attribute.NOMINAL;
339        nominalIndexes[i - 1] = new Hashtable();
340        nominalIndexes[i - 1].put("false", new Double(0));
341        nominalIndexes[i - 1].put("true", new Double(1));
342        nominalStrings[i - 1] = new FastVector();
343        nominalStrings[i - 1].addElement("false");
344        nominalStrings[i - 1].addElement("true");
345        break;
346      case DOUBLE:
347        //System.err.println("BigDecimal --> numeric");
348        attributeTypes[i - 1] = Attribute.NUMERIC;
349        break;
350      case BYTE:
351        //System.err.println("byte --> numeric");
352        attributeTypes[i - 1] = Attribute.NUMERIC;
353        break;
354      case SHORT:
355        //System.err.println("short --> numeric");
356        attributeTypes[i - 1] = Attribute.NUMERIC;
357        break;
358      case INTEGER:
359        //System.err.println("int --> numeric");
360        attributeTypes[i - 1] = Attribute.NUMERIC;
361        break;
362      case LONG:
363        //System.err.println("long --> numeric");
364        attributeTypes[i - 1] = Attribute.NUMERIC;
365        break;
366      case FLOAT:
367        //System.err.println("float --> numeric");
368        attributeTypes[i - 1] = Attribute.NUMERIC;
369        break;
370      case DATE:
371        attributeTypes[i - 1] = Attribute.DATE;
372        break;
373      case TIME:
374        attributeTypes[i - 1] = Attribute.DATE;
375        break;
376      default:
377        //System.err.println("Unknown column type");
378        attributeTypes[i - 1] = Attribute.STRING;
379      }
380    }
381
382    // For sqlite
383    // cache column names because the last while(rs.next()) { iteration for
384    // the tuples below will close the md object: 
385    Vector<String> columnNames = new Vector<String>(); 
386    for (int i = 0; i < numAttributes; i++) {
387      columnNames.add(md.getColumnName(i + 1));
388    }
389
390    // Step through the tuples
391    if (m_Debug) 
392      System.err.println("Creating instances...");
393    FastVector instances = new FastVector();
394    int rowCount = 0;
395    while(rs.next()) {
396      if (rowCount % 100 == 0) {
397        if (m_Debug)  {
398          System.err.print("read " + rowCount + " instances \r");
399          System.err.flush();
400        }
401      }
402      double[] vals = new double[numAttributes];
403      for(int i = 1; i <= numAttributes; i++) {
404        /*switch (md.getColumnType(i)) {
405        case Types.CHAR:
406        case Types.VARCHAR:
407        case Types.LONGVARCHAR:
408        case Types.BINARY:
409        case Types.VARBINARY:
410        case Types.LONGVARBINARY:*/
411        switch (translateDBColumnType(md.getColumnTypeName(i))) {
412        case STRING :
413          String str = rs.getString(i);
414         
415          if (rs.wasNull()) {
416            vals[i - 1] = Utils.missingValue();
417          } else {
418            Double index = (Double)nominalIndexes[i - 1].get(str);
419            if (index == null) {
420              index = new Double(nominalStrings[i - 1].size());
421              nominalIndexes[i - 1].put(str, index);
422              nominalStrings[i - 1].addElement(str);
423            }
424            vals[i - 1] = index.doubleValue();
425          }
426          break;
427        case TEXT:
428          String txt = rs.getString(i);
429         
430          if (rs.wasNull()) {
431            vals[i - 1] = Utils.missingValue();
432          } else {
433            Double index = (Double)nominalIndexes[i - 1].get(txt);
434            if (index == null) {
435              index = new Double(nominalStrings[i - 1].size());
436              nominalIndexes[i - 1].put(txt, index);
437              nominalStrings[i - 1].addElement(txt);
438            }
439            vals[i - 1] = index.doubleValue();
440          }
441          break;
442        case BOOL:
443          boolean boo = rs.getBoolean(i);
444          if (rs.wasNull()) {
445            vals[i - 1] = Utils.missingValue();
446          } else {
447            vals[i - 1] = (boo ? 1.0 : 0.0);
448          }
449          break;
450        case DOUBLE:
451          //      BigDecimal bd = rs.getBigDecimal(i, 4);
452          double dd = rs.getDouble(i);
453          // Use the column precision instead of 4?
454          if (rs.wasNull()) {
455            vals[i - 1] = Utils.missingValue();
456          } else {
457            //      newInst.setValue(i - 1, bd.doubleValue());
458            vals[i - 1] =  dd;
459          }
460          break;
461        case BYTE:
462          byte by = rs.getByte(i);
463          if (rs.wasNull()) {
464            vals[i - 1] = Utils.missingValue();
465          } else {
466            vals[i - 1] = (double)by;
467          }
468          break;
469        case SHORT:
470          short sh = rs.getShort(i);
471          if (rs.wasNull()) {
472            vals[i - 1] = Utils.missingValue();
473          } else {
474            vals[i - 1] = (double)sh;
475          }
476          break;
477        case INTEGER:
478          int in = rs.getInt(i);
479          if (rs.wasNull()) {
480            vals[i - 1] = Utils.missingValue();
481          } else {
482            vals[i - 1] = (double)in;
483          }
484          break;
485        case LONG:
486          long lo = rs.getLong(i);
487          if (rs.wasNull()) {
488            vals[i - 1] = Utils.missingValue();
489          } else {
490            vals[i - 1] = (double)lo;
491          }
492          break;
493        case FLOAT:
494          float fl = rs.getFloat(i);
495          if (rs.wasNull()) {
496            vals[i - 1] = Utils.missingValue();
497          } else {
498            vals[i - 1] = (double)fl;
499          }
500          break;
501        case DATE:
502          Date date = rs.getDate(i);
503          if (rs.wasNull()) {
504            vals[i - 1] = Utils.missingValue();
505          } else {
506            // TODO: Do a value check here.
507            vals[i - 1] = (double)date.getTime();
508          }
509          break;
510        case TIME:
511          Time time = rs.getTime(i);
512          if (rs.wasNull()) {
513            vals[i - 1] = Utils.missingValue();
514          } else {
515            // TODO: Do a value check here.
516            vals[i - 1] = (double) time.getTime();
517          }
518          break;
519        default:
520          vals[i - 1] = Utils.missingValue();
521        }
522      }
523      Instance newInst;
524      if (m_CreateSparseData) {
525        newInst = new SparseInstance(1.0, vals);
526      } else {
527        newInst = new DenseInstance(1.0, vals);
528      }
529      instances.addElement(newInst);
530      rowCount++;
531    }
532    //disconnectFromDatabase();  (perhaps other queries might be made)
533   
534    // Create the header and add the instances to the dataset
535    if (m_Debug) 
536      System.err.println("Creating header...");
537    FastVector attribInfo = new FastVector();
538    for (int i = 0; i < numAttributes; i++) {
539      /* Fix for databases that uppercase column names */
540      // String attribName = attributeCaseFix(md.getColumnName(i + 1));
541      String attribName = attributeCaseFix(columnNames.get(i));
542      switch (attributeTypes[i]) {
543      case Attribute.NOMINAL:
544        attribInfo.addElement(new Attribute(attribName, nominalStrings[i]));
545        break;
546      case Attribute.NUMERIC:
547        attribInfo.addElement(new Attribute(attribName));
548        break;
549      case Attribute.STRING:
550        Attribute att = new Attribute(attribName, (FastVector) null);
551        attribInfo.addElement(att);
552        for (int n = 0; n < nominalStrings[i].size(); n++) {
553          att.addStringValue((String) nominalStrings[i].elementAt(n));
554        }
555        break;
556      case Attribute.DATE:
557        attribInfo.addElement(new Attribute(attribName, (String)null));
558        break;
559      default:
560        throw new Exception("Unknown attribute type");
561      }
562    }
563    Instances result = new Instances("QueryResult", attribInfo, 
564                                     instances.size());
565    for (int i = 0; i < instances.size(); i++) {
566      result.add((Instance)instances.elementAt(i));
567    }
568    close(rs);
569   
570    return result;
571  }
572
573  /**
574   * Test the class from the command line. The instance
575   * query should be specified with -Q sql_query
576   *
577   * @param args contains options for the instance query
578   */
579  public static void main(String args[]) {
580
581    try {
582      InstanceQuery iq = new InstanceQuery();
583      String query = Utils.getOption('Q', args);
584      if (query.length() == 0) {
585        iq.setQuery("select * from Experiment_index");
586      } else {
587        iq.setQuery(query);
588      }
589      iq.setOptions(args);
590      try {
591        Utils.checkForRemainingOptions(args);
592      } catch (Exception e) {
593        System.err.println("Options for weka.experiment.InstanceQuery:\n");
594        Enumeration en = iq.listOptions();
595        while (en.hasMoreElements()) {
596          Option o = (Option)en.nextElement();
597          System.err.println(o.synopsis()+"\n"+o.description());
598        }
599        System.exit(1);
600      }
601     
602      Instances aha = iq.retrieveInstances();
603      iq.disconnectFromDatabase();
604      // query returned no result -> exit
605      if (aha == null)
606        return;
607      // The dataset may be large, so to make things easier we'll
608      // output an instance at a time (rather than having to convert
609      // the entire dataset to one large string)
610      System.out.println(new Instances(aha, 0));
611      for (int i = 0; i < aha.numInstances(); i++) {
612        System.out.println(aha.instance(i));
613      }
614    } catch(Exception e) {
615      e.printStackTrace();
616      System.err.println(e.getMessage());
617    }
618  }
619 
620  /**
621   * Returns the revision string.
622   *
623   * @return            the revision
624   */
625  public String getRevision() {
626    return RevisionUtils.extract("$Revision: 5987 $");
627  }
628}
Note: See TracBrowser for help on using the repository browser.