Add Cloud Spanner dialect support

Example events-log.config update:

[events-log]
    storeDriver = com.google.cloud.spanner.jdbc.JdbcDriver
    storeUrl = jdbc:cloudspanner:/projects/project-name/instances/instance-name/databases/db-name

As the Spanner jdbc driver does not allow allowMultiQueries it
was necessary to add a Spanner-specific case in index creation.

Spanner does not support AUTOINCREMENT and recommends against
implementing it as it creates hotspots, so we use GENERATE_UUID()
for primary key creation:
https://cloud.google.com/spanner/docs/schema-and-data-model#choosing_a_primary_key

Change-Id: I7c9ead0ff59af681c13d753f13a19e36cc44dd0c
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLClient.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLClient.java
index b554ecd..0949935 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLClient.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLClient.java
@@ -66,7 +66,14 @@
    */
   void createDBIfNotCreated() throws SQLException {
     execute(SQLTable.createTableQuery(databaseDialect));
-    execute(SQLTable.createIndexes(databaseDialect));
+    switch (databaseDialect) {
+      case SPANNER:
+        execute(SQLTable.createSpannerDateIndex());
+        execute(SQLTable.createSpannerProjectIndex());
+        break;
+      default:
+        execute(SQLTable.createIndexes(databaseDialect));
+    }
   }
 
   /**
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialect.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialect.java
index 10df147..c6eade5 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialect.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialect.java
@@ -18,7 +18,8 @@
 public enum SQLDialect {
   H2,
   MYSQL,
-  POSTGRESQL;
+  POSTGRESQL,
+  SPANNER;
 
   /**
    * This attempts to determine the SQL dialect from the JDBC URL. If the URL does not match one of
@@ -32,6 +33,8 @@
       return POSTGRESQL;
     } else if (jdbcUrl.contains("mysql")) {
       return MYSQL;
+    } else if (jdbcUrl.contains("cloudspanner")) {
+      return SPANNER;
     }
     return H2;
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLTable.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLTable.java
index 6093a38..2e9378f 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLTable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLTable.java
@@ -59,6 +59,13 @@
           + "END IF;\n"
           + "END$$;";
 
+  /**
+   * This is the Spanner idempotent index-creation query format. Inputs, in order: index-name,
+   * table-name, index-column
+   */
+  private static final String SPANNER_INDEX_CREATION_FORMAT =
+      "CREATE INDEX IF NOT EXISTS %s ON %s (%s)";
+
   private SQLTable() {}
 
   static String createTableQuery(SQLDialect databaseDialect) {
@@ -68,14 +75,26 @@
       case POSTGRESQL:
         query.append(format("%s SERIAL PRIMARY KEY,", PRIMARY_ENTRY));
         break;
+      case SPANNER:
+        query.append(format("%s STRING(36) DEFAULT (GENERATE_UUID()), ", PRIMARY_ENTRY));
+        break;
       case MYSQL:
       case H2:
       default:
         query.append(format("%s INT AUTO_INCREMENT PRIMARY KEY,", PRIMARY_ENTRY));
     }
-    query.append(format("%s VARCHAR(255),", PROJECT_ENTRY));
-    query.append(format("%s TIMESTAMP DEFAULT NOW(),", DATE_ENTRY));
-    query.append(format("%s TEXT)", EVENT_ENTRY));
+    switch (databaseDialect) {
+      case SPANNER:
+        query.append(format("%s STRING(255),", PROJECT_ENTRY));
+        query.append(format("%s TIMESTAMP DEFAULT (CURRENT_TIMESTAMP()),", DATE_ENTRY));
+        query.append(format("%s STRING(MAX))", EVENT_ENTRY));
+        query.append(format(" PRIMARY KEY (%s)", PRIMARY_ENTRY));
+        break;
+      default:
+        query.append(format("%s VARCHAR(255),", PROJECT_ENTRY));
+        query.append(format("%s TIMESTAMP DEFAULT NOW(),", DATE_ENTRY));
+        query.append(format("%s TEXT)", EVENT_ENTRY));
+    }
     return query.toString();
   }
 
@@ -140,4 +159,16 @@
     query.append(format(H2_INDEX_CREATION_FORMAT, PROJECT_INDEX, TABLE_NAME, PROJECT_ENTRY));
     return query.toString();
   }
+
+  static String createSpannerDateIndex() {
+    StringBuilder query = new StringBuilder();
+    query.append(format(SPANNER_INDEX_CREATION_FORMAT, CREATED_INDEX, TABLE_NAME, DATE_ENTRY));
+    return query.toString();
+  }
+
+  static String createSpannerProjectIndex() {
+    StringBuilder query = new StringBuilder();
+    query.append(format(SPANNER_INDEX_CREATION_FORMAT, PROJECT_INDEX, TABLE_NAME, PROJECT_ENTRY));
+    return query.toString();
+  }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialectTest.java b/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialectTest.java
index 60b7cb1..96802ac 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialectTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialectTest.java
@@ -35,4 +35,9 @@
   public void postgresqlIsParsed() throws Exception {
     assertThat(SQLDialect.fromJdbcUrl("jdbc:postgresql://")).isEqualTo(SQLDialect.POSTGRESQL);
   }
+
+  @Test
+  public void spannerIsParsed() throws Exception {
+    assertThat(SQLDialect.fromJdbcUrl("jdbc:cloudspanner://")).isEqualTo(SQLDialect.SPANNER);
+  }
 }