Author: srowen
Date: Tue Nov  9 14:01:57 2010
New Revision: 1032985

URL: http://svn.apache.org/viewvc?rev=1032985&view=rev
Log:
Experimental PostgreSQL support

Added:
    
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/PostgreSQLBooleanPrefJDBCDataModel.java
      - copied, changed from r1032231, 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/MySQLBooleanPrefJDBCDataModel.java
    
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/PostgreSQLJDBCDataModel.java
      - copied, changed from r1032231, 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/MySQLJDBCDataModel.java
Modified:
    
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/AbstractBooleanPrefJDBCDataModel.java
    
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/AbstractJDBCDataModel.java
    
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/MySQLBooleanPrefJDBCDataModel.java
    mahout/trunk/core/src/main/java/org/apache/mahout/common/IOUtils.java

Modified: 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/AbstractBooleanPrefJDBCDataModel.java
URL: 
http://svn.apache.org/viewvc/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/AbstractBooleanPrefJDBCDataModel.java?rev=1032985&r1=1032984&r2=1032985&view=diff
==============================================================================
--- 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/AbstractBooleanPrefJDBCDataModel.java
 (original)
+++ 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/AbstractBooleanPrefJDBCDataModel.java
 Tue Nov  9 14:01:57 2010
@@ -36,7 +36,9 @@ import com.google.common.base.Preconditi
 public abstract class AbstractBooleanPrefJDBCDataModel extends 
