Merge branch 'stable-1.7'

* stable-1.7:
  gwtorm 1.7.1
  Add transaction support for Jdbc dialects
  Fix a bug where "LIMIT ?" in queries was omitted in the generated query
  Drop "bar" tables in test setup
  Start 1.7.1 development

All the changes on stable-1.7 already exist on master.
Merged with `-s ours`.

Change-Id: I28c4a3f5757b85f0bb17ec2089e7682c1bbdb1bb
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index 5002612..6dbcac5 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -1,15 +1,15 @@
 #Wed May 13 16:33:08 PDT 2009
 eclipse.preferences.version=1
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.compliance=1.7
 org.eclipse.jdt.core.compiler.debug.lineNumber=generate
 org.eclipse.jdt.core.compiler.debug.localVariable=generate
 org.eclipse.jdt.core.compiler.debug.sourceFile=generate
 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
 org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.compiler.source=1.7
 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
diff --git a/README_MAXDB b/README_MAXDB
new file mode 100644
index 0000000..7230e5b
--- /dev/null
+++ b/README_MAXDB
@@ -0,0 +1,23 @@
+To test DialectMaxDB a SAP MaxDB JDBC driver "sapdbc.jar" is needed. It is
+not available in a public maven repository. However, the driver can be found
+in your MaxDB installation at the following location:
+
+- on Windows 64bit at "C:\Program Files\sdb\MaxDB\runtime\jar\sapdbc.jar"
+- on Linux at "/opt/sdb/MaxDB/runtime/jar/sapdbc.jar"
+
+To execute tests on MaxDB, you firstly need to create a test user with an
+associated empty schema in your database. Then you can execute the tests
+using maven with the profile "maxdb". The following properties need to be set:
+
+maxdb.driver.jar=<path to maxdb jdbc driver>
+maxdb.url=<url of test database>
+maxdb.user=<user name>
+maxdb.password=<password of test user>
+
+So the complete command would be:
+
+mvn package -P maxdb
+  -Dmaxdb.driver.jar=<path to maxdb jdbc driver>
+  -Dmaxdb.url=<url of test database>
+  -Dmaxdb.user=<user name>
+  -Dmaxdb.password=<password of test user>
diff --git a/pom.xml b/pom.xml
index ef262f7..d5b3bd8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,11 +21,16 @@
   <groupId>gwtorm</groupId>
   <artifactId>gwtorm</artifactId>
   <packaging>jar</packaging>
-  <version>1.7.1</version>
+  <version>1.12-SNAPSHOT</version>
   <name>gwtorm</name>
   <description>Tiny ORM</description>
   <url>https://gerrit.googlesource.com/gwtorm</url>
 
+  <scm>
+    <url>https://gerrit.googlesource.com/gwtorm</url>
+    <connection>https://gerrit.googlesource.com/gwtorm</connection>
+  </scm>
+
   <mailingLists>
     <mailingList>
       <name>repo-discuss mailing list</name>
@@ -265,6 +270,23 @@
       </dependencies>
     </profile>
     <profile>
+      <id>maxdb</id>
+      <build>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-surefire-plugin</artifactId>
+          <version>2.17</version>
+          <configuration>
+            <additionalClasspathElements>
+              <additionalClasspathElement>${maxdb.driver.jar}</additionalClasspathElement>
+            </additionalClasspathElements>
+          </configuration>
+        </plugin>
+      </plugins>
+    </build>
+    </profile>
+    <profile>
       <id>skip-proprietary-databases</id>
       <activation>
         <activeByDefault>true</activeByDefault>
@@ -290,8 +312,8 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
         <configuration>
-          <source>1.6</source>
-          <target>1.6</target>
+          <source>1.7</source>
+          <target>1.7</target>
           <encoding>UTF-8</encoding>
         </configuration>
       </plugin>
@@ -305,6 +327,26 @@
           </execution>
         </executions>
       </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.9.1</version>
+        <configuration>
+          <encoding>UTF-8</encoding>
+          <quiet>true</quiet>
+          <links>
+            <link>http://java.sun.com/j2se/1.7.0/docs/api</link>
+          </links>
+        </configuration>
+        <executions>
+          <execution>
+            <id>attach-javadocs</id>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
     </plugins>
     <extensions>
       <extension>
@@ -319,14 +361,14 @@
     <dependency>
       <groupId>org.ow2.asm</groupId>
       <artifactId>asm</artifactId>
-      <version>4.0</version>
+      <version>4.1</version>
       <scope>compile</scope>
     </dependency>
 
     <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
-      <version>13.0.1</version>
+      <version>15.0</version>
       <scope>compile</scope>
     </dependency>
 
@@ -375,7 +417,7 @@
     <dependency>
       <groupId>com.google.protobuf</groupId>
       <artifactId>protobuf-java</artifactId>
-      <version>2.4.1</version>
+      <version>2.5.0</version>
       <scope>provided</scope>
     </dependency>
   </dependencies>
diff --git a/src/main/java/com/google/gwtorm/client/CompoundKey.java b/src/main/java/com/google/gwtorm/client/CompoundKey.java
index 8224cac..0087ccb 100644
--- a/src/main/java/com/google/gwtorm/client/CompoundKey.java
+++ b/src/main/java/com/google/gwtorm/client/CompoundKey.java
@@ -33,6 +33,7 @@
   /**
    * @return the parent key instance; null if this is a root level key.
    */
+  @Override
   public P getParentKey() {
     return null;
   }
@@ -92,6 +93,7 @@
     return r.toString();
   }
 
