Improve conversion to OrmDuplicateException if appropriate

Improve or implement the conversion of SQL exception to instance of
OrmDuplicateException for all Jdbc dialects. Add unit tests for this
use case.

For more information about the bug on the mailing list
https://groups.google.com/d/topic/repo-discuss/Ix1oMCLGMtw/discussion

Change-Id: Ic5d507e8a4e896a0e038f7109f38013aa2e2c8a1
diff --git a/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java b/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java
index af061f0..3cf7ff9 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java
@@ -38,9 +38,9 @@
       final SQLException err) {
     switch (getSQLStateInt(err)) {
       case 23001: // UNIQUE CONSTRAINT VIOLATION
+      case 23505: // DUPLICATE_KEY_1
         return new OrmDuplicateKeyException(entity, err);
 
-      case 23000: // CHECK CONSTRAINT VIOLATION
       default:
         return super.convertError(op, entity, err);
     }
diff --git a/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java b/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java
index e07740d..aad5037 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java
@@ -17,6 +17,7 @@
 import com.google.gwtorm.client.Column;
 import com.google.gwtorm.schema.ColumnModel;
 import com.google.gwtorm.schema.SequenceModel;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.StatementExecutor;
 
@@ -214,4 +215,16 @@
     r.append(getSqlTypeInfo(col).getSqlType(col, this));
     stmt.execute(r.toString());
   }
+
+  @Override
+  public OrmException convertError(String op, String entity, SQLException err) {
+    switch (err.getErrorCode()) {
+      case 1022: // ER_DUP_KEY
+      case 1062: // ER_DUP_ENTRY
+      case 1169: // ER_DUP_UNIQUE;
+        return new OrmDuplicateKeyException(entity, err);
+      default:
+        return super.convertError(op, entity, err);
+    }
+  }
 }
diff --git a/src/main/java/com/google/gwtorm/schema/sql/DialectOracle.java b/src/main/java/com/google/gwtorm/schema/sql/DialectOracle.java
index 827c656..d006b16 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/DialectOracle.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectOracle.java
@@ -15,6 +15,7 @@
 package com.google.gwtorm.schema.sql;
 
 import com.google.gwtorm.schema.ColumnModel;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.StatementExecutor;
 
@@ -148,4 +149,14 @@
   public boolean isStatementDelimiterSupported() {
     return false;
   }
+
+  @Override
+  public OrmException convertError(String op, String entity, SQLException err) {
+    switch (err.getErrorCode()) {
+      case 1: // ORA-00001: unique constraint violated
+        return new OrmDuplicateKeyException(entity, err);
+      default:
+        return super.convertError(op, entity, err);
+    }
+  }
 }
diff --git a/src/test/java/com/google/gwtorm/schema/sql/SqlDialectTest.java b/src/test/java/com/google/gwtorm/schema/sql/SqlDialectTest.java
index ac8cd46..b159b83 100644
--- a/src/test/java/com/google/gwtorm/schema/sql/SqlDialectTest.java
+++ b/src/test/java/com/google/gwtorm/schema/sql/SqlDialectTest.java
@@ -15,12 +15,17 @@
 package com.google.gwtorm.schema.sql;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.google.gwtorm.data.Person;
 import com.google.gwtorm.data.PhoneBookDb;
 import com.google.gwtorm.data.PhoneBookDb2;
 import com.google.gwtorm.jdbc.Database;
 import com.google.gwtorm.jdbc.JdbcExecutor;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
 import com.google.gwtorm.server.OrmException;
 
 import org.junit.Test;
@@ -29,6 +34,7 @@
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Collections;
 
 public abstract class SqlDialectTest {
   protected JdbcExecutor executor;
@@ -75,4 +81,51 @@
     List<Person> r = schema.people().olderThan(10).toList();
     assertEquals(1, r.size());
   }
+
+  @Test
+  public void testThrowsOrmDuplicateKeyExceptionWhenTryingToInsertDuplicates()
+      throws Exception {
+    PhoneBookDb p = phoneBook.open();
+    try {
+      p.updateSchema(executor);
+
+      final Person.Key pk = new Person.Key("Bob");
+      final Person bob = new Person(pk, 18);
+      p.people().insert(Collections.singleton(bob));
+
+      p.people().insert(Collections.singleton(bob));
+      fail("Expected " + OrmDuplicateKeyException.class);
+    } catch (OrmDuplicateKeyException e) {
+      assertTrue(e.getCause() instanceof SQLException);
+      assertContainsString(e.getMessage(), p.people().getRelationName());
+    } finally {
+      p.close();
+    }
+  }
+
+  @Test
+  public void testThrowsOrmExceptionForOtherErrors() throws Exception {
+    PhoneBookDb p = phoneBook.open();
+    try {
+      p.updateSchema(executor);
+
+      String invalidKey = null;
+      final Person.Key pk = new Person.Key(invalidKey);
+      final Person bob = new Person(pk, 18);
+
+      p.people().insert(Collections.singleton(bob));
+      fail("Expected " + OrmException.class);
+    } catch (OrmException e) {
+      assertTrue(e.getCause() instanceof SQLException);
+      assertFalse(e instanceof OrmDuplicateKeyException);
+      assertContainsString(e.getMessage(), p.people().getRelationName());
+    } finally {
+      p.close();
+    }
+  }
+
+  private void assertContainsString(String string, String substring) {
+    assertNotNull(string);
+    assertTrue(string.contains(substring));
+  }
 }