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.
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.
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.
18
* DatabaseResultListener.java
19
* Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
24
package weka.experiment;
26
import weka.core.FastVector;
28
import java.sql.DatabaseMetaData;
29
import java.sql.ResultSet;
32
<!-- globalinfo-start -->
33
* Takes results from a result producer and sends them to a database.
35
<!-- globalinfo-end -->
37
* @author Len Trigg (trigg@cs.waikato.ac.nz)
38
* @version $Revision: 1.13 $
40
public class DatabaseResultListener
42
implements ResultListener {
44
/** for serialization */
45
static final long serialVersionUID = 7388014746954652818L;
47
/** The ResultProducer to listen to */
48
protected ResultProducer m_ResultProducer;
50
/** The name of the current results table */
51
protected String m_ResultsTableName;
53
/** True if debugging output should be printed */
54
protected boolean m_Debug = true;
56
/** Holds the name of the key field to cache upon, or null if no caching */
57
protected String m_CacheKeyName = "";
59
/** Stores the index of the key column holding the cache key data */
60
protected int m_CacheKeyIndex;
62
/** Stores the key for which the cache is valid */
63
protected Object [] m_CacheKey;
65
/** Stores the cached values */
66
protected FastVector m_Cache = new FastVector();
70
* Returns a string describing this result listener
71
* @return a description of the result listener suitable for
72
* displaying in the explorer/experimenter gui
74
public String globalInfo() {
75
return "Takes results from a result producer and sends them to a "
80
* Sets up the database drivers
82
* @throws Exception if an error occurs
84
public DatabaseResultListener() throws Exception {
90
* Prepare for the results to be received.
92
* @param rp the ResultProducer that will generate the results
93
* @throws Exception if an error occurs during preprocessing.
95
public void preProcess(ResultProducer rp) throws Exception {
97
m_ResultProducer = rp;
98
// Connect to the database and find out what table corresponds to this
100
updateResultsTableName(m_ResultProducer);
104
* Perform any postprocessing. When this method is called, it indicates
105
* that no more results will be sent that need to be grouped together
108
* @param rp the ResultProducer that generated the results
109
* @throws Exception if an error occurs
111
public void postProcess(ResultProducer rp) throws Exception {
113
if (m_ResultProducer != rp) {
114
throw new Error("Unrecognized ResultProducer calling postProcess!!");
116
disconnectFromDatabase();
120
* Determines if there are any constraints (imposed by the
121
* destination) on any additional measures produced by
122
* resultProducers. Null should be returned if there are NO
123
* constraints, otherwise a list of column names should be
124
* returned as an array of Strings. In the case of
125
* DatabaseResultListener, the structure of an existing database
126
* will impose constraints.
127
* @param rp the ResultProducer to which the constraints will apply
128
* @return an array of column names to which resutltProducer's
129
* results will be restricted.
130
* @throws Exception if an error occurs.
132
public String [] determineColumnConstraints(ResultProducer rp)
134
FastVector cNames = new FastVector();
135
updateResultsTableName(rp);
136
DatabaseMetaData dbmd = m_Connection.getMetaData();
138
// gets a result set where each row is info on a column
139
if (m_checkForUpperCaseNames) {
140
rs = dbmd.getColumns(null, null, m_ResultsTableName.toUpperCase(), null);
142
rs = dbmd.getColumns(null, null, m_ResultsTableName, null);
144
boolean tableExists=false;
149
// column four contains the column name
150
if (rs.getString(4).toLowerCase().startsWith("measure")) {
152
cNames.addElement(rs.getString(4));
156
// no constraints on any additional measures if the table does not exist
161
// a zero element array indicates maximum constraint
162
String [] columnNames = new String [numColumns];
163
for (int i=0;i<numColumns;i++) {
164
columnNames[i] = (String)(cNames.elementAt(i));
171
* Submit the result to the appropriate table of the database
173
* @param rp the ResultProducer that generated the result
174
* @param key The key for the results.
175
* @param result The actual results.
176
* @throws Exception if the result couldn't be sent to the database
178
public void acceptResult(ResultProducer rp, Object[] key, Object[] result)
181
if (m_ResultProducer != rp) {
182
throw new Error("Unrecognized ResultProducer calling acceptResult!!");
185
// null result could occur from a chain of doRunKeys calls
186
if (result != null) {
187
putResultInTable(m_ResultsTableName, rp, key, result);
192
* Always says a result is required. If this is the first call,
193
* prints out the header for the Database output.
195
* @param rp the ResultProducer wanting to generate the result
196
* @param key The key for which a result may be needed.
197
* @return true if the result should be calculated.
198
* @throws Exception if the database couldn't be queried
200
public boolean isResultRequired(ResultProducer rp, Object[] key)
203
if (m_ResultProducer != rp) {
204
throw new Error("Unrecognized ResultProducer calling isResultRequired!");
207
System.err.print("Is result required...");
208
for (int i = 0; i < key.length; i++) {
209
System.err.print(" " + key[i]);
213
boolean retval = false;
215
// Check the key cache first
216
if (!m_CacheKeyName.equals("")) {
217
if (!isCacheValid(key)) {
220
retval = !isKeyInCache(rp, key);
222
// Ask whether the results are needed
223
retval = !isKeyInTable(m_ResultsTableName,
228
System.err.println(" ..." + (retval ? "required" : "not required")
229
+ (m_CacheKeyName.equals("") ? "" : " (cache)"));
237
* Determines the table name that results will be inserted into. If
238
* required: a connection will be opened, an experiment index table created,
239
* and the results table created.
241
* @param rp the ResultProducer
242
* @throws Exception if an error occurs
244
protected void updateResultsTableName(ResultProducer rp) throws Exception {
246
if (!isConnected()) {
249
if (!experimentIndexExists()) {
250
createExperimentIndex();
253
String tableName = getResultsTableName(rp);
254
if (tableName == null) {
255
tableName = createExperimentIndexEntry(rp);
257
if (!tableExists(tableName)) {
258
createResultsTable(rp, tableName);
260
m_ResultsTableName = tableName;
264
* Returns the tip text for this property
265
* @return tip text for this property suitable for
266
* displaying in the explorer/experimenter gui
268
public String cacheKeyNameTipText() {
269
return "Set the name of the key field by which to cache.";
273
* Get the value of CacheKeyName.
275
* @return Value of CacheKeyName.
277
public String getCacheKeyName() {
279
return m_CacheKeyName;
284
* Set the value of CacheKeyName.
286
* @param newCacheKeyName Value to assign to CacheKeyName.
288
public void setCacheKeyName(String newCacheKeyName) {
290
m_CacheKeyName = newCacheKeyName;
296
* Checks whether the current cache contents are valid for the supplied
299
* @param key the results key
300
* @return true if the cache contents are valid for the key given
302
protected boolean isCacheValid(Object []key) {
304
if (m_CacheKey == null) {
307
if (m_CacheKey.length != key.length) {
310
for (int i = 0; i < key.length; i++) {
311
if ((i != m_CacheKeyIndex) && (!m_CacheKey[i].equals(key[i]))) {
319
* Returns true if the supplied key is in the key cache (and thus
320
* we do not need to execute a database query).
322
* @param rp the ResultProducer the key belongs to.
323
* @param key the result key
324
* @return true if the key is in the key cache
325
* @throws Exception if an error occurs
327
protected boolean isKeyInCache(ResultProducer rp, Object[] key)
330
for (int i = 0; i < m_Cache.size(); i++) {
331
if (m_Cache.elementAt(i).equals(key[m_CacheKeyIndex])) {
339
* Executes a database query to fill the key cache
341
* @param rp the ResultProducer the key belongs to
343
* @throws Exception if an error occurs
345
protected void loadCache(ResultProducer rp, Object[] key)
348
System.err.print(" (updating cache)"); System.err.flush();
349
m_Cache.removeAllElements();
351
String query = "SELECT Key_" + m_CacheKeyName
352
+ " FROM " + m_ResultsTableName;
353
String [] keyNames = rp.getKeyNames();
354
if (keyNames.length != key.length) {
355
throw new Exception("Key names and key values of different lengths");
357
m_CacheKeyIndex = -1;
358
for (int i = 0; i < keyNames.length; i++) {
359
if (keyNames[i].equalsIgnoreCase(m_CacheKeyName)) {
364
if (m_CacheKeyIndex == -1) {
365
throw new Exception("No key field named " + m_CacheKeyName
366
+ " (as specified for caching)");
368
boolean first = true;
369
for (int i = 0; i < key.length; i++) {
370
if ((key[i] != null) && (i != m_CacheKeyIndex)) {
377
query += "Key_" + keyNames[i] + '=';
378
if (key[i] instanceof String) {
379
query += "'" + DatabaseUtils.processKeyString(key[i].toString()) + "'";
381
query += key[i].toString();
385
ResultSet rs = select(query);
387
String keyVal = rs.getString(1);
389
m_Cache.addElement(keyVal);
393
m_CacheKey = (Object [])key.clone();