+  @Override
   public void fromString(final String in) {
     final String[] parts = in.split(",");
     int p = 0;
diff --git a/src/main/java/com/google/gwtorm/client/IntKey.java b/src/main/java/com/google/gwtorm/client/IntKey.java
index e7995a3..f64493b 100644
--- a/src/main/java/com/google/gwtorm/client/IntKey.java
+++ b/src/main/java/com/google/gwtorm/client/IntKey.java
@@ -37,6 +37,7 @@
   /**
    * @return the parent key instance; null if this is a root level key.
    */
+  @Override
   public P getParentKey() {
     return null;
   }
@@ -72,6 +73,7 @@
     return r.toString();
   }
 
+  @Override
   public void fromString(final String in) {
     set(Integer.parseInt(KeyUtil.parseFromString(getParentKey(), in)));
   }
diff --git a/src/main/java/com/google/gwtorm/client/Key.java b/src/main/java/com/google/gwtorm/client/Key.java
index a08929a..b5b3d3a 100644
--- a/src/main/java/com/google/gwtorm/client/Key.java
+++ b/src/main/java/com/google/gwtorm/client/Key.java
@@ -31,11 +31,14 @@
    */
   public P getParentKey();
 
+  @Override
   public int hashCode();
 
+  @Override
   public boolean equals(Object o);
 
   /** @return the key, encoded in a string format . */
+  @Override
   public String toString();
 
   /** Reset this key instance to represent the data in the supplied string. */
diff --git a/src/main/java/com/google/gwtorm/client/KeyUtil.java b/src/main/java/com/google/gwtorm/client/KeyUtil.java
index d9caeb5..c4b4551 100644
--- a/src/main/java/com/google/gwtorm/client/KeyUtil.java
+++ b/src/main/java/com/google/gwtorm/client/KeyUtil.java
@@ -90,6 +90,7 @@
     if (comma < 0 && parent != null) {
       throw new IllegalArgumentException("Not enough components: " + in);
     }
+    assert(parent != null);
     parent.fromString(in.substring(0, comma));
     return decode(in.substring(comma + 1));
   }
diff --git a/src/main/java/com/google/gwtorm/client/LongKey.java b/src/main/java/com/google/gwtorm/client/LongKey.java
index 0fc4ae6..986285e 100644
--- a/src/main/java/com/google/gwtorm/client/LongKey.java
+++ b/src/main/java/com/google/gwtorm/client/LongKey.java
@@ -37,6 +37,7 @@
   /**
    * @return the parent key instance; null if this is a root level key.
    */
+  @Override
   public P getParentKey() {
     return null;
   }
@@ -72,6 +73,7 @@
     return r.toString();
   }
 
+  @Override
   public void fromString(final String in) {
     set(Long.parseLong(KeyUtil.parseFromString(getParentKey(), in)));
   }
diff --git a/src/main/java/com/google/gwtorm/client/ShortKey.java b/src/main/java/com/google/gwtorm/client/ShortKey.java
index 59783f4..abda6d1 100644
--- a/src/main/java/com/google/gwtorm/client/ShortKey.java
+++ b/src/main/java/com/google/gwtorm/client/ShortKey.java
@@ -38,6 +38,7 @@
   /**
    * @return the parent key instance; null if this is a root level key.
    */
+  @Override
   public P getParentKey() {
     return null;
   }
@@ -73,6 +74,7 @@
     return r.toString();
   }
 
+  @Override
   public void fromString(final String in) {
     set(Short.parseShort(KeyUtil.parseFromString(getParentKey(), in)));
   }
diff --git a/src/main/java/com/google/gwtorm/client/StringKey.java b/src/main/java/com/google/gwtorm/client/StringKey.java
index 4c85d2a..a4dcf4c 100644
--- a/src/main/java/com/google/gwtorm/client/StringKey.java
+++ b/src/main/java/com/google/gwtorm/client/StringKey.java
@@ -38,6 +38,7 @@
   /**
    * @return the parent key instance; null if this is a root level key.
    */
+  @Override
   public P getParentKey() {
     return null;
   }
@@ -79,6 +80,7 @@
     return r.toString();
   }
 
+  @Override
   public void fromString(final String in) {
     set(KeyUtil.parseFromString(getParentKey(), in));
   }
diff --git a/src/main/java/com/google/gwtorm/jdbc/Database.java b/src/main/java/com/google/gwtorm/jdbc/Database.java
index 2c8d256..802bcec 100644
--- a/src/main/java/com/google/gwtorm/jdbc/Database.java
+++ b/src/main/java/com/google/gwtorm/jdbc/Database.java
@@ -117,6 +117,7 @@
    *         The JDBC exception detail should be examined to determine the root
    *         cause of the connection failure.
    */
+  @Override
   public T open() throws OrmException {
     return implFactory.open();
   }
diff --git a/src/main/java/com/google/gwtorm/jdbc/JdbcSchema.java b/src/main/java/com/google/gwtorm/jdbc/JdbcSchema.java
index 00310e4..c7c5b5a 100644
--- a/src/main/java/com/google/gwtorm/jdbc/JdbcSchema.java
+++ b/src/main/java/com/google/gwtorm/jdbc/JdbcSchema.java
@@ -78,6 +78,7 @@
     }
   }
 
