Merge branch 'stable'

* stable:
  MySQL: Declare VARCHAR columns as BINARY
  Allow registration of custom SqlDialects

Change-Id: Icf91aac2f14cd65b3678cac9c6f289e216c7fc3d
diff --git a/src/main/java/com/google/gwtorm/jdbc/Database.java b/src/main/java/com/google/gwtorm/jdbc/Database.java
index 4f768cd..c47124a 100644
--- a/src/main/java/com/google/gwtorm/jdbc/Database.java
+++ b/src/main/java/com/google/gwtorm/jdbc/Database.java
@@ -21,9 +21,6 @@
 import com.google.gwtorm.schema.RelationModel;
 import com.google.gwtorm.schema.SchemaModel;
 import com.google.gwtorm.schema.java.JavaSchemaModel;
-import com.google.gwtorm.schema.sql.DialectH2;
-import com.google.gwtorm.schema.sql.DialectMySQL;
-import com.google.gwtorm.schema.sql.DialectPostgreSQL;
 import com.google.gwtorm.schema.sql.SqlDialect;
 import com.google.gwtorm.server.GeneratedClassLoader;
 import com.google.gwtorm.server.SchemaConstructorGen;
@@ -74,28 +71,14 @@
 
     SqlDialect dialect;
     try {
-      final Connection c = ds.getConnection();
+      Connection c = ds.getConnection();
       try {
-        final String url = c.getMetaData().getURL();
-        if (url.startsWith("jdbc:postgresql:")) {
-          dialect = new DialectPostgreSQL();
-
-        } else if (url.startsWith("jdbc:h2:")) {
-          dialect = new DialectH2();
-
-        } else if (url.startsWith("jdbc:mysql:")) {
-          dialect = new DialectMySQL();
-
-        } else {
-          throw new OrmException("No dialect known for " + url);
-        }
-
-        dialect = dialect.refine(c);
+        dialect = SqlDialect.getDialectFor(c);
       } finally {
         c.close();
       }
     } catch (SQLException e) {
-      throw new OrmException("Unable to determine driver URL", e);
+      throw new OrmException("Unable to determine SqlDialect", e);
     }
 
     schemaModel = new JavaSchemaModel(schema);
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 dab8535..5277459 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectH2.java
@@ -29,6 +29,11 @@
 /** Dialect for <a href="http://www.h2database.com/">H2</a> */
 public class DialectH2 extends SqlDialect {
   @Override
+  public boolean handles(String url, Connection c) {
+    return url.startsWith("jdbc:h2:");
+  }
+
+  @Override
   public OrmException convertError(final String op, final String entity,
       final SQLException err) {
     switch (getSQLStateInt(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 d57a6b0..86afa6a 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectMySQL.java
@@ -41,12 +41,12 @@
         final int type;
 
         if (column.length() <= 0) {
-          r.append("VARCHAR(255)");
+          r.append("VARCHAR(255) BINARY");
           if (col.isNotNull()) {
             r.append(" DEFAULT ''");
           }
         } else if (column.length() <= 255) {
-          r.append("VARCHAR(" + column.length() + ")");
+          r.append("VARCHAR(" + column.length() + ") BINARY");
           if (col.isNotNull()) {
             r.append(" DEFAULT ''");
           }
@@ -77,6 +77,11 @@
   }
 
   @Override
+  public boolean handles(String url, Connection c) {
+    return url.startsWith("jdbc:mysql:");
+  }
+
+  @Override
   public String getCreateSequenceSql(final SequenceModel seq) {
     final StringBuilder r = new StringBuilder();
     r.append("CREATE TABLE ");
diff --git a/src/main/java/com/google/gwtorm/schema/sql/DialectPostgreSQL.java b/src/main/java/com/google/gwtorm/schema/sql/DialectPostgreSQL.java
index b413dd1..5a469ec 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/DialectPostgreSQL.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectPostgreSQL.java
@@ -36,6 +36,11 @@
   }
 
   @Override
+  public boolean handles(String url, Connection c) {
+    return url.startsWith("jdbc:postgresql:");
+  }
+
+  @Override
   public SqlDialect refine(final Connection c) throws SQLException {
     final int major = c.getMetaData().getDatabaseMajorVersion();
     final int minor = c.getMetaData().getDatabaseMinorVersion();
diff --git a/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java b/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
index 7b1fb65..cb1e77d 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
@@ -29,10 +29,36 @@
 import java.sql.Types;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 public abstract class SqlDialect {
+  private static final List<SqlDialect> DIALECTS =
+      new CopyOnWriteArrayList<SqlDialect>();
+
+  static {
+    DIALECTS.add(new DialectH2());
+    DIALECTS.add(new DialectPostgreSQL());
+    DIALECTS.add(new DialectMySQL());
+  }
+
+  public static void register(SqlDialect dialect) {
+    DIALECTS.add(0, dialect);
+  }
+
+  public static SqlDialect getDialectFor(Connection c)
+      throws SQLException, OrmException {
+    String url = c.getMetaData().getURL();
+    for (SqlDialect d : DIALECTS) {
+      if (d.handles(url, c)) {
+        return d.refine(c);
+      }
+    }
+    throw new OrmException("No dialect known for " + url);
+  }
+
   protected final Map<Class<?>, SqlTypeInfo> types;
   protected final Map<Integer, String> typeNames;
 
@@ -58,6 +84,8 @@
     typeNames.put(Types.TIMESTAMP, "TIMESTAMP");
   }
 
+  public abstract boolean handles(String url, Connection c) throws SQLException;
+
   /** Select a better dialect definition for this connection */
   public SqlDialect refine(final Connection c) throws SQLException {
     return this;