AbstractJDBCDataModel {
   
   private static final Logger log = 
LoggerFactory.getLogger(AbstractBooleanPrefJDBCDataModel.class);
-  
+
+  static final String NO_SUCH_COLUMN = "NO_SUCH_COLUMN";
+
   private final String setPreferenceSQL;
   
   protected AbstractBooleanPrefJDBCDataModel(DataSource dataSource,
@@ -86,6 +88,11 @@ public abstract class AbstractBooleanPre
   protected Preference buildPreference(ResultSet rs) throws SQLException {
     return new BooleanPreference(getLongColumn(rs, 1), getLongColumn(rs, 2));
   }
+
+  @Override
+  String getSetPreferenceSQL() {
+    return setPreferenceSQL;
+  }
   
   @Override
   public void setPreference(long userID, long itemID, float value) throws 
TasteException {

Modified: 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/AbstractJDBCDataModel.java
URL: 
http://svn.apache.org/viewvc/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/AbstractJDBCDataModel.java?rev=1032985&r1=1032984&r2=1032985&view=diff
==============================================================================
--- 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/AbstractJDBCDataModel.java
 (original)
+++ 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/AbstractJDBCDataModel.java
 Tue Nov  9 14:01:57 2010
@@ -247,6 +247,10 @@ public abstract class AbstractJDBCDataMo
     return preferenceColumn;
   }
 
+  String getSetPreferenceSQL() {
+    return setPreferenceSQL;
+  }
+
   @Override
   public LongPrimitiveIterator getUserIDs() throws TasteException {
     log.debug("Retrieving all users...");

Modified: 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/MySQLBooleanPrefJDBCDataModel.java
URL: 
http://svn.apache.org/viewvc/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/MySQLBooleanPrefJDBCDataModel.java?rev=1032985&r1=1032984&r2=1032985&view=diff
==============================================================================
--- 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/MySQLBooleanPrefJDBCDataModel.java
 (original)
+++ 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/MySQLBooleanPrefJDBCDataModel.java
 Tue Nov  9 14:01:57 2010
@@ -45,9 +45,7 @@ import org.apache.mahout.cf.taste.impl.c
  * </p>
  */
 public class MySQLBooleanPrefJDBCDataModel extends 
AbstractBooleanPrefJDBCDataModel {
-  
-  private static final String NO_SUCH_COLUMN = "NO_SUCH_COLUMN";
-  
+
   /**
    * <p>
    * Creates a  using the default {...@link javax.sql.DataSource} (named

Copied: 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/PostgreSQLBooleanPrefJDBCDataModel.java
 (from r1032231, 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/MySQLBooleanPrefJDBCDataModel.java)
URL: 
http://svn.apache.org/viewvc/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/PostgreSQLBooleanPrefJDBCDataModel.java?p2=mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/PostgreSQLBooleanPrefJDBCDataModel.java&p1=mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/MySQLBooleanPrefJDBCDataModel.java&r1=1032231&r2=1032985&rev=1032985&view=diff
==============================================================================
--- 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/MySQLBooleanPrefJDBCDataModel.java
 (original)
+++ 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/PostgreSQLBooleanPrefJDBCDataModel.java
 Tue Nov  9 14:01:57 2010
@@ -17,92 +17,102 @@
 
 package org.apache.mahout.cf.taste.impl.model.jdbc;
 
-import java.sql.ResultSet;
-import java.sql.SQLException;
-
-import javax.sql.DataSource;
-
+import com.google.common.base.Preconditions;
 import org.apache.mahout.cf.taste.common.TasteException;
 import org.apache.mahout.cf.taste.impl.common.jdbc.AbstractJDBCComponent;
+import org.apache.mahout.common.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
 
 /**
  * <p>
- * See also {...@link MySQLJDBCDataModel} -- same except deals with a table 
without preference info:
+ * See also {...@link 
org.apache.mahout.cf.taste.impl.model.jdbc.PostgreSQLJDBCDataModel} --
+ * same except deals with a table without preference info:
  * </p>
- * 
+ *
  * <p>
- * 
+ *
  * <pre>
  * CREATE TABLE taste_preferences (
  *   user_id BIGINT NOT NULL,
  *   item_id BIGINT NOT NULL,
- *   PRIMARY KEY (user_id, item_id),
- *   INDEX (user_id),
- *   INDEX (item_id)
- * )
+ *   PRIMARY KEY (user_id, item_id)
+ * );
+ * CREATE INDEX taste_preferences_user_id_index ON taste_preferences (user_id);
+ * CREATE INDEX taste_preferences_item_id_index ON taste_preferences (item_id);
  * </pre>
- * 
+ *
  * </p>
+ *
+ * <p>See {...@link MySQLBooleanPrefJDBCDataModel} which is largely 
identical.</p>
+ *
+ * @see MySQLBooleanPrefJDBCDataModel
  */
-public class MySQLBooleanPrefJDBCDataModel extends 
AbstractBooleanPrefJDBCDataModel {
-  
-  private static final String NO_SUCH_COLUMN = "NO_SUCH_COLUMN";
-  
+public class PostgreSQLBooleanPrefJDBCDataModel extends 
AbstractBooleanPrefJDBCDataModel {
+
+  private static final Logger log = 
LoggerFactory.getLogger(PostgreSQLBooleanPrefJDBCDataModel.class);
+  private static final String POSTGRESQL_DUPLICATE_KEY_STATE = "23505"; // 
this is brittle...
+
   /**
    * <p>
    * Creates a  using the default {...@link javax.sql.DataSource} (named
    * {...@link #DEFAULT_DATASOURCE_NAME} and default table/column names.
    * </p>
-   * 
+   *
    * @throws org.apache.mahout.cf.taste.common.TasteException
    *           if {...@link javax.sql.DataSource} can't be found
    */
-  public MySQLBooleanPrefJDBCDataModel() throws TasteException {
+  public PostgreSQLBooleanPrefJDBCDataModel() throws TasteException {
     this(DEFAULT_DATASOURCE_NAME);
   }
-  
+
   /**
    * <p>
    * Creates a  using the default {...@link javax.sql.DataSource} found
    * under the given name, and using default table/column names.
    * </p>
-   * 
+   *
    * @param dataSourceName
    *          name of {...@link javax.sql.DataSource} to look up
    * @throws org.apache.mahout.cf.taste.common.TasteException
    *           if {...@link javax.sql.DataSource} can't be found
    */
-  public MySQLBooleanPrefJDBCDataModel(String dataSourceName) throws 
TasteException {
+  public PostgreSQLBooleanPrefJDBCDataModel(String dataSourceName) throws 
TasteException {
     this(AbstractJDBCComponent.lookupDataSource(dataSourceName),
          DEFAULT_PREFERENCE_TABLE,
          DEFAULT_USER_ID_COLUMN,
          DEFAULT_ITEM_ID_COLUMN,
          DEFAULT_PREFERENCE_TIME_COLUMN);
   }
-  
+
   /**
    * <p>
    * Creates a  using the given {...@link javax.sql.DataSource} and default
    * table/column names.
    * </p>
-   * 
+   *
    * @param dataSource
    *          {...@link javax.sql.DataSource} to use
    */
-  public MySQLBooleanPrefJDBCDataModel(DataSource dataSource) {
+  public PostgreSQLBooleanPrefJDBCDataModel(DataSource dataSource) {
     this(dataSource,
          DEFAULT_PREFERENCE_TABLE,
          DEFAULT_USER_ID_COLUMN,
          DEFAULT_ITEM_ID_COLUMN,
          DEFAULT_PREFERENCE_TIME_COLUMN);
   }
-  
+
   /**
    * <p>
    * Creates a  using the given {...@link javax.sql.DataSource} and default
    * table/column names.
    * </p>
-   * 
+   *
    * @param dataSource
    *          {...@link javax.sql.DataSource} to use
    * @param preferenceTable
@@ -113,11 +123,11 @@ public class MySQLBooleanPrefJDBCDataMod
    *          item ID column name
    * @param timestampColumn timestamp column name (may be null)
    */
-  public MySQLBooleanPrefJDBCDataModel(DataSource dataSource,
-                                       String preferenceTable,
-                                       String userIDColumn,
-                                       String itemIDColumn,
-                                       String timestampColumn) {
+  public PostgreSQLBooleanPrefJDBCDataModel(DataSource dataSource,
+                                            String preferenceTable,
+                                            String userIDColumn,
+                                            String itemIDColumn,
+                                            String timestampColumn) {
     super(dataSource, preferenceTable, userIDColumn, itemIDColumn,
         NO_SUCH_COLUMN,
         // getPreferenceSQL
@@ -136,7 +146,7 @@ public class MySQLBooleanPrefJDBCDataMod
         // getNumUsersSQL
         "SELECT COUNT(DISTINCT " + userIDColumn + ") FROM " + preferenceTable,
         // setPreferenceSQL
-        "INSERT IGNORE INTO " + preferenceTable + '(' + userIDColumn + ',' + 
itemIDColumn + ") VALUES (?,?)",
+        "INSERT INTO " + preferenceTable + '(' + userIDColumn + ',' + 
itemIDColumn + ") VALUES (?,?)",
         // removePreference SQL
         "DELETE FROM " + preferenceTable + " WHERE " + userIDColumn + "=? AND 
" + itemIDColumn + "=?",
         // getUsersSQL
@@ -156,20 +166,34 @@ public class MySQLBooleanPrefJDBCDataMod
         // getMinPreferenceSQL
         "SELECT 1.0");
   }
-  
-  @Override
-  protected int getFetchSize() {
-    // Need to return this for MySQL Connector/J to make it use streaming mode
-    return Integer.MIN_VALUE;
-  }
-  
+
+  /**
+   * Override since PostgreSQL doesn't have the same non-standard capability 
that MySQL has, to optionally
+   * ignore an insert that fails since the row exists already.
+   */
   @Override
-  protected void advanceResultSet(ResultSet resultSet, int n) throws 
SQLException {
-    // Can't use relative on MySQL Connector/J
-    int i = 0;
-    while ((i < n) && resultSet.next()) {
-      i++;
+  public void setPreference(long userID, long itemID, float value) throws 
TasteException {
+    Preconditions.checkArgument(!Float.isNaN(value), "Invalid value: " + 
value);
+    log.debug("Setting preference for user {}, item {}", userID, itemID);
+
+    String setPreferenceSQL = getSetPreferenceSQL();
+    Connection conn = null;
+    PreparedStatement stmt = null;
+    try {
+      conn = getDataSource().getConnection();
+      stmt = conn.prepareStatement(setPreferenceSQL);
+      setLongParameter(stmt, 1, userID);
+      setLongParameter(stmt, 2, itemID);
+      log.debug("Executing SQL update: {}", setPreferenceSQL);
+      stmt.executeUpdate();
+    } catch (SQLException sqle) {
+      if (!POSTGRESQL_DUPLICATE_KEY_STATE.equals(sqle.getSQLState())) {
+        log.warn("Exception while setting preference", sqle);
+        throw new TasteException(sqle);
+      }
+    } finally {
+      IOUtils.quietClose(null, stmt, conn);
     }
   }
-  
+
 }
\ No newline at end of file

Copied: 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/PostgreSQLJDBCDataModel.java
 (from r1032231, 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/MySQLJDBCDataModel.java)
URL: 
http://svn.apache.org/viewvc/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/PostgreSQLJDBCDataModel.java?p2=mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/PostgreSQLJDBCDataModel.java&p1=mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/MySQLJDBCDataModel.java&r1=1032231&r2=1032985&rev=1032985&view=diff
==============================================================================
--- 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/MySQLJDBCDataModel.java
 (original)
+++ 
mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/PostgreSQLJDBCDataModel.java
 Tue Nov  9 14:01:57 2010
@@ -17,144 +17,77 @@
 
 package org.apache.mahout.cf.taste.impl.model.jdbc;
 
-import java.sql.ResultSet;
-import java.sql.SQLException;
-
-import javax.sql.DataSource;
-
+import com.google.common.base.Preconditions;
 import org.apache.mahout.cf.taste.common.TasteException;
 import org.apache.mahout.cf.taste.impl.common.jdbc.AbstractJDBCComponent;
+import org.apache.mahout.common.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
 
 /**
  * <p>
- * A {...@link org.apache.mahout.cf.taste.model.JDBCDataModel} backed by a 
MySQL database and
+ * A {...@link org.apache.mahout.cf.taste.model.JDBCDataModel} backed by a 
PostgreSQL database and
  * accessed via JDBC. It may work with other JDBC databases. By default, this 
class
- * assumes that there is a {...@link DataSource} available under the JNDI name
+ * assumes that there is a {...@link javax.sql.DataSource} available under the 
JNDI name
  * "jdbc/taste", which gives access to a database with a "taste_preferences" 
table with the following schema:
  * </p>
- * 
- * <table>
- * <tr>
- * <th>user_id</th>
- * <th>item_id</th>
- * <th>preference</th>
- * </tr>
- * <tr>
- * <td>987</td>
- * <td>123</td>
- * <td>0.9</td>
- * </tr>
- * <tr>
- * <td>987</td>
- * <td>456</td>
- * <td>0.1</td>
- * </tr>
- * <tr>
- * <td>654</td>
- * <td>123</td>
- * <td>0.2</td>
- * </tr>
- * <tr>
- * <td>654</td>
- * <td>789</td>
- * <td>0.3</td>
- * </tr>
- * </table>
- * 
- * <p>
- * <code>preference</code> must have a type compatible with the Java 
<code>float</code> type.
- * <code>user_id</code> and <code>item_id</code> should be compatible with 
long type (BIGINT). For example,
- * the following command sets up a suitable table in MySQL, complete with 
primary key and indexes:
- * </p>
- * 
+ *
  * <p>
- * 
+ *
  * <pre>
  * CREATE TABLE taste_preferences (
  *   user_id BIGINT NOT NULL,
  *   item_id BIGINT NOT NULL,
- *   preference FLOAT NOT NULL,
- *   PRIMARY KEY (user_id, item_id),
- *   INDEX (user_id),
- *   INDEX (item_id)
+ *   preference REAL NOT NULL,
+ *   PRIMARY KEY (user_id, item_id)
  * )
+ * CREATE INDEX taste_preferences_user_id_index ON taste_preferences (user_id);
+ * CREATE INDEX taste_preferences_item_id_index ON taste_preferences (item_id);
  * </pre>
- * 
- * </p>
  *
- * <p>The table may optionally have a <code>timestamp</code> column whose type 
is compatible with Java
- * <code>long</code>.</p>
- * 
- * <h3>Performance Notes</h3>
- * 
- * <p>
- * See the notes in {...@link AbstractJDBCDataModel} regarding using 
connection pooling. It's pretty vital to
- * performance.
- * </p>
- * 
- * <p>
- * Some experimentation suggests that MySQL's InnoDB engine is faster than 
MyISAM for these kinds of
- * applications. While MyISAM is the default and, I believe, generally 
considered the lighter-weight and
- * faster of the two engines, my guess is the row-level locking of InnoDB 
helps here. Your mileage may vary.
- * </p>
- * 
- * <p>
- * Here are some key settings that can be tuned for MySQL, and suggested size 
for a data set of around 1
- * million elements:
- * </p>
- * 
- * <ul>
- * <li>innodb_buffer_pool_size=64M</li>
- * <li>myisam_sort_buffer_size=64M</li>
- * <li>query_cache_limit=64M</li>
- * <li>query_cache_min_res_unit=512K</li>
- * <li>query_cache_type=1</li>
- * <li>query_cache_size=64M</li>
- * </ul>
- * 
- * <p>
- * Also consider setting some parameters on the MySQL Connector/J driver:
- * </p>
- * 
- * <pre>
- * cachePreparedStatements = true
- * cachePrepStmts = true
- * cacheResultSetMetadata = true
- * alwaysSendSetIsolation = false
- * elideSetAutoCommits = true
- * </pre>
- * 
- * <p>
- * Thanks to Amila Jayasooriya for contributing MySQL notes above as part of 
Google Summer of Code 2007.
  * </p>
+ *
+ * <p>See {...@link MySQLJDBCDataModel} which is largely identical.</p>
+ *
+ * @see MySQLJDBCDataModel
  */
-public class MySQLJDBCDataModel extends AbstractJDBCDataModel {
-  
+public class PostgreSQLJDBCDataModel extends AbstractJDBCDataModel {
+
+  private static final Logger log = 
LoggerFactory.getLogger(PostgreSQLJDBCDataModel.class);
+  private static final String POSTGRESQL_DUPLICATE_KEY_STATE = "23505"; // 
this is brittle...
+
+  private final String updatePreferenceSQL;
+
   /**
    * <p>
-   * Creates a  using the default {...@link DataSource} (named
+   * Creates a  using the default {...@link javax.sql.DataSource} (named
    * {...@link #DEFAULT_DATASOURCE_NAME} and default table/column names.
    * </p>
-   * 
-   * @throws TasteException
-   *           if {...@link DataSource} can't be found
+   *
+   * @throws org.apache.mahout.cf.taste.common.TasteException
+   *           if {...@link javax.sql.DataSource} can't be found
    */
-  public MySQLJDBCDataModel() throws TasteException {
+  public PostgreSQLJDBCDataModel() throws TasteException {
     this(DEFAULT_DATASOURCE_NAME);
   }
-  
+
   /**
    * <p>
-   * Creates a  using the default {...@link DataSource} found under the given 
name, and
+   * Creates a  using the default {...@link javax.sql.DataSource} found under 
the given name, and
    * using default table/column names.
    * </p>
-   * 
+   *
    * @param dataSourceName
-   *          name of {...@link DataSource} to look up
-   * @throws TasteException
-   *           if {...@link DataSource} can't be found
+   *          name of {...@link javax.sql.DataSource} to look up
+   * @throws org.apache.mahout.cf.taste.common.TasteException
+   *           if {...@link javax.sql.DataSource} can't be found
    */
-  public MySQLJDBCDataModel(String dataSourceName) throws TasteException {
+  public PostgreSQLJDBCDataModel(String dataSourceName) throws TasteException {
     this(AbstractJDBCComponent.lookupDataSource(dataSourceName),
          DEFAULT_PREFERENCE_TABLE,
          DEFAULT_USER_ID_COLUMN,
@@ -162,16 +95,16 @@ public class MySQLJDBCDataModel extends 
          DEFAULT_PREFERENCE_COLUMN,
          DEFAULT_PREFERENCE_TIME_COLUMN);
   }
-  
+
   /**
    * <p>
-   * Creates a  using the given {...@link DataSource} and default table/column 
names.
+   * Creates a  using the given {...@link javax.sql.DataSource} and default 
table/column names.
    * </p>
-   * 
+   *
    * @param dataSource
-   *          {...@link DataSource} to use
+   *          {...@link javax.sql.DataSource} to use
    */
-  public MySQLJDBCDataModel(DataSource dataSource) {
+  public PostgreSQLJDBCDataModel(DataSource dataSource) {
     this(dataSource,
          DEFAULT_PREFERENCE_TABLE,
          DEFAULT_USER_ID_COLUMN,
@@ -179,14 +112,14 @@ public class MySQLJDBCDataModel extends 
          DEFAULT_PREFERENCE_COLUMN,
          DEFAULT_PREFERENCE_TIME_COLUMN);
   }
-  
+
   /**
    * <p>
-   * Creates a  using the given {...@link DataSource} and default table/column 
names.
+   * Creates a  using the given {...@link javax.sql.DataSource} and default 
table/column names.
    * </p>
-   * 
+   *
    * @param dataSource
-   *          {...@link DataSource} to use
+   *          {...@link javax.sql.DataSource} to use
    * @param preferenceTable
    *          name of table containing preference data
    * @param userIDColumn
@@ -197,12 +130,12 @@ public class MySQLJDBCDataModel extends 
    *          preference column name
    * @param timestampColumn timestamp column name (may be null)
    */
-  public MySQLJDBCDataModel(DataSource dataSource,
-                            String preferenceTable,
-                            String userIDColumn,
-                            String itemIDColumn,
-                            String preferenceColumn,
-                            String timestampColumn) {
+  public PostgreSQLJDBCDataModel(DataSource dataSource,
+                                 String preferenceTable,
+                                 String userIDColumn,
+                                 String itemIDColumn,
+                                 String preferenceColumn,
+                                 String timestampColumn) {
     super(dataSource, preferenceTable, userIDColumn, itemIDColumn, 
preferenceColumn,
         // getPreferenceSQL
         "SELECT " + preferenceColumn + " FROM " + preferenceTable + " WHERE " 
+ userIDColumn + "=? AND "
@@ -222,7 +155,7 @@ public class MySQLJDBCDataModel extends 
         "SELECT COUNT(DISTINCT " + userIDColumn + ") FROM " + preferenceTable,
         // setPreferenceSQL
         "INSERT INTO " + preferenceTable + '(' + userIDColumn + ',' + 
itemIDColumn + ',' + preferenceColumn
-            + ") VALUES (?,?,?) ON DUPLICATE KEY UPDATE " + preferenceColumn + 
"=?",
+            + ") VALUES (?,?,?)",
         // removePreference SQL
         "DELETE FROM " + preferenceTable + " WHERE " + userIDColumn + "=? AND 
" + itemIDColumn + "=?",
         // getUsersSQL
@@ -239,20 +172,60 @@ public class MySQLJDBCDataModel extends 
             + userIDColumn + ") WHERE tp1." + itemIDColumn + "=? and tp2." + 
itemIDColumn + "=?",
         "SELECT MAX(" + preferenceColumn + ") FROM " + preferenceTable,
         "SELECT MIN(" + preferenceColumn + ") FROM " + preferenceTable);
+
+    updatePreferenceSQL = "UPDATE " + preferenceTable + " SET " + 
preferenceColumn + "=? WHERE " + userIDColumn
+        + "=? AND " + itemIDColumn + "=?";
   }
-  
-  @Override
-  protected int getFetchSize() {
-    // Need to return this for MySQL Connector/J to make it use streaming mode
-    return Integer.MIN_VALUE;
-  }
-  
+
+  /**
+   * Override since PostgreSQL doesn't have the same non-standard capability 
that MySQL has, to optionally
+   * insert or update in one statement.
+   */
   @Override
-  protected void advanceResultSet(ResultSet resultSet, int n) throws 
SQLException {
-    // Can't use relative on MySQL Connector/J
-    int i = 0;
-    while ((i < n) && resultSet.next()) {
-      i++;
+  public void setPreference(long userID, long itemID, float value) throws 
TasteException {
+    Preconditions.checkArgument(!Float.isNaN(value), "Invalid value: " + 
value);
+
+    log.debug("Setting preference for user {}, item {}", userID, itemID);
+
+    String setPreferenceSQL = getSetPreferenceSQL();
+
+    Connection conn = null;
+    PreparedStatement stmt1 = null;
+    PreparedStatement stmt2 = null;
+    try {
+      conn = getDataSource().getConnection();
+
+      stmt1 = conn.prepareStatement(setPreferenceSQL);
+      setLongParameter(stmt1, 1, userID);
+      setLongParameter(stmt1, 2, itemID);
+      stmt1.setDouble(3, value);
+
+      log.debug("Executing SQL update: {}", setPreferenceSQL);
+      try {
+      stmt1.executeUpdate();
+      } catch (SQLException sqle) {
+        if (!POSTGRESQL_DUPLICATE_KEY_STATE.equals(sqle.getSQLState())) {
+          throw sqle;
+        }
+      }
+
+      // Continue with update; just found the key already exists
+
+      stmt2 = conn.prepareStatement(updatePreferenceSQL);
+      stmt2.setDouble(1, value);
+      setLongParameter(stmt2, 2, userID);
+      setLongParameter(stmt2, 3, itemID);
+
+      log.debug("Executing SQL update: {}", updatePreferenceSQL);
+      stmt2.executeUpdate();
+
+    } catch (SQLException sqle) {
+      log.warn("Exception while setting preference", sqle);
+      throw new TasteException(sqle);
+    } finally {
+      IOUtils.quietClose(null, stmt1, null);
+      IOUtils.quietClose(null, stmt2, null);
+      IOUtils.quietClose(null, null, conn);
     }
   }
   

Modified: mahout/trunk/core/src/main/java/org/apache/mahout/common/IOUtils.java
URL: 
http://svn.apache.org/viewvc/mahout/trunk/core/src/main/java/org/apache/mahout/common/IOUtils.java?rev=1032985&r1=1032984&r2=1032985&view=diff
==============================================================================
--- mahout/trunk/core/src/main/java/org/apache/mahout/common/IOUtils.java 
(original)
+++ mahout/trunk/core/src/main/java/org/apache/mahout/common/IOUtils.java Tue 
Nov  9 14:01:57 2010
@@ -24,6 +24,7 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
 
+import com.google.common.base.Preconditions;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -38,12 +39,15 @@ public final class IOUtils {
   
   private IOUtils() { }
   
-  public static void quietClose(Closeable closeable) {
-    if (closeable != null) {
-      try {
-        closeable.close();
-      } catch (IOException ioe) {
-        log.warn("Unexpected exception while closing; continuing", ioe);
+  public static void quietClose(Closeable... closeables) {
+    Preconditions.checkNotNull(closeables);
+    for (Closeable closeable : closeables) {
+      if (closeable != null) {
+        try {
+          closeable.close();
+        } catch (IOException ioe) {
+          log.warn("Unexpected exception while closing; continuing", ioe);
+        }
       }
     }
   }


Reply via email to