Permalink
Browse files

Adding a ComplexKey class and tests for querying with arrays.

This changeset extends the capabilities of the ComplexKey
class, fixes a few issues and adds test cases for all
supported operations. It also adds docblocks where
appropriate.

Change-Id: I9e0e31179e08fa482f7615370798b7af254dfdc5
Reviewed-on: http://review.couchbase.org/21353
Tested-by: Michael Wiederhold <mike@couchbase.com>
Reviewed-by: Michael Wiederhold <mike@couchbase.com>
  • Loading branch information...
daschl authored and mikewied committed Sep 16, 2012
1 parent 58c1c9f commit 2178868be2bd3212d19e02943da26c72762509d3
@@ -0,0 +1,149 @@
+/**
+ * Copyright (C) 2009-2012 Couchbase, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALING
+ * IN THE SOFTWARE.
+ */
+
+package com.couchbase.client.protocol.views;
+
+import java.util.Arrays;
+import java.util.List;
+import org.codehaus.jettison.json.JSONArray;
+import org.codehaus.jettison.json.JSONObject;
+
+/**
+ * Allows simple definition of complex JSON keys for query inputs.
+ *
+ * If you use the ComplexKex class, the stored objects ultimately get converted
+ * into a JSON string. As a result, make sure your custom objects implement the
+ * "toString" method accordingly (unless you work with trivial types like
+ * Strings or numbers).
+ *
+ * Instead of using a constructor, use the static "of" method to generate your
+ * objects. You can also use a special empty object or array.
+ *
+ * Here are some simple examples:
+ *
+ * // generated JSON: [2012,9,7]
+ * ComplexKey.of(2012, 9, 7);
+ *
+ * // generated JSON: ["Hello","World",5.12]
+ * ComplexKey.of("Hello", "World", 5.12);
+ *
+ * // generated JSON: {}
+ * ComplexKey.of(ComplexKey.emptyObject());
+ *
+ * // generated JSON: []
+ * ComplexKey.of(ComplexKey.emptyArray());
+ *
+ * This was inspired by the Ektorp project, which queries Apache CouchDB.
+ */
+public final class ComplexKey {
+
+ /**
+ * Holds the list of object components to convert.
+ */
+ private final List<Object> components;
+
+ /**
+ * Defines the empty object to use.
+ */
+ private static final Object EMPTY_OBJECT = new Object();
+
+ /**
+ * Defines the empty array to use.
+ */
+ private static final Object[] EMPTY_ARRAY = new Object[0];
+
+ /**
+ * Private constructor used by the "of" or other factory methods.
+ *
+ * @param components List of objects that should be converted
+ */
+ private ComplexKey(Object[] components) {
+ this.components = Arrays.asList(components);
+ }
+
+ /**
+ * Generate a ComplexKey based on the input Object arguments (varargs).
+ *
+ * This method is most often used along with the Query object and done
+ * when new a complex key is used as a query input. For example, to query
+ * with the array of integers 2012, 9, 5 (a common method of setting up
+ * reduceable date queries) one may do something like:
+ *
+ * ComplexKey.of(2012, 9, 5);
+ *
+ * @param components List of objects that should be converted
+ * @return Returns a new instance of ComplexKey
+ */
+ public static ComplexKey of(Object... components) {
+ return new ComplexKey(components);
+ }
+
+ /**
+ * Returns a single empty object.
+ *
+ * @return Returns the empty object
+ */
+ public static Object emptyObject() {
+ return EMPTY_OBJECT;
+ }
+
+ /**
+ * Returns an empty array of objects.
+ *
+ * @return Returns an empty array of objects
+ */
+ public static Object[] emptyArray() {
+ return EMPTY_ARRAY;
+ }
+
+
+ /**
+ * Generate a JSON string of the ComplexKey.
+ *
+ * This method is responsible for processing and converting the
+ * complex key list and returning it as a JSON string. This string
+ * heavily depends on the structure of the stored objects.
+ *
+ * @return the JSON of the underlying complex key
+ */
+ public String toJson() {
+ if(components.size() == 1) {
+ if(components.get(0) == EMPTY_OBJECT) {
+ return new JSONObject().toString();
+ } else {
+ return components.get(0).toString();
+ }
+ }
+
+ JSONArray key = new JSONArray();
+ for (Object component : components) {
+ if (component == EMPTY_OBJECT) {
+ key.put(new JSONObject());
+ } else {
+ key.put(component);
+ }
+ }
+
+ return key.toString();
+ }
+
+}
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2009-2011 Couchbase, Inc.
+ * Copyright (C) 2009-2012 Couchbase, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -39,6 +39,7 @@
private static final String GROUPLEVEL = "group_level";
private static final String INCLUSIVEEND = "inclusive_end";
private static final String KEY = "key";
+ private static final String KEYS = "keys";
private static final String LIMIT = "limit";
private static final String REDUCE = "reduce";
private static final String SKIP = "skip";
@@ -94,11 +95,58 @@ public Query setInclusiveEnd(boolean inclusiveend) {
return this;
}
+ /**
+ * Return only documents that match the specified key.
+ *
+ * The "key" param must be specified as a valid JSON string, but the
+ * ComplexKey class takes care of this. See the documentation of the
+ * ComplexKey class for more information on its usage.
+ *
+ * @param key The document key.
+ * @return The Query instance.
+ */
+ public Query setKey(ComplexKey key) {
+ args.put(KEY, key.toJson());
+ return this;
+ }
+
public Query setKey(String key) {
args.put(KEY, key);
return this;
}
+ /**
+ * Return only documents that match each of keys specified within the given
+ * array.
+ *
+ * The "keys" param must be specified as a valid JSON string, but the
+ * ComplexKey class takes care of this. See the documentation of the
+ * ComplexKey class for more information on its usage.
+ *
+ * Also, sorting is not applied when using this option.
+ *
+ * @param keys The document keys.
+ * @return The Query instance.
+ */
+ public Query setKeys(ComplexKey keys) {
+ args.put(KEYS, keys.toJson());
+ return this;
+ }
+
+ /**
+ * Return only documents that match each of keys specified within the given
+ * array.
+ *
+ * Note that the given key string has to be valid JSON! Also, sorting is not
+ * applied when using this option.
+ *
+ * @param keys The document keys.
+ * @return The Query instance.
+ */
+ public Query setKeys(String keys) {
+ args.put(KEYS, keys);
+ return this;
+ }
public Query setLimit(int limit) {
args.put(LIMIT, Integer.valueOf(limit));
return this;
@@ -118,11 +166,43 @@ public Query setRange(String startkey, String endkey) {
return this;
}
+ /**
+ * Returns records in the given key range.
+ *
+ * The range keys must be specified as a valid JSON strings, but the
+ * ComplexKey class takes care of this. See the documentation of the
+ * ComplexKey class for more information on its usage.
+ *
+ * @param startkey The start of the key range.
+ * @param endkey The end of the key range.
+ * @return The Query instance.
+ */
+ public Query setRange(ComplexKey startkey, ComplexKey endkey) {
+ args.put(ENDKEY, endkey.toJson());
+ args.put(STARTKEY, startkey.toJson());
+ return this;
+ }
+
public Query setRangeStart(String startkey) {
args.put(STARTKEY, startkey);
return this;
}
+ /**
+ * Return records with a value equal to or greater than the specified key.
+ *
+ * The range key must be specified as a valid JSON string, but the
+ * ComplexKey class takes care of this. See the documentation of the
+ * ComplexKey class for more information on its usage.
+ *
+ * @param startkey The start of the key range.
+ * @return The Query instance.
+ */
+ public Query setRangeStart(ComplexKey startkey) {
+ args.put(STARTKEY, startkey.toJson());
+ return this;
+ }
+
public Query setReduce(boolean reduce) {
args.put(REDUCE, new Boolean(reduce));
return this;
@@ -133,6 +213,21 @@ public Query setRangeEnd(String endkey) {
return this;
}
+ /**
+ * Stop returning records when the specified key is reached.
+ *
+ * The range key must be specified as a valid JSON string, but the
+ * ComplexKey class takes care of this. See the documentation of the
+ * ComplexKey class for more information on its usage.
+ *
+ * @param endkey The end of the key range.
+ * @return The Query instance.
+ */
+ public Query setRangeEnd(ComplexKey endkey) {
+ args.put(ENDKEY, endkey.toJson());
+ return this;
+ }
+
public Query setSkip(int docstoskip) {
args.put(SKIP, Integer.valueOf(docstoskip));
return this;
@@ -182,6 +277,9 @@ public Query copy() {
if (args.containsKey(KEY)) {
query.setKey(((String)args.get(KEY)));
}
+ if (args.containsKey(KEYS)) {
+ query.setKeys(((String)args.get(KEYS)));
+ }
if (args.containsKey(LIMIT)) {
query.setLimit(((Integer)args.get(LIMIT)).intValue());
}
Oops, something went wrong.

0 comments on commit 2178868

Please sign in to comment.