From a37bc78d4af3c8de89cfaefa04accf455fc58339 Mon Sep 17 00:00:00 2001
From: k1xme <kexi@google.com>
Date: Fri, 16 Dec 2016 11:19:40 -0500
Subject: [PATCH] [azuredocumentdb] Added support for Azure DocumentDB (#838)

---
 azuredocumentdb/pom.xml                       |  43 +++
 .../AzureDocumentDBClient.java                | 257 ++++++++++++++++++
 .../ycsb/db/azuredocumentdb/package-info.java |  22 ++
 bin/bindings.properties                       |   1 +
 bin/ycsb                                      |   1 +
 distribution/pom.xml                          |   5 +
 pom.xml                                       |   2 +
 7 files changed, 331 insertions(+)
 create mode 100644 azuredocumentdb/pom.xml
 create mode 100644 azuredocumentdb/src/main/java/com/yahoo/ycsb/db/azuredocumentdb/AzureDocumentDBClient.java
 create mode 100644 azuredocumentdb/src/main/java/com/yahoo/ycsb/db/azuredocumentdb/package-info.java

diff --git a/azuredocumentdb/pom.xml b/azuredocumentdb/pom.xml
new file mode 100644
index 00000000..0f7363e9
--- /dev/null
+++ b/azuredocumentdb/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (c) 2016 YCSB contributors. All rights reserved.
+
+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. See accompanying
+LICENSE file.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.yahoo.ycsb</groupId>
+    <artifactId>binding-parent</artifactId>
+    <version>0.13.0-SNAPSHOT</version>
+    <relativePath>../binding-parent/</relativePath>
+  </parent>
+
+  <artifactId>azuredocumentdb-binding</artifactId>
+  <name>Azure DocumentDB Binding</name>
+  <dependencies>
+    <dependency>
+      <groupId>com.microsoft.azure</groupId>
+      <artifactId>azure-documentdb</artifactId>
+      <version>${azuredocumentdb.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.yahoo.ycsb</groupId>
+      <artifactId>core</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/azuredocumentdb/src/main/java/com/yahoo/ycsb/db/azuredocumentdb/AzureDocumentDBClient.java b/azuredocumentdb/src/main/java/com/yahoo/ycsb/db/azuredocumentdb/AzureDocumentDBClient.java
new file mode 100644
index 00000000..ee9e9647
--- /dev/null
+++ b/azuredocumentdb/src/main/java/com/yahoo/ycsb/db/azuredocumentdb/AzureDocumentDBClient.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2016 YCSB Contributors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * This file 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.yahoo.ycsb.db.azuredocumentdb;
+
+import com.yahoo.ycsb.*;
+
+import com.microsoft.azure.documentdb.ConnectionPolicy;
+import com.microsoft.azure.documentdb.ConsistencyLevel;
+import com.microsoft.azure.documentdb.Database;
+import com.microsoft.azure.documentdb.Document;
+import com.microsoft.azure.documentdb.DocumentClient;
+import com.microsoft.azure.documentdb.DocumentClientException;
+import com.microsoft.azure.documentdb.DocumentCollection;
+import com.microsoft.azure.documentdb.FeedOptions;
+
+import java.util.HashMap;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Vector;
+import java.util.List;
+
+/**
+ * Azure DocumentDB client binding.
+ */
+public class AzureDocumentDBClient extends DB {
+  private static String host;
+  private static String masterKey;
+  private static String databaseId;
+  private static Database database;
+  private static DocumentClient documentClient;
+  private static DocumentCollection collection;
+  private static FeedOptions feedOptions;
+
+  @Override
+  public void init() throws DBException {
+    host = getProperties().getProperty("documentdb.host", null);
+    masterKey = getProperties().getProperty("documentdb.masterKey", null);
+
+    if (host == null) {
+      System.err.println("ERROR: 'documentdb.host' must be set!");
+      System.exit(1);
+    }
+
+    if (masterKey == null) {
+      System.err.println("ERROR: 'documentdb.masterKey' must be set!");
+      System.exit(1);
+    }
+
+    databaseId = getProperties().getProperty("documentdb.databaseId", "ycsb");
+    String collectionId =
+        getProperties().getProperty("documentdb.collectionId", "usertable");
+    documentClient =
+        new DocumentClient(host, masterKey, ConnectionPolicy.GetDefault(),
+                           ConsistencyLevel.Session);
+    try {
+      // Initialize test database and collection.
+      collection = getCollection(collectionId);
+    } catch (DocumentClientException e) {
+      throw new DBException("Initialze collection failed", e);
+    }
+
+    feedOptions = new FeedOptions();
+    feedOptions.setEmitVerboseTracesInQuery(false);
+  }
+
+  @Override
+  public Status read(String table, String key, Set<String> fields,
+                     HashMap<String, ByteIterator> result) {
+    Document record = getDocumentById(table, key);
+
+    if (record != null) {
+      Set<String> fieldsToReturn =
+          (fields == null ? record.getHashMap().keySet() : fields);
+
+      for (String field : fieldsToReturn) {
+        if (field.startsWith("_")) {
+          continue;
+        }
+        result.put(field, new StringByteIterator(record.getString(field)));
+      }
+      return Status.OK;
+    }
+    // Unable to find the specidifed document.
+    return Status.ERROR;
+  }
+
+  @Override
+  public Status update(String table, String key,
+                       HashMap<String, ByteIterator> values) {
+    Document record = getDocumentById(table, key);
+
+    if (record == null) {
+      return Status.ERROR;
+    }
+
+    // Update each field.
+    for (Entry<String, ByteIterator> val : values.entrySet()) {
+      record.set(val.getKey(), val.getValue().toString());
+    }
+
+    // Replace the document.
+    try {
+      documentClient.replaceDocument(record, null);
+    } catch (DocumentClientException e) {
+      e.printStackTrace(System.err);
+      return Status.ERROR;
+    }
+
+    return Status.OK;
+  }
+
+  @Override
+  public Status insert(String table, String key,
+                       HashMap<String, ByteIterator> values) {
+    Document record = new Document();
+
+    record.set("id", key);
+
+    for (Entry<String, ByteIterator> val : values.entrySet()) {
+      record.set(val.getKey(), val.getValue().toString());
+    }
+
+    try {
+      documentClient.createDocument(collection.getSelfLink(), record, null,
+                                    false);
+    } catch (DocumentClientException e) {
+      e.printStackTrace(System.err);
+      return Status.ERROR;
+    }
+    return Status.OK;
+  }
+
+  @Override
+  public Status delete(String table, String key) {
+    Document record = getDocumentById(table, key);
+
+    try {
+      // Delete the document by self link.
+      documentClient.deleteDocument(record.getSelfLink(), null);
+    } catch (DocumentClientException e) {
+      e.printStackTrace();
+      return Status.ERROR;
+    }
+
+    return Status.OK;
+  }
+
+  @Override
+  public Status scan(String table, String startkey, int recordcount,
+                     Set<String> fields,
+                     Vector<HashMap<String, ByteIterator>> result) {
+    // TODO: Implement Scan as query on primary key.
+    return Status.NOT_IMPLEMENTED;
+  }
+
+  private Database getDatabase() {
+    if (database == null) {
+      // Get the database if it exists
+      List<Database> databaseList =
+          documentClient
+              .queryDatabases(
+                  "SELECT * FROM root r WHERE r.id='" + databaseId + "'", null)
+              .getQueryIterable()
+              .toList();
+
+      if (databaseList.size() > 0) {
+        // Cache the database object so we won't have to query for it
+        // later to retrieve the selfLink.
+        database = databaseList.get(0);
+      } else {
+        // Create the database if it doesn't exist.
+        try {
+          Database databaseDefinition = new Database();
+          databaseDefinition.setId(databaseId);
+
+          database = documentClient.createDatabase(databaseDefinition, null)
+                         .getResource();
+        } catch (DocumentClientException e) {
+          // TODO: Something has gone terribly wrong - the app wasn't
+          // able to query or create the collection.
+          // Verify your connection, endpoint, and key.
+          e.printStackTrace(System.err);
+        }
+      }
+    }
+
+    return database;
+  }
+
+  private DocumentCollection getCollection(String collectionId)
+      throws DocumentClientException {
+    if (collection == null) {
+      // Get the collection if it exists.
+      List<DocumentCollection> collectionList =
+          documentClient
+              .queryCollections(getDatabase().getSelfLink(),
+                                "SELECT * FROM root r WHERE r.id='" +
+                                    collectionId + "'",
+                                null)
+              .getQueryIterable()
+              .toList();
+
+      if (collectionList.size() > 0) {
+        // Cache the collection object so we won't have to query for it
+        // later to retrieve the selfLink.
+        collection = collectionList.get(0);
+      } else {
+        // Create the collection if it doesn't exist.
+        try {
+          DocumentCollection collectionDefinition = new DocumentCollection();
+          collectionDefinition.setId(collectionId);
+
+          collection = documentClient
+                           .createCollection(getDatabase().getSelfLink(),
+                                             collectionDefinition, null)
+                           .getResource();
+        } catch (DocumentClientException e) {
+          // TODO: Something has gone terribly wrong - the app wasn't
+          // able to query or create the collection.
+          // Verify your connection, endpoint, and key.
+          e.printStackTrace(System.err);
+          throw e;
+        }
+      }
+    }
+
+    return collection;
+  }
+
+  private Document getDocumentById(String collectionId, String id) {
+    if (collection == null) {
+      return null;
+    }
+    // Retrieve the document using the DocumentClient.
+    List<Document> documentList =
+        documentClient
+            .queryDocuments(collection.getSelfLink(),
+                            "SELECT * FROM root r WHERE r.id='" + id + "'",
+                            feedOptions)
+            .getQueryIterable()
+            .toList();
+
+    if (documentList.size() > 0) {
+      return documentList.get(0);
+    }
+    return null;
+  }
+}
diff --git a/azuredocumentdb/src/main/java/com/yahoo/ycsb/db/azuredocumentdb/package-info.java b/azuredocumentdb/src/main/java/com/yahoo/ycsb/db/azuredocumentdb/package-info.java
new file mode 100644
index 00000000..7a116647
--- /dev/null
+++ b/azuredocumentdb/src/main/java/com/yahoo/ycsb/db/azuredocumentdb/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016 YCSB Contributors. All Rights Reserved.
+ *
+ * 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. See accompanying
+ * LICENSE file.
+ */
+
+/**
+ * The YCSB binding for Azure DocumentDB.
+ */
+package com.yahoo.ycsb.db.azuredocumentdb;
+
diff --git a/bin/bindings.properties b/bin/bindings.properties
index 580ae742..aa75f886 100644
--- a/bin/bindings.properties
+++ b/bin/bindings.properties
@@ -35,6 +35,7 @@ cassandra-cql:com.yahoo.ycsb.db.CassandraCQLClient
 cassandra2-cql:com.yahoo.ycsb.db.CassandraCQLClient
 couchbase:com.yahoo.ycsb.db.CouchbaseClient
 couchbase2:com.yahoo.ycsb.db.couchbase2.Couchbase2Client
+azuredocumentdb:com.yahoo.ycsb.db.azuredocumentdb.AzureDocumentDBClient
 dynamodb:com.yahoo.ycsb.db.DynamoDBClient
 elasticsearch:com.yahoo.ycsb.db.ElasticsearchClient
 geode:com.yahoo.ycsb.db.GeodeClient
diff --git a/bin/ycsb b/bin/ycsb
index 72e218a5..36175b40 100755
--- a/bin/ycsb
+++ b/bin/ycsb
@@ -60,6 +60,7 @@ DATABASES = {
     "cassandra2-cql": "com.yahoo.ycsb.db.CassandraCQLClient",
     "couchbase"    : "com.yahoo.ycsb.db.CouchbaseClient",
     "couchbase2"   : "com.yahoo.ycsb.db.couchbase2.Couchbase2Client",
+    "azuredocumentdb"   : "com.yahoo.ycsb.db.azuredocumentdb.AzureDocumentDBClient",
     "dynamodb"     : "com.yahoo.ycsb.db.DynamoDBClient",
     "elasticsearch": "com.yahoo.ycsb.db.ElasticsearchClient",
     "geode"        : "com.yahoo.ycsb.db.GeodeClient",
diff --git a/distribution/pom.xml b/distribution/pom.xml
index 72ecfcd8..36b4ec07 100644
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -74,6 +74,11 @@ LICENSE file.
       <artifactId>couchbase2-binding</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>com.yahoo.ycsb</groupId>
+      <artifactId>azuredocumentdb-binding</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>com.yahoo.ycsb</groupId>
       <artifactId>dynamodb-binding</artifactId>
diff --git a/pom.xml b/pom.xml
index 6f935968..b32487a3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,6 +75,7 @@ LICENSE file.
     <accumulo.version>1.6.0</accumulo.version>
     <cassandra.cql.version>3.0.0</cassandra.cql.version>
     <geode.version>1.0.0-incubating.M3</geode.version>
+    <azuredocumentdb.version>1.8.1</azuredocumentdb.version>
     <googlebigtable.version>0.2.3</googlebigtable.version>
     <infinispan.version>7.2.2.Final</infinispan.version>
     <kudu.version>1.1.0</kudu.version>
@@ -114,6 +115,7 @@ LICENSE file.
     <module>couchbase</module>
     <module>couchbase2</module>
     <module>distribution</module>
+    <module>azuredocumentdb</module>
     <module>dynamodb</module>
     <module>elasticsearch</module>
     <module>geode</module>
-- 
GitLab