+  @Override
   public void updateSchema(final StatementExecutor e) throws OrmException {
     try {
       createSequences(e);
@@ -171,6 +172,7 @@
     return null;
   }
 
+  @Override
   public void pruneSchema(final StatementExecutor e) throws OrmException {
     try {
       pruneSequences(e);
@@ -234,6 +236,7 @@
     return getDialect().nextLong(getConnection(), poolName);
   }
 
+  @Override
   public void close() {
     if (conn != null) {
       try {
diff --git a/src/main/java/com/google/gwtorm/jdbc/SimpleDataSource.java b/src/main/java/com/google/gwtorm/jdbc/SimpleDataSource.java
index 9bb96d2..a80a3a1 100644
--- a/src/main/java/com/google/gwtorm/jdbc/SimpleDataSource.java
+++ b/src/main/java/com/google/gwtorm/jdbc/SimpleDataSource.java
@@ -98,6 +98,7 @@
     return sqle;
   }
 
+  @Override
   public Connection getConnection() throws SQLException {
     if (driver != null) {
       return driver.connect(url, connectionInfo);
@@ -105,6 +106,7 @@
     return DriverManager.getConnection(url, connectionInfo);
   }
 
+  @Override
   public Connection getConnection(String user, String password)
       throws SQLException {
     if (driver != null) {
@@ -120,29 +122,36 @@
     return DriverManager.getConnection(url, user, password);
   }
 
+  @Override
   public PrintWriter getLogWriter() {
     return logWriter;
   }
 
+  @Override
   public void setLogWriter(final PrintWriter out) {
     logWriter = out;
   }
 
+  @Override
   public int getLoginTimeout() {
     return 0;
   }
 
+  @Override
   public void setLoginTimeout(int seconds) {
   }
 
+  @Override
   public boolean isWrapperFor(Class<?> iface) {
     return false;
   }
 
+  @Override
   public <T> T unwrap(Class<T> iface) throws SQLException {
     throw new SQLException(getClass().getName() + " wraps nothing");
   }
 
+  @Override
   public Logger getParentLogger() throws SQLFeatureNotSupportedException {
     throw new SQLFeatureNotSupportedException();
   }
diff --git a/src/main/java/com/google/gwtorm/schema/Util.java b/src/main/java/com/google/gwtorm/schema/Util.java
index 1ca5e5d..6a55c72 100644
--- a/src/main/java/com/google/gwtorm/schema/Util.java
+++ b/src/main/java/com/google/gwtorm/schema/Util.java
@@ -41,9 +41,16 @@
         r.append(c);
       }
     }
-    return r.toString();
+    String friendlyName = r.toString();
+    if (friendlyName.length() > 30) {
+      throw new IllegalArgumentException(String.format(
+          "Identifier '%s' for name '%s' is greater than 30 characters",
+          friendlyName, name));
+    }
+    return friendlyName;
   }
 
+
   public static String any(final String a, final String b) {
     if (a != null && a.length() > 0) {
       return a;
diff --git a/src/main/java/com/google/gwtorm/schema/sql/DialectMaxDB.java b/src/main/java/com/google/gwtorm/schema/sql/DialectMaxDB.java
new file mode 100644
index 0000000..b1cbc07
--- /dev/null
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectMaxDB.java
@@ -0,0 +1,119 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.HashSet;
+import java.util.Set;
+
+public class DialectMaxDB extends SqlDialect {
+
+  public DialectMaxDB() {
+    typeNames.put(Types.BIGINT, "FIXED (19,0)");
+    typeNames.put(Types.LONGVARCHAR, "LONG UNICODE");
+  }
+
+  @Override
+  public boolean canDetermineIndividualBatchUpdateCounts() {
+    return false;
+  }
+
+  @Override
+  public boolean canDetermineTotalBatchUpdateCount() {
+    return true;
+  }
+
+  @Override
+  public int executeBatch(PreparedStatement ps) throws SQLException {
+    ps.executeBatch();
+    return ps.getUpdateCount(); // total number of rows updated (on MaxDB)
+  }
+
+  @Override
+  public Set<String> listSequences(Connection conn) throws SQLException {
+    final Statement s = conn.createStatement();
+    try {
+      // lists sequences from schema associated with the current connection only
+      final ResultSet rs =
+          s.executeQuery("SELECT sequence_name FROM sequences");
+      try {
+        HashSet<String> sequences = new HashSet<String>();
+        while (rs.next()) {
+          sequences.add(rs.getString(1).toLowerCase());
+        }
+        return sequences;
+      } finally {
+        rs.close();
+      }
+    } finally {
+      s.close();
+    }
+  }
+
+  @Override
+  public void renameColumn(StatementExecutor e, String tableName,
+      String fromColumn, ColumnModel col) throws OrmException {
+    final StringBuilder s = new StringBuilder();
+    s.append("RENAME COLUMN ");
+    s.append(tableName).append(".").append(fromColumn);
+    s.append(" TO ");
+    s.append(col.getColumnName());
+    e.execute(s.toString());
+  }
+
+  @Override
+  public OrmException convertError(String op, String entity, SQLException err) {
+    int sqlstate = getSQLStateInt(err);
+    if (sqlstate == 23000) { // UNIQUE CONSTRAINT VIOLATION
+      int errorCode = err.getErrorCode();
+      if (errorCode == 200 || errorCode == -20) { // Duplicate Key
+        return new OrmDuplicateKeyException(entity, err);
+      }
+    }
+    return super.convertError(op, entity, err);
+  }
+
+  @Override
+  public String getNextSequenceValueSql(String seqname) {
+    return "SELECT " + seqname + ".nextval FROM dual";
+  }
+
+  @Override
+  public boolean handles(String url, Connection c) throws SQLException {
+    return url.startsWith("jdbc:sapdb:");
+  }
+
+  @Override
+  public void renameTable(StatementExecutor e, String from, String to)
+      throws OrmException {
+    final StringBuilder r = new StringBuilder();
+    r.append("RENAME TABLE ");
+    r.append(from);
+    r.append(" TO ");
+    r.append(to);
+    e.execute(r.toString());
+  }
+
+}
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 63227be..827c656 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/DialectOracle.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/DialectOracle.java
@@ -19,6 +19,7 @@
 import com.google.gwtorm.server.StatementExecutor;
 
 import java.sql.Connection;
+import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
@@ -53,7 +54,7 @@
     try {
       ResultSet rs = s.executeQuery("SELECT table_name FROM user_tables");
       try {
-        HashSet<String> tables = new HashSet<String>();
+        Set<String> tables = new HashSet<String>();
         while (rs.next()) {
           tables.add(rs.getString(1).toLowerCase());
         }
@@ -67,12 +68,34 @@
   }
 
   @Override
+  public Set<String> listIndexes(final Connection db, String tableName)
+      throws SQLException {
+    PreparedStatement s = db.prepareStatement("SELECT distinct index_name"
+        + " FROM user_indexes WHERE table_name = ?");
+    try {
+      s.setString(1, tableName.toUpperCase());
+      ResultSet rs = s.executeQuery();
+      try {
+        Set<String> indexes = new HashSet<String>();
+        while (rs.next()) {
+          indexes.add(rs.getString(1).toLowerCase());
+        }
+        return indexes;
+      } finally {
+        rs.close();
+      }
+    } finally {
+      s.close();
+    }
+  }
+
+  @Override
   public Set<String> listSequences(Connection db) throws SQLException {
     Statement s = db.createStatement();
     try {
       ResultSet rs = s.executeQuery("SELECT sequence_name FROM user_sequences");
       try {
-        HashSet<String> sequences = new HashSet<String>();
+        Set<String> sequences = new HashSet<String>();
         while (rs.next()) {
           sequences.add(rs.getString(1).toLowerCase());
         }
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 ad1f16b..fdfe6c8 100644
--- a/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
+++ b/src/main/java/com/google/gwtorm/schema/sql/SqlDialect.java
@@ -44,6 +44,7 @@
     DIALECTS.add(new DialectPostgreSQL());
     DIALECTS.add(new DialectMySQL());
     DIALECTS.add(new DialectOracle());
+    DIALECTS.add(new DialectMaxDB());
   }
 
   public static void register(SqlDialect dialect) {
@@ -224,7 +225,7 @@
     final String[] types = new String[] {"TABLE"};
     final ResultSet rs = db.getMetaData().getTables(null, null, null, types);
     try {
-      HashSet<String> tables = new HashSet<String>();
+      Set<String> tables = new HashSet<String>();
       while (rs.next()) {
         tables.add(rs.getString("TABLE_NAME").toLowerCase());
       }
@@ -254,6 +255,35 @@
   }
 
   /**
+   * List all indexes for the given table name.
+   *
+   * @param db connection to the schema.
+   * @param tableName the table to list indexes from, in lowercase.
+   * @return set of declared indexes, in lowercase.
+   * @throws SQLException the indexes cannot be listed.
+   */
+  public Set<String> listIndexes(final Connection db, String tableName)
+      throws SQLException {
+    final DatabaseMetaData meta = db.getMetaData();
+    if (meta.storesUpperCaseIdentifiers()) {
+      tableName = tableName.toUpperCase();
+    } else if (meta.storesLowerCaseIdentifiers()) {
+      tableName = tableName.toLowerCase();
+    }
+
+    ResultSet rs = meta.getIndexInfo(null, null, tableName, false, true);
+    try {
+      Set<String> indexes = new HashSet<String>();
+      while (rs.next()) {
+        indexes.add(rs.getString("INDEX_NAME").toLowerCase());
+      }
+      return indexes;
+    } finally {
+      rs.close();
+    }
+  }
+
+  /**
    * List all sequences in the current database schema.
    *
    * @param db connection to the schema.
diff --git a/src/main/java/com/google/gwtorm/server/AbstractAccess.java b/src/main/java/com/google/gwtorm/server/AbstractAccess.java
index 8c6fd9f..f598630 100644
--- a/src/main/java/com/google/gwtorm/server/AbstractAccess.java
+++ b/src/main/java/com/google/gwtorm/server/AbstractAccess.java
@@ -32,6 +32,7 @@
     // Do nothing by default.
   }
 
+  @Override
   public CheckedFuture<E, OrmException> getAsync(K key) {
     try {
       return Futures.immediateCheckedFuture(get(key));
@@ -40,6 +41,7 @@
     }
   }
 
+  @Override
   public ResultSet<E> get(final Iterable<K> keys) throws OrmException {
     final ArrayList<E> r = new ArrayList<E>();
     for (final K key : keys) {
@@ -51,6 +53,7 @@
     return new ListResultSet<E>(r);
   }
 
+  @Override
   public Map<K, E> toMap(final Iterable<E> c) {
     try {
       final HashMap<K, E> r = new HashMap<K, E>();
diff --git a/src/main/java/com/google/gwtorm/server/AbstractResultSet.java b/src/main/java/com/google/gwtorm/server/AbstractResultSet.java
index c17270b..5eb2803 100644
--- a/src/main/java/com/google/gwtorm/server/AbstractResultSet.java
+++ b/src/main/java/com/google/gwtorm/server/AbstractResultSet.java
@@ -45,6 +45,7 @@
     };
   }
 
+  @Override
   public List<T> toList() {
     List<T> r = new ArrayList<T>();
     for (T obj : this) {
diff --git a/src/main/java/com/google/gwtorm/server/ListResultSet.java b/src/main/java/com/google/gwtorm/server/ListResultSet.java
index de6b224..642dd0c 100644
--- a/src/main/java/com/google/gwtorm/server/ListResultSet.java
+++ b/src/main/java/com/google/gwtorm/server/ListResultSet.java
@@ -26,10 +26,12 @@
     items = r;
   }
 
+  @Override
   public Iterator<T> iterator() {
     return toList().iterator();
   }
 
+  @Override
   public List<T> toList() {
     if (items == null) {
       throw new IllegalStateException("Results already obtained");
@@ -39,6 +41,7 @@
     return r;
   }
 
+  @Override
   public void close() {
     items = null;
   }
diff --git a/src/main/java/com/google/gwtorm/server/ResultSet.java b/src/main/java/com/google/gwtorm/server/ResultSet.java
index cf589c5..18f8c75 100644
--- a/src/main/java/com/google/gwtorm/server/ResultSet.java
+++ b/src/main/java/com/google/gwtorm/server/ResultSet.java
@@ -30,6 +30,7 @@
    * <code>hasNext()</code> returns false) {@link #close()} will be
    * automatically called.
    */
+  @Override
   Iterator<T> iterator();
 
   /**
diff --git a/src/test/java/com/google/gwtorm/data/Person.java b/src/test/java/com/google/gwtorm/data/Person.java
index 4984802..38644da 100644
--- a/src/test/java/com/google/gwtorm/data/Person.java
+++ b/src/test/java/com/google/gwtorm/data/Person.java
@@ -67,6 +67,10 @@
     return age;
   }
 
+  public void setAge(int age) {
+    this.age = age;
+  }
+
   public boolean isRegistered() {
     return registered;
   }
diff --git a/src/test/java/com/google/gwtorm/data/PersonAccess.java b/src/test/java/com/google/gwtorm/data/PersonAccess.java
index 091af8e..af36e7a 100644
--- a/src/test/java/com/google/gwtorm/data/PersonAccess.java
+++ b/src/test/java/com/google/gwtorm/data/PersonAccess.java
@@ -21,6 +21,7 @@
 import com.google.gwtorm.server.ResultSet;
 
 public interface PersonAccess extends Access<Person, Person.Key> {
+  @Override
   @PrimaryKey("name")
   Person get(Person.Key key) throws OrmException;
 
diff --git a/src/test/java/com/google/gwtorm/data/PersonAccess2.java b/src/test/java/com/google/gwtorm/data/PersonAccess2.java
index 46e12cd..a937ef9 100644
--- a/src/test/java/com/google/gwtorm/data/PersonAccess2.java
+++ b/src/test/java/com/google/gwtorm/data/PersonAccess2.java
@@ -19,6 +19,7 @@
 import com.google.gwtorm.server.PrimaryKey;
 
 public interface PersonAccess2 extends Access<Person2, Person.Key> {
+  @Override
   @PrimaryKey("name")
   Person2 get(Person.Key key) throws OrmException;
 }
diff --git a/src/test/java/com/google/gwtorm/jdbc/AbstractTestJdbcAccess.java b/src/test/java/com/google/gwtorm/jdbc/AbstractTestJdbcAccess.java
index c3893cd..3fad032 100644
--- a/src/test/java/com/google/gwtorm/jdbc/AbstractTestJdbcAccess.java
+++ b/src/test/java/com/google/gwtorm/jdbc/AbstractTestJdbcAccess.java
@@ -459,6 +459,7 @@
       super(s);
     }
 
+    @Override
     public String getRelationName() {
       return "Data";
     }
diff --git a/src/test/java/com/google/gwtorm/schema/sql/DialectH2Test.java b/src/test/java/com/google/gwtorm/schema/sql/DialectH2Test.java
index db674fa..ce37fbe 100644
--- a/src/test/java/com/google/gwtorm/schema/sql/DialectH2Test.java
+++ b/src/test/java/com/google/gwtorm/schema/sql/DialectH2Test.java
@@ -129,6 +129,21 @@
   }
 
   @Test
+  public void testListIndexes() throws OrmException, SQLException {
+    assertTrue(dialect.listTables(db).isEmpty());
+
+    execute("CREATE SEQUENCE cnt");
+    execute("CREATE TABLE foo (cnt INT, bar INT, baz INT)");
+    execute("CREATE UNIQUE INDEX FOO_PRIMARY_IND ON foo(cnt)");
+    execute("CREATE INDEX FOO_SECOND_IND ON foo(bar, baz)");
+
+    Set<String> s = dialect.listIndexes(db, "foo");
+    assertEquals(2, s.size());
+    assertTrue(s.contains("foo_primary_ind"));
+    assertTrue(s.contains("foo_second_ind"));
+  }
+
+  @Test
   public void testUpgradeSchema() throws SQLException, OrmException {
     final PhoneBookDb p = phoneBook.open();
     try {
@@ -200,7 +215,7 @@
     PhoneBookDb schema = phoneBook.open();
     schema.updateSchema(executor);
     schema.people().beginTransaction(null);
-    ArrayList<Person> all = new ArrayList<Person>();
+    ArrayList<Person> all = new ArrayList<>();
     all.add(new Person(new Person.Key("Bob"), 18));
     schema.people().insert(all);
     schema.rollback();
@@ -213,7 +228,7 @@
     PhoneBookDb schema = phoneBook.open();
     schema.updateSchema(executor);
     schema.people().beginTransaction(null);
-    ArrayList<Person> all = new ArrayList<Person>();
+    ArrayList<Person> all = new ArrayList<>();
     all.add(new Person(new Person.Key("Bob"), 18));
     schema.people().insert(all);
     schema.commit();
diff --git a/src/test/java/com/google/gwtorm/schema/sql/DialectMaxDBTest.java b/src/test/java/com/google/gwtorm/schema/sql/DialectMaxDBTest.java
new file mode 100644
index 0000000..9bab614
--- /dev/null
+++ b/src/test/java/com/google/gwtorm/schema/sql/DialectMaxDBTest.java
@@ -0,0 +1,358 @@
+// Copyright 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.sql;
+
+import static java.util.Arrays.asList;
+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 static org.junit.Assume.assumeNoException;
+
+import com.google.gwtorm.data.Address;
+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.jdbc.JdbcSchema;
+import com.google.gwtorm.jdbc.SimpleDataSource;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
+import com.google.gwtorm.server.OrmException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+public class DialectMaxDBTest {
+  private static final String MAXDB_URL_KEY = "maxdb.url";
+  private static final String MAXDB_USER_KEY = "maxdb.user";
+  private static final String MAXDB_PASSWORD_KEY = "maxdb.password";
+  private static final String MAXDB_DRIVER = "com.sap.dbtech.jdbc.DriverSapDB";
+  private Connection db;
+  private JdbcExecutor executor;
+  private SqlDialect dialect;
+  private Database<PhoneBookDb> phoneBook;
+  private Database<PhoneBookDb2> phoneBook2;
+
+  @Before
+  public void setUp() throws Exception {
+    try {
+      Class.forName(MAXDB_DRIVER);
+    } catch (Exception e) {
+      assumeNoException(e);
+    }
+
+    final String url = System.getProperty(MAXDB_URL_KEY);
+    final String user = System.getProperty(MAXDB_USER_KEY);
+    final String pass = System.getProperty(MAXDB_PASSWORD_KEY);
+
+    db = DriverManager.getConnection(url, user, pass);
+    executor = new JdbcExecutor(db);
+    dialect = new DialectMaxDB().refine(db);
+
+    final Properties p = new Properties();
+    p.setProperty("driver", MAXDB_DRIVER);
+    p.setProperty("url", db.getMetaData().getURL());
+    p.setProperty("user", user);
+    p.setProperty("password", pass);
+    phoneBook =
+        new Database<PhoneBookDb>(new SimpleDataSource(p), PhoneBookDb.class);
+    phoneBook2 =
+        new Database<PhoneBookDb2>(new SimpleDataSource(p), PhoneBookDb2.class);
+  }
+
+  private void drop(String drop) {
+    try {
+      execute("DROP " + drop);
+    } catch (OrmException e) {
+    }
+  }
+
+  @After
+  public void tearDown() {
+    // Database content must be flushed because
+    // tests assume that the database is empty
+    drop("SEQUENCE address_id");
+    drop("SEQUENCE cnt");
+
+    drop("TABLE addresses");
+    drop("TABLE foo");
+    drop("TABLE bar");
+    drop("TABLE people");
+
+    if (executor != null) {
+      executor.close();
+    }
+    executor = null;
+
+    if (db != null) {
+      try {
+        db.close();
+      } catch (SQLException e) {
+        throw new RuntimeException("Cannot close database", e);
+      }
+    }
+    db = null;
+  }
+
+  private void execute(final String sql) throws OrmException {
+    executor.execute(sql);
+  }
+
+  @Test
+  public void testListSequences() throws OrmException, SQLException {
+    assertTrue(dialect.listSequences(db).isEmpty());
+
+    execute("CREATE SEQUENCE cnt");
+    execute("CREATE TABLE foo (cnt INT)");
+
+    Set<String> s = dialect.listSequences(db);
+    assertEquals(1, s.size());
+    assertTrue(s.contains("cnt"));
+    assertFalse(s.contains("foo"));
+  }
+
+  @Test
+  public void testListTables() throws OrmException, SQLException {
+    assertTrue(dialect.listTables(db).isEmpty());
+
+    execute("CREATE SEQUENCE cnt");
+    execute("CREATE TABLE foo (cnt INT)");
+
+    Set<String> s = dialect.listTables(db);
+    assertEquals(1, s.size());
+    assertFalse(s.contains("cnt"));
+    assertTrue(s.contains("foo"));
+  }
+
+  @Test
+  public void testListIndexes() throws OrmException, SQLException {
+    assertTrue(dialect.listTables(db).isEmpty());
+
+    execute("CREATE SEQUENCE cnt");
+    execute("CREATE TABLE foo (cnt INT, bar INT, baz INT)");
+    execute("CREATE UNIQUE INDEX FOO_PRIMARY_IND ON foo(cnt)");
+    execute("CREATE INDEX FOO_SECOND_IND ON foo(bar, baz)");
+
+    Set<String> s = dialect.listIndexes(db, "foo");
+    assertEquals(2, s.size());
+    assertTrue(s.contains("foo_primary_ind"));
+    assertTrue(s.contains("foo_second_ind"));
+  }
+
+  @Test
+  public void testUpgradeSchema() throws SQLException, OrmException {
+    final PhoneBookDb p = phoneBook.open();
+    try {
+      p.updateSchema(executor);
+
+      execute("CREATE SEQUENCE cnt");
+      execute("CREATE TABLE foo (cnt INT)");
+
+      execute("ALTER TABLE people ADD fake_name VARCHAR(20)");
+      execute("ALTER TABLE people DROP COLUMN registered");
+      execute("DROP TABLE addresses");
+      execute("DROP SEQUENCE address_id");
+
+      Set<String> sequences, tables;
+
+      p.updateSchema(executor);
+      sequences = dialect.listSequences(db);
+      tables = dialect.listTables(db);
+      assertTrue(sequences.contains("cnt"));
+      assertTrue(tables.contains("foo"));
+
+      assertTrue(sequences.contains("address_id"));
+      assertTrue(tables.contains("addresses"));
+
+      p.pruneSchema(executor);
+      sequences = dialect.listSequences(db);
+      tables = dialect.listTables(db);
+      assertFalse(sequences.contains("cnt"));
+      assertFalse(tables.contains("foo"));
+
+      final Person.Key pk = new Person.Key("Bob");
+      final Person bob = new Person(pk, p.nextAddressId());
+      p.people().insert(asList(bob));
+
+      final Address addr =
+          new Address(new Address.Key(pk, "home"), "some place");
+      p.addresses().insert(asList(addr));
+    } finally {
+      p.close();
+    }
+
+    final PhoneBookDb2 p2 = phoneBook2.open();
+    try {
+      ((JdbcSchema) p2).renameField(executor, "people", "registered",
+          "isRegistered");
+    } finally {
+      p2.close();
+    }
+  }
+
+  @Test
+  public void testRenameTable() throws SQLException, OrmException {
+    assertTrue(dialect.listTables(db).isEmpty());
+    execute("CREATE TABLE foo (cnt INT)");
+    Set<String> s = dialect.listTables(db);
+    assertEquals(1, s.size());
+    assertTrue(s.contains("foo"));
+    final PhoneBookDb p = phoneBook.open();
+    try {
+      ((JdbcSchema) p).renameTable(executor, "foo", "bar");
+    } finally {
+      p.close();
+    }
+    s = dialect.listTables(db);
+    assertTrue(s.contains("bar"));
+    assertFalse(s.contains("for"));
+  }
+
+  @Test
+  public void testInsert() throws OrmException {
+    final PhoneBookDb p = phoneBook.open();
+    try {
+      p.updateSchema(executor);
+
+      final Person.Key pk = new Person.Key("Bob");
+      final Person bob = new Person(pk, p.nextAddressId());
+      p.people().insert(asList(bob));
+
+      try {
+        p.people().insert(asList(bob));
+        fail();
+      } catch (OrmDuplicateKeyException duprec) {
+        // expected
+      }
+    } finally {
+      p.close();
+    }
+  }
+
+  @Test
+  public void testConstraintViolationOnIndex() throws OrmException {
+    final PhoneBookDb p = phoneBook.open();
+    try {
+      p.updateSchema(executor);
+
+      execute("CREATE UNIQUE INDEX idx ON people (age)");
+      try {
+
+        final Person.Key pk = new Person.Key("Bob");
+        final Person bob = new Person(pk, p.nextAddressId());
+        bob.setAge(40);
+        p.people().insert(asList(bob));
+
+        final Person.Key joePk = new Person.Key("Joe");
+        Person joe = new Person(joePk, p.nextAddressId());
+        joe.setAge(40);
+        try {
+        p.people().insert(asList(joe));
+        fail();
+        } catch (OrmDuplicateKeyException duprec) {
+          fail();
+        } catch (OrmException noDuprec) {
+          // expeceted
+        }
+      } finally {
+        execute("DROP INDEX idx ON people");
+      }
+    } finally {
+      p.close();
+    }
+  }
+
+  @Test
+  public void testUpdate() throws OrmException {
+    final PhoneBookDb p = phoneBook.open();
+    try {
+      p.updateSchema(executor);
+
+      final Person.Key pk = new Person.Key("Bob");
+      Person bob = new Person(pk, p.nextAddressId());
+      bob.setAge(40);
+      p.people().insert(asList(bob));
+
+      bob.setAge(50);
+      p.people().update(asList(bob));
+
+      bob = p.people().get(pk);
+      assertEquals(50, bob.age());
+    } finally {
+      p.close();
+    }
+  }
+
+  @Test
+  public void testUpsert() throws OrmException {
+    final PhoneBookDb p = phoneBook.open();
+    try {
+      p.updateSchema(executor);
+
+      final Person.Key bobPk = new Person.Key("Bob");
+      Person bob = new Person(bobPk, p.nextAddressId());
+      bob.setAge(40);
+      p.people().insert(asList(bob));
+
+      final Person.Key joePk = new Person.Key("Joe");
+      Person joe = new Person(joePk, p.nextAddressId());
+      bob.setAge(50);
+      p.people().upsert(asList(bob, joe));
+
+      bob = p.people().get(bobPk);
+      assertEquals(50, bob.age());
+      assertNotNull(p.people().get(joePk));
+    } finally {
+      p.close();
+    }
+  }
+
+  @Test
+  public void testRollbackTransaction() throws SQLException, OrmException {
+    PhoneBookDb schema = phoneBook.open();
+    schema.updateSchema(executor);
+    schema.people().beginTransaction(null);
+    ArrayList<Person> all = new ArrayList<>();
+    all.add(new Person(new Person.Key("Bob"), 18));
+    schema.people().insert(all);
+    schema.rollback();
+    List<Person> r = schema.people().olderThan(10).toList();
+    assertEquals(0, r.size());
+  }
+
+  @Test
+  public void testCommitTransaction() throws SQLException, OrmException {
+    PhoneBookDb schema = phoneBook.open();
+    schema.updateSchema(executor);
+    schema.people().beginTransaction(null);
+    ArrayList<Person> all = new ArrayList<>();
+    all.add(new Person(new Person.Key("Bob"), 18));
+    schema.people().insert(all);
+    schema.commit();
+    List<Person> r = schema.people().olderThan(10).toList();
+    assertEquals(1, r.size());
+  }
+}
diff --git a/src/test/java/com/google/gwtorm/schema/sql/DialectMySQLTest.java b/src/test/java/com/google/gwtorm/schema/sql/DialectMySQLTest.java
index 186134a..b0c8db5 100644
--- a/src/test/java/com/google/gwtorm/schema/sql/DialectMySQLTest.java
+++ b/src/test/java/com/google/gwtorm/schema/sql/DialectMySQLTest.java
@@ -143,6 +143,20 @@
   }
 
   @Test
+  public void testListIndexes() throws OrmException, SQLException {
+    assertTrue(dialect.listTables(db).isEmpty());
+
+    execute("CREATE TABLE foo (cnt INT, bar INT, baz INT)");
+    execute("CREATE UNIQUE INDEX FOO_PRIMARY_IND ON foo(cnt)");
+    execute("CREATE INDEX FOO_SECOND_IND ON foo(bar, baz)");
+
+    Set<String> s = dialect.listIndexes(db, "foo");
+    assertEquals(2, s.size());
+    assertTrue(s.contains("foo_primary_ind"));
+    assertTrue(s.contains("foo_second_ind"));
+  }
+
+  @Test
   public void testUpgradeSchema() throws SQLException, OrmException {
     final PhoneBookDb p = phoneBook.open();
     try {
@@ -216,7 +230,7 @@
     PhoneBookDb schema = phoneBook.open();
     schema.updateSchema(executor);
     schema.people().beginTransaction(null);
-    ArrayList<Person> all = new ArrayList<Person>();
+    ArrayList<Person> all = new ArrayList<>();
     all.add(new Person(new Person.Key("Bob"), 18));
     schema.people().insert(all);
     schema.rollback();
@@ -229,7 +243,7 @@
     PhoneBookDb schema = phoneBook.open();
     schema.updateSchema(executor);
     schema.people().beginTransaction(null);
-    ArrayList<Person> all = new ArrayList<Person>();
+    ArrayList<Person> all = new ArrayList<>();
     all.add(new Person(new Person.Key("Bob"), 18));
     schema.people().insert(all);
     schema.commit();
diff --git a/src/test/java/com/google/gwtorm/schema/sql/DialectOracleSQLTest.java b/src/test/java/com/google/gwtorm/schema/sql/DialectOracleSQLTest.java
index 8f75bb1..bb4d7fb 100644
--- a/src/test/java/com/google/gwtorm/schema/sql/DialectOracleSQLTest.java
+++ b/src/test/java/com/google/gwtorm/schema/sql/DialectOracleSQLTest.java
@@ -143,6 +143,21 @@
   }
 
   @Test
+  public void testListIndexes() throws OrmException, SQLException {
+    assertTrue(dialect.listTables(db).isEmpty());
+
+    execute("CREATE SEQUENCE cnt");
+    execute("CREATE TABLE foo (cnt INT, bar INT, baz INT)");
+    execute("CREATE UNIQUE INDEX FOO_PRIMARY_IND ON foo(cnt)");
+    execute("CREATE INDEX FOO_SECOND_IND ON foo(bar, baz)");
+
+    Set<String> s = dialect.listIndexes(db, "foo");
+    assertEquals(2, s.size());
+    assertTrue(s.contains("foo_primary_ind"));
+    assertTrue(s.contains("foo_second_ind"));
+  }
+
+  @Test
   public void testUpgradeSchema() throws SQLException, OrmException {
     final PhoneBookDb p = phoneBook.open();
     try {
@@ -216,7 +231,7 @@
     PhoneBookDb schema = phoneBook.open();
     schema.updateSchema(executor);
     schema.people().beginTransaction(null);
-    ArrayList<Person> all = new ArrayList<Person>();
+    ArrayList<Person> all = new ArrayList<>();
     all.add(new Person(new Person.Key("Bob"), 18));
     schema.people().insert(all);
     schema.rollback();
@@ -229,7 +244,7 @@
     PhoneBookDb schema = phoneBook.open();
     schema.updateSchema(executor);
     schema.people().beginTransaction(null);
-    ArrayList<Person> all = new ArrayList<Person>();
+    ArrayList<Person> all = new ArrayList<>();
     all.add(new Person(new Person.Key("Bob"), 18));
     schema.people().insert(all);
     schema.commit();
diff --git a/src/test/java/com/google/gwtorm/schema/sql/DialectPostgreSQLTest.java b/src/test/java/com/google/gwtorm/schema/sql/DialectPostgreSQLTest.java
index 0330b18..aae302f 100644
--- a/src/test/java/com/google/gwtorm/schema/sql/DialectPostgreSQLTest.java
+++ b/src/test/java/com/google/gwtorm/schema/sql/DialectPostgreSQLTest.java
@@ -142,6 +142,20 @@
   }
 
   @Test
+  public void testListIndexes() throws OrmException, SQLException {
+    assertTrue(dialect.listTables(db).isEmpty());
+
+    execute("CREATE TABLE foo (cnt INT, bar INT, baz INT)");
+    execute("CREATE UNIQUE INDEX FOO_PRIMARY_IND ON foo(cnt)");
+    execute("CREATE INDEX FOO_SECOND_IND ON foo(bar, baz)");
+
+    Set<String> s = dialect.listIndexes(db, "foo");
+    assertEquals(2, s.size());
+    assertTrue(s.contains("foo_primary_ind"));
+    assertTrue(s.contains("foo_second_ind"));
+  }
+
+  @Test
   public void testUpgradeSchema() throws SQLException, OrmException {
     final PhoneBookDb p = phoneBook.open();
     try {
@@ -215,7 +229,7 @@
     PhoneBookDb schema = phoneBook.open();
     schema.updateSchema(executor);
     schema.people().beginTransaction(null);
-    ArrayList<Person> all = new ArrayList<Person>();
+    ArrayList<Person> all = new ArrayList<>();
     all.add(new Person(new Person.Key("Bob"), 18));
     schema.people().insert(all);
     schema.rollback();
@@ -228,7 +242,7 @@
     PhoneBookDb schema = phoneBook.open();
     schema.updateSchema(executor);
     schema.people().beginTransaction(null);
-    ArrayList<Person> all = new ArrayList<Person>();
+    ArrayList<Person> all = new ArrayList<>();
     all.add(new Person(new Person.Key("Bob"), 18));
     schema.people().insert(all);
     schema.commit();