import java.io.OutputStream;
/**
+ * Util class for encode/decode BSON objects
+ *
* @author Tyutyunkov Vyacheslav (tve@softmotions.com)
* @version $Id$
*/
private BSON() {
}
+ /**
+ * Encode BSON object to plain byte array
+ */
public static byte[] encode(BSONObject obj){
return new BSONEncoder().encode(obj);
}
+ /**
+ * Decode BSON object from plain byte array
+ */
public static BSONObject decode(byte[] data) {
return new BSONDecoder().decode(data);
}
import java.util.regex.Pattern;
/**
+ * Default BSON object decoder (from plain byte array to Java object)
+ *
* @author Tyutyunkov Vyacheslav (tve@softmotions.com)
* @version $Id$
*/
private InputBuffer input;
- public BSONObject decode(byte[] data) {
+ /**
+ * Decode BSON object
+ *
+ * @throws IllegalStateException if other decoding process active with this decoder
+ */
+ public BSONObject decode(byte[] data) throws IllegalStateException {
if (isBusy()) {
throw new IllegalStateException("other decoding in process");
}
return result;
}
+ /**
+ * @return <code>true</code> if decoder currently in use
+ */
public boolean isBusy() {
return input != null;
}
import java.util.regex.Pattern;
/**
+ * Default BSON object encoder (from Java object to plain byte array)
+ *
* @author Tyutyunkov Vyacheslav (tve@softmotions.com)
* @version $Id$
*/
private OutputBuffer output;
+ /**
+ * Encode BSON object
+ *
+ * @throws IllegalStateException if other encoding process active with this encoder
+ */
public byte[] encode(BSONObject object) throws IllegalStateException {
if (isBusy()) {
throw new IllegalStateException("other encoding in process");
return result;
}
+ /**
+ * @return <code>true</code> if encoder currently in use
+ */
public boolean isBusy() {
return output != null;
}
output.writeInt(0);
- for (String field : object.keySet()) {
+ for (String field : object.fields()) {
writeField(field, object.get(field));
}
import org.ejdb.bson.types.ObjectId;
import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Set;
/**
+ * BSON object.
+ *
+ * NOTE:
+ * - {@link BSONObject#ID_KEY} must be valid {@link ObjectId}((@link ObjectId} instance or valid <code>byte[]</code> or <code>String</code>)
+ *
* @author Tyutyunkov Vyacheslav (tve@softmotions.com)
* @version $Id$
*/
public class BSONObject {
+ /**
+ * ID-field name
+ */
public static final String ID_KEY = "_id";
- private Map<String, Object> data;
+ protected Map<String, Object> data;
+ protected List<String> fields;
{
data = new HashMap<String, Object>();
+ fields = new ArrayList<String>();
}
+ /**
+ * Constructs new BSON object
+ */
public BSONObject() {
}
+ /**
+ * Constructs new BSON object with specified id
+ */
public BSONObject(ObjectId oid) {
this(ID_KEY, oid);
}
+ /**
+ * Constructs new BSON object with initial data.
+ * The same as:
+ * <code>
+ * BSONObject obj = new BSONObject();
+ * obj.put(key, value);
+ * </code>
+ */
public BSONObject(String key, Object value) {
this.put(key, value);
}
+ /**
+ * Constructs new BSON object and init data from specified Map.
+ * The same as
+ * <code>
+ * BSONObject obj = new BSONObject();
+ * obj.putAll(data);
+ * </code>
+ */
public BSONObject(Map<String, Object> data) {
this.putAll(data);
}
- public Object put(String key, Object value) {
+ /**
+ * Constructs new BSON object as copy of other BSON object.
+ */
+ public BSONObject(BSONObject src) {
+ if (src != null) {
+ this.putAll(src);
+ }
+ }
+
+ protected Object registerField(String key, Object value) {
+ if (!data.containsKey(key)) {
+ fields.add(key);
+ }
+
+ data.put(key, value);
+ return value;
+ }
+
+ /**
+ * Add new key->value to BSON object.
+ *
+ * @return added value
+ * @throws IllegalArgumentException if not valid ObjectId data passed as _id ({@link BSONObject#ID_KEY} field.
+ */
+ public Object put(String key, Object value) throws IllegalArgumentException {
if (ID_KEY.equals(key)) {
if (value instanceof ObjectId) {
// noop
}
}
- return data.put(key, value);
+ return registerField(key, value);
}
+ /**
+ * The same as {@link BSONObject#put(String, Object)} but return <code>this</code>
+ */
public BSONObject append(String key, Object value) {
this.put(key, value);
return this;
}
+ /**
+ * Adds key->value pair to BSON object from specified Map
+ */
public void putAll(Map<String, Object> values) {
for (Map.Entry<String, Object> entry : values.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
+ /**
+ * Adds key->value pair to BSON object from other BSON object
+ */
public void putAll(BSONObject object) {
- this.putAll(object.asMap());
+ for (String field : object.fields) {
+ this.put(field, object.get(field));
+ }
}
- public Map<String, Object> asMap() {
- return new HashMap<String, Object>(data);
+ /**
+ * @return fields in adding order
+ */
+ public List<String> fields() {
+ return Collections.unmodifiableList(fields);
}
- public Set<String> keySet() {
- return new HashSet<String>(data.keySet());
+ /**
+ * @return id of BSON object (if specified)
+ */
+ public ObjectId getId() {
+ return (ObjectId) get(ID_KEY);
}
+ /**
+ * @return value of specified field if exists, or <code>null</code> otherwise
+ */
public Object get(String key) {
return data.get(key);
}
- public ObjectId getId() {
- return (ObjectId) get(ID_KEY);
- }
-
+ /**
+ * @return fields count
+ */
public int size() {
return data.size();
}
+ /**
+ * Checks field contains in BSON object
+ */
public boolean containsField(String key) {
return data.containsKey(key);
}
+ /**
+ * Removes field from Object
+ */
+ public void remove(String field) {
+ if (data.containsKey(field)) {
+ fields.remove(field);
+ data.remove(field);
+ }
+ }
+
+ /**
+ * Removes all fields
+ */
+ public void clear() {
+ fields.clear();
+ data.clear();
+ }
+
@Override
public boolean equals(Object o) {
if (this != o && (null == o || !(o instanceof BSONObject))) {
import java.util.Arrays;
/**
+ * BSON Object ID
+ *
* @author Tyutyunkov Vyacheslav (tve@softmotions.com)
* @version $Id$
*/
private byte[] data;
- public ObjectId(byte[] data) {
+ /**
+ * Read ObjectId from byte array
+ *
+ * @throws IllegalStateException if not valid ObjectId data passed
+ */
+ public ObjectId(byte[] data) throws IllegalArgumentException {
if (data == null || data.length != 12) {
throw new IllegalArgumentException("unexpected ObjectId data");
}
System.arraycopy(data, 0, this.data, 0, 12);
}
+ /**
+ * Read ObjectId from string
+ *
+ * @throws IllegalStateException if not valid ObjectId data passed
+ */
public ObjectId(String value) {
if (!isValid(value)) {
throw new IllegalArgumentException("unexpected ObjectId data");
}
}
+ /**
+ * Export ObjectId to plain byte array
+ */
public byte[] toByteArray() {
byte[] result = new byte[12];
System.arraycopy(data, 0, result, 0, 12);
return builder.toString();
}
+ /**
+ * Checks string on valid ObjectId data
+ */
public static boolean isValid(String value) {
if (value == null || value.length() != 24) {
return false;
import java.util.regex.Pattern;
/**
+ * Util class for convert Java regex flags to BSON string and conversely
+ *
* @author Tyutyunkov Vyacheslav (tve@softmotions.com)
* @version $Id$
*/
this.supported = supported;
}
+ /**
+ * Convert Java regex flags to BSON string
+ */
public static String regexFlagsToString(int flags) {
StringBuilder result = new StringBuilder();
for (RegexFlag rf : regexFlags) {
return result.toString();
}
+ /**
+ * Read Java regex flags from BSON string
+ */
public static int stringToRegexFlags(String str) {
int flags = 0;
return flags;
}
- public static void registerRegexFlag(int flag, char character, boolean supported) {
+ /**
+ * Register flag conversation rules
+ */
+ protected static void registerRegexFlag(int flag, char character, boolean supported) {
RegexFlag rf = new RegexFlag(flag, character, supported);
regexFlags.add(rf);
characterToRegexFlagMap.put(rf.getCharacter(), rf);
}
+ /**
+ * @return Java flag
+ */
public int getFlag() {
return flag;
}
+ /**
+ * @return BSON character for associated Java regex flag
+ */
public char getCharacter() {
return character;
}
+ /**
+ * @return <code>true</code> if BSON supported current Java flag
+ */
public boolean isSupported() {
return supported;
}
--- /dev/null
+package org.ejdb.driver;
+
+import org.ejdb.bson.BSONObject;
+import org.ejdb.bson.types.ObjectId;
+
+import java.util.Map;
+
+/**
+ * BSON object for EJDB queries (limitation checks for {@link BSONObject#ID_KEY} field)
+ *
+ * @author Tyutyunkov Vyacheslav (tve@softmotions.com)
+ * @version $Id$
+ */
+public class BSONQueryObject extends BSONObject {
+
+ public BSONQueryObject() {
+ super();
+ }
+
+ public BSONQueryObject(String key, Object value) {
+ super(key, value);
+ }
+
+ public BSONQueryObject(Map<String, Object> data) {
+ super(data);
+ }
+
+ public BSONQueryObject(BSONObject src) {
+ super(src);
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ return registerField(key, value);
+ }
+
+ @Override
+ public BSONQueryObject append(String key, Object value) {
+ super.append(key, value);
+ return this;
+ }
+
+ /**
+ * BSON Query objects can not contains dedicated ObjectID
+ * @return
+ */
+ @Deprecated
+ @Override
+ public ObjectId getId() {
+ return null;
+ }
+}
System.loadLibrary("jejdb");
}
- private long dbPointer;
+ private transient long dbPointer;
private String path;
private Map<String, EJDBCollection> collections;
import org.ejdb.bson.types.ObjectId;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
}
/**
+ * Creates new EJDB Query for current collection.
+ */
+ public EJDBQuery createQuery(EJDBQueryBuilder query) {
+ return new EJDBQuery(this, query);
+ }
+
+ /**
* @see EJDBCollection#createQuery(org.ejdb.bson.BSONObject, org.ejdb.bson.BSONObject[], org.ejdb.bson.BSONObject)
+ * @deprecated
+ * @use EJDBCollection#createQuery(EJDBQueryBuilder)
*/
+ @Deprecated
public EJDBQuery createQuery(BSONObject query) {
- return new EJDBQuery(this, query, null, null);
+ return this.createQuery(new EJDBQueryBuilder(query, null, null));
}
/**
* @see EJDBCollection#createQuery(org.ejdb.bson.BSONObject, org.ejdb.bson.BSONObject[], org.ejdb.bson.BSONObject)
+ * @deprecated
+ * @use EJDBCollection#createQuery(EJDBQueryBuilder)
*/
+ @Deprecated
public EJDBQuery createQuery(BSONObject query, BSONObject[] qors) {
- return new EJDBQuery(this, query, qors, null);
+ return this.createQuery(new EJDBQueryBuilder(query, qors != null ? Arrays.asList(qors) : null, null));
}
/**
* @see EJDBCollection#createQuery(org.ejdb.bson.BSONObject, org.ejdb.bson.BSONObject[], org.ejdb.bson.BSONObject)
+ * @deprecated
+ * @use EJDBCollection#createQuery(EJDBQueryBuilder)
*/
+ @Deprecated
public EJDBQuery createQuery(BSONObject query, BSONObject hints) {
- return new EJDBQuery(this, query, null, hints);
+ return this.createQuery(new EJDBQueryBuilder(query, null, hints));
}
/**
* @param query Main BSON query object
* @param qors Array of additional OR query objects (joined with OR predicate).
* @param hints BSON object with query hints.
- * @return
+ * @deprecated
+ * @use EJDBCollection#createQuery(EJDBQueryBuilder)
*/
+ @Deprecated
public EJDBQuery createQuery(BSONObject query, BSONObject[] qors, BSONObject hints) {
- return new EJDBQuery(this, query, qors, hints);
+ return this.createQuery(new EJDBQueryBuilder(query, qors != null ? Arrays.asList(qors) : null, hints));
}
/**
* @author Tyutyunkov Vyacheslav (tve@softmotions.com)
* @version $Id$
*/
-public class EJDBException extends Exception {
+public class EJDBException extends RuntimeException {
private int code;
+ public EJDBException() {
+ super();
+ }
+
+ public EJDBException(String message) {
+ super(message);
+ }
+
+ public EJDBException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public EJDBException(Throwable cause) {
+ super(cause);
+ }
+
public EJDBException(int code, String message) {
super(message);
this.code = code;
import java.util.Map;
/**
+ * EJDB Query object.
+ *
* @author Tyutyunkov Vyacheslav (tve@softmotions.com)
* @version $Id$
*/
protected static final int JBQRYCOUNT = 1;
private EJDBCollection coll;
- private BSONObject query;
- private List<BSONObject> qors;
- private BSONObject hints;
+ private EJDBQueryBuilder query;
private int flags;
- EJDBQuery(EJDBCollection coll, BSONObject query, BSONObject[] qors, BSONObject hints) {
+
+ EJDBQuery(EJDBCollection coll, EJDBQueryBuilder query) {
this.coll = coll;
this.query = query;
- this.qors = new ArrayList<BSONObject>();
- if (qors != null) {
- this.qors.addAll(Arrays.asList(qors));
- }
- this.hints = hints;
- }
-
- /**
- * Return main query object
- *
- * @return
- */
- public BSONObject getQueryObject() {
- return query;
}
/**
* Execute query
*/
public EJDBResultSet find(OutputStream log) throws EJDBException, IOException {
- return this.execute(hints, 0, log).getResultSet();
+ return this.execute(query.getQueryHints(), 0, log).getResultSet();
}
/**
* Same as {@link org.ejdb.driver.EJDBQuery#find()} but retrieves only one matching JSON object.
*/
public BSONObject findOne(OutputStream log) throws EJDBException, IOException {
- Map<String, Object> hintsMap = hints != null ? hints.asMap() : new HashMap();
- hintsMap.put("$max", 1);
+ BSONQueryObject hints = new BSONQueryObject(query.getQueryHints()).append("$max", 1);
- EJDBResultSet rs = this.execute(new BSONObject(hintsMap), 0, null).getResultSet();
+ EJDBResultSet rs = this.execute(hints, 0, null).getResultSet();
BSONObject result = rs.hasNext() ? rs.next() : null;
rs.close();
* @return count of affected objects
*/
public int update(OutputStream log) throws EJDBException, IOException {
- return this.execute(hints, JBQRYCOUNT, log).getCount();
+ return this.execute(query.getQueryHints(), JBQRYCOUNT, log).getCount();
}
/**
* Convenient count(*) operation
*/
public int count(OutputStream log) throws EJDBException, IOException {
- return this.execute(hints, JBQRYCOUNT, log).getCount();
+ return this.execute(query.getQueryHints(), JBQRYCOUNT, log).getCount();
}
protected QResult execute(BSONObject hints, int flags, OutputStream log) throws EJDBException, IOException {
- BSONObject[] qors = new BSONObject[this.qors.size()];
- this.qors.toArray(qors);
-
- return this.execute(query, qors, hints, flags, log);
+ return this.execute(query.getMainQuery(), query.getOrQueries(), hints, flags, log);
}
protected native QResult execute(BSONObject query, BSONObject[] qors, BSONObject hints, int flags, OutputStream log) throws EJDBException, IOException;
--- /dev/null
+package org.ejdb.driver;
+
+import org.ejdb.bson.BSONObject;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Query/BSON builder is used to create EJDB queries.
+ * EJDBQueryBuilder can be used to construct BSON objects as well as queries.
+ *
+ * @author Tyutyunkov Vyacheslav (tve@softmotions.com)
+ * @version $Id$
+ */
+public class EJDBQueryBuilder {
+ private EJDBQueryBuilder parent;
+
+ private BSONObject query;
+ private List<BSONObject> queryOrs;
+ private BSONObject hints;
+
+ public EJDBQueryBuilder() {
+ query = new BSONQueryObject();
+ queryOrs = new ArrayList<BSONObject>();
+ hints = new BSONQueryObject();
+ }
+
+ public EJDBQueryBuilder(BSONObject query, List<BSONObject> queryOrs, BSONObject hints) {
+ this.query = query;
+ this.queryOrs = queryOrs != null ? queryOrs : new ArrayList<BSONObject>();
+ this.hints = hints != null ? hints : new BSONQueryObject();
+ }
+
+ protected EJDBQueryBuilder(EJDBQueryBuilder parent, BSONObject query) {
+ this.parent = parent;
+ this.query = query;
+ }
+
+ /**
+ * @return main BSON query object
+ */
+ public BSONObject getMainQuery() {
+ return query;
+ }
+
+ /**
+ * @return BSON objects for additional OR queries
+ */
+ public BSONObject[] getOrQueries() {
+ if (queryOrs == null) {
+ return new BSONObject[0];
+ }
+
+ int i = -1;
+ BSONObject[] ors = new BSONObject[queryOrs.size()];
+ for (BSONObject qor : queryOrs) {
+ ors[++i] = qor;
+ }
+
+ return ors;
+ }
+
+ /**
+ * @return BSON hints object
+ */
+ public BSONObject getQueryHints() {
+ return hints;
+ }
+
+ /**
+ * Adds query restrintions in main query object.
+ *
+ * @param field field path
+ * @param value field value
+ * @param replace if <code>true</code> all other restrictions will be replaces, otherwise trying to add restrictions for field
+ */
+ protected EJDBQueryBuilder addOperation(String field, Object value, boolean replace) {
+ if (!query.containsField(field) || replace || !(value instanceof BSONObject)) {
+ query.put(field, value);
+ } else {
+ Object cvalue = query.get(field);
+ if (cvalue instanceof BSONObject) {
+ ((BSONObject) cvalue).putAll((BSONObject) value);
+ } else {
+ query.put(field, value);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Checks hints section allowed.
+ *
+ * @throws EJDBException if hints section if not allowed for current EJDBQueryBuilder object
+ */
+ protected void checkHintsAvailable() throws EJDBException {
+ if (hints == null) {
+ throw new EJDBException("hints section not available for subquery such as or-query or element match query");
+ }
+ }
+
+ /**
+ * Adds pair name->value to hints BSON object.
+ *
+ * @throws EJDBException if hints section if not allowed for current EJDBQueryBuilder object
+ */
+ protected EJDBQueryBuilder addHint(String name, Object value) throws EJDBException {
+ checkHintsAvailable();
+ hints.put(name, value);
+ return this;
+ }
+
+ /**
+ * Adds field equality restriction.
+ * <p/>
+ * <code>query.field(field, value); // -> {field : value}</code>
+ */
+ public EJDBQueryBuilder field(String field, Object value) {
+ return this.field(field).eq(value);
+ }
+
+ /**
+ * Adds contraint for field
+ */
+ public Constraint field(String field) {
+ return new Constraint(field, false);
+ }
+
+ /**
+ * Element match construction.
+ * - $elemMatch The $elemMatch operator matches more than one component within an array element.
+ * - { array: { $elemMatch: { value1 : 1, value2 : { $gt: 1 } } } }
+ * <p/>
+ * Restriction: only one $elemMatch allowed in context of one array field.
+ */
+ public EJDBQueryBuilder elementMatch(String field) {
+ BSONQueryObject emqbson = new BSONQueryObject();
+ query.put(field, new BSONQueryObject("$elemMatch", emqbson));
+ return new EJDBQueryBuilder(null, emqbson);
+ }
+
+ /**
+ * Add <code>OR</code> joined query restrictions.
+ *
+ * @throws EJDBException if or section if not allowed for current EJDBQueryBuilder object (in ElementMatch-query, for example)
+ */
+ public EJDBQueryBuilder or() throws EJDBException {
+ if (parent != null) {
+ return parent.or();
+ }
+
+ if (queryOrs == null) {
+ throw new EJDBException("or section not available for subquery such as element match query");
+ }
+
+ BSONQueryObject qor = new BSONQueryObject();
+ queryOrs.add(qor);
+ return new EJDBQueryBuilder(this, qor);
+ }
+
+ /**
+ * Set specified fiels to value
+ * <p/>
+ * <code>query.set(field1, value1).set(field2, value2); // -> { ..., $set : {field1 : value1, field2 : value2}}</code>
+ */
+ public EJDBQueryBuilder set(String field, Object value) {
+ return new Constraint(field, new Constraint("$set", false)).addOperation(value);
+ }
+
+ /**
+ * Atomic upsert.
+ * If matching records are found it will be <code>$set</code> operation, otherwise new record will be inserted with field specified by value.
+ * <p/>
+ * <code>query.field(field, value).upsert(field, value); // -> {field : value, $upsert : {field : value}}</code>
+ */
+ public EJDBQueryBuilder upsert(String field, Object value) {
+ return new Constraint(field, new Constraint("$upsert", false)).addOperation(value);
+ }
+
+ /**
+ * Increment specified field. Only number types are supported.
+ * <p/>
+ * <code>query.int(field1, value1).int(field2, value2); // -> { ..., $int : {field1 : value1, field2 : value2}}</code>
+ */
+ public EJDBQueryBuilder inc(String field, Number inc) {
+ return new Constraint(field, new Constraint("$inc", false)).addOperation(inc);
+ }
+
+ /**
+ * In-place record removal operation.
+ * <p/>
+ * Example:
+ * Next update query removes all records with name eq 'andy':
+ * <code>query.field("name", "andy").dropAll()</code>
+ */
+ public EJDBQueryBuilder dropAll() {
+ return new Constraint("$dropAll").addOperation(true);
+ }
+
+ /**
+ * Atomically adds value to the array field only if value not in the array already. If containing array is missing it will be created.
+ */
+ public EJDBQueryBuilder addToSet(String field, Object value) {
+ return new Constraint(field, new Constraint("$addToSet", false)).addOperation(value);
+ }
+
+ /**
+ * Atomically performs <code>set union</code> with values in val for specified array field.
+ */
+ public EJDBQueryBuilder addToSetAll(String field, Object... values) {
+ return new Constraint(field, new Constraint("$addToSetAll", false)).addOperation((Object[]) values);
+ }
+
+ /**
+ * Atomically performs <code>set union</code> with values in val for specified array field.
+ */
+ public EJDBQueryBuilder addToSetAll(String field, Collection<Object> values) {
+ Object[] objects = new Object[values.size()];
+ values.toArray(objects);
+ return new Constraint(field, new Constraint("$addToSetAll", false)).addOperation(objects);
+ }
+
+ /**
+ * Atomically removes all occurrences of value from field, if field is an array.
+ */
+ public EJDBQueryBuilder pull(String field, Object value) {
+ return new Constraint(field, new Constraint("$pull", false)).addOperation(value);
+ }
+
+ /**
+ * Atomically performs <code>set substraction</code> of values for specified array field.
+ */
+ public EJDBQueryBuilder pullAll(String field, Object... values) {
+ return new Constraint(field, new Constraint("$pullAll", false)).addOperation((Object[]) values);
+ }
+
+ /**
+ * Atomically performs <code>set substraction</code> of values for specified array field.
+ */
+ public EJDBQueryBuilder pullAll(String field, Collection<Object> values) {
+ Object[] objects = new Object[values.size()];
+ values.toArray(objects);
+ return new Constraint(field, new Constraint("$pullAll", false)).addOperation(objects);
+ }
+
+ /**
+ * Make {@se http://github.com/Softmotions/ejdb/wiki/Collection-joins collection join} for select queries.
+ */
+ public EJDBQueryBuilder join(String fpath, String collname) {
+ return new Constraint("$join", new Constraint(fpath, new Constraint("$do", false))).addOperation(collname);
+ }
+
+ /**
+ * Sets max number of records in the result set.
+ */
+ public EJDBQueryBuilder setMaxResults(int maxResults) {
+ return addHint("$max", maxResults);
+ }
+
+ /**
+ * Sets number of skipped records in the result set.
+ */
+ public EJDBQueryBuilder setOffset(int offset) {
+ return addHint("$skip", offset);
+ }
+
+ /**
+ * Sets fields to be included or exluded in resulting objects.
+ * If field presented in <code>$orderby</code> clause it will be forced to include in resulting records.
+ */
+ public EJDBQueryBuilder setFieldIncluded(String field, boolean incldue) {
+ checkHintsAvailable();
+
+ BSONObject fields = hints.containsField("$fields") && (hints.get("$fields") instanceof BSONObject) ? (BSONObject) hints.get("$fields") : new BSONQueryObject();
+ fields.put(field, incldue ? 1 : -1);
+ hints.put("$fields", fields);
+
+ return this;
+ }
+
+ /**
+ * Sets fields to be included in resulting objects.
+ * If field presented in <code>$orderby</code> clause it will be forced to include in resulting records.
+ */
+ public EJDBQueryBuilder includeField(String field) {
+ return setFieldIncluded(field, true);
+ }
+
+ /**
+ * Sets fields to be excluded from resulting objects.
+ * If field presented in <code>$orderby</code> clause it will be forced to include in resulting records.
+ */
+ public EJDBQueryBuilder excludeField(String field) {
+ return setFieldIncluded(field, false);
+ }
+
+ /**
+ * Resturs return sorting rules control object
+ */
+ public OrderBy orderBy() {
+ checkHintsAvailable();
+
+ if (hints.containsField("$orderby") && hints.get("$orderby") != null && (hints.get("$orderby") instanceof BSONObject)) {
+ return new OrderBy((BSONObject) hints.get("$orderby"));
+ } else {
+ return new OrderBy((BSONObject) hints.put("$orderby", new BSONQueryObject()));
+ }
+ }
+
+ /**
+ * Find constraint for specified field
+ */
+ public class Constraint {
+ private String field;
+ private boolean replace;
+ private Constraint parent;
+
+ protected Constraint(String field) {
+ this(field, true);
+ }
+
+ protected Constraint(String field, boolean replace) {
+ this.field = field;
+ this.replace = replace;
+ }
+
+ protected Constraint(String field, Constraint parent) {
+ this.field = field;
+ this.parent = parent;
+ this.replace = true;
+ }
+
+ /**
+ * Add curently restrictons tree to query
+ */
+ protected EJDBQueryBuilder addOperation(Object value) {
+ if (parent != null) {
+ return parent.addOperation(new BSONQueryObject(field, value));
+ } else {
+ return EJDBQueryBuilder.this.addOperation(field, value, replace);
+ }
+ }
+
+ /**
+ * Add <code>$not</code> negatiation contraint
+ * <p/>
+ * Example:
+ * <code>query.field(field).not().eq(value); // {field : { $not : value }}</code>
+ * <code>query.field(field).not().bt(start, end); // {field : { $not : {$bt : [start, end]}}}</code>
+ */
+ public Constraint not() {
+ return new Constraint("$not", this);
+ }
+
+ /**
+ * Field equality restriction.
+ * All usage samples represent same thing: {"field" : value}
+ * <p/>
+ * Example:
+ * <code>query.field(field, value); // -> {field : value}</code>
+ * <code>query.field(field).eq(value); // -> {field : value}</code>
+ */
+ public EJDBQueryBuilder eq(Object value) {
+ return this.addOperation(value);
+ }
+
+ /**
+ * Greater than or equal value (field_value >= value)
+ */
+ public EJDBQueryBuilder gte(Number value) {
+ return new Constraint("$gte", this).addOperation(value);
+ }
+
+ /**
+ * Greater than value (field_value > value)
+ */
+ public EJDBQueryBuilder gt(Number value) {
+ return new Constraint("$gt", this).addOperation(value);
+ }
+
+ /**
+ * Lesser then or equal value (field_value <= value)
+ */
+ public EJDBQueryBuilder lte(Number value) {
+ return new Constraint("$lte", this).addOperation(value);
+ }
+
+ /**
+ * Lesser then value (field_value < value)
+ */
+ public EJDBQueryBuilder lt(Number value) {
+ return new Constraint("$lt", this).addOperation(value);
+ }
+
+ /**
+ * Between number (start <= field_value <= end)
+ */
+ public EJDBQueryBuilder bt(Number start, Number end) {
+ return new Constraint("$bt", this).addOperation(new Number[]{start, end});
+ }
+
+ /**
+ * Field value matched <b>any</b> value of specified in values.
+ */
+ public EJDBQueryBuilder in(Object... values) {
+ return new Constraint("$in", this).addOperation((Object[]) values);
+ }
+
+ /**
+ * Field value matched <b>any</b> value of specified in values.
+ */
+ public EJDBQueryBuilder in(Collection<Object> values) {
+ Object[] objects = new Object[values.size()];
+ values.toArray(objects);
+ return new Constraint("$in", this).addOperation(objects);
+ }
+
+ /**
+ * Negation of {@link Constraint#in(Object...)}
+ */
+ public EJDBQueryBuilder notIn(Object... values) {
+ return new Constraint("$nin", this).addOperation((Object[]) values);
+ }
+
+ /**
+ * Negation of {@link Constraint#in(java.util.Collection)}
+ */
+ public EJDBQueryBuilder notIn(Collection<Object> values) {
+ Object[] objects = new Object[values.size()];
+ values.toArray(objects);
+ return new Constraint("$nin", this).addOperation(objects);
+ }
+
+ /**
+ * Strins starts with prefix
+ */
+ public EJDBQueryBuilder begin(String value) {
+ return new Constraint("$begin", this).addOperation(value);
+ }
+
+ /**
+ * String tokens (or string array vals) matches <b>all</b> tokens in specified array.
+ */
+ public EJDBQueryBuilder strAnd(String... values) {
+ return new Constraint("$strand", this).addOperation((String[]) values);
+ }
+
+ /**
+ * String tokens (or string array vals) matches <b>all</b> tokens in specified collection.
+ */
+ public EJDBQueryBuilder strAnd(Collection<String> values) {
+ String[] strs = new String[values.size()];
+ values.toArray(strs);
+ return new Constraint("$strand", this).addOperation(strs);
+ }
+
+ /**
+ * String tokens (or string array vals) matches <b>any</b> tokens in specified array.
+ */
+ public EJDBQueryBuilder strOr(String... values) {
+ return new Constraint("$stror", this).addOperation((String[]) values);
+ }
+
+ /**
+ * String tokens (or string array vals) matches <b>any</b> tokens in specified collection.
+ */
+ public EJDBQueryBuilder strOr(Collection<String> values) {
+ String[] strs = new String[values.size()];
+ values.toArray(strs);
+ return new Constraint("$stror", this).addOperation(strs);
+ }
+
+ /**
+ * Field existence matching {@link Constraint#exists(boolean = true)}
+ */
+ public EJDBQueryBuilder exists() {
+ return this.exists(true);
+ }
+
+ /**
+ * Field existence matching
+ */
+ public EJDBQueryBuilder exists(boolean exists) {
+ return new Constraint("$exists", this).addOperation(exists);
+ }
+
+ /**
+ * Case insensitive string matching
+ * <p/>
+ * Example:
+ * <code>query.field(field).icase().eq(value); // -> {field : {$icase : value}}</code>
+ * <code>query.field(field).icase().in(value1, value2); // -> {field : {$icase : {$in : [value1, value2]}}}</code>
+ */
+ public Constraint icase() {
+ return new Constraint("$icase", this);
+ }
+ }
+
+ /**
+ * Sorting rules for query results
+ */
+ public class OrderBy {
+ private BSONObject orderBy;
+
+ protected OrderBy(BSONObject orderBy) {
+ this.orderBy = orderBy;
+ }
+
+ /**
+ * Add ascending sorting order for field
+ *
+ * @param field BSON field path
+ */
+ public OrderBy asc(String field) {
+ return add(field, true);
+ }
+
+ /**
+ * Add descinding sorting order for field
+ *
+ * @param field BSON field path
+ */
+ public OrderBy desc(String field) {
+ return add(field, false);
+ }
+
+ /**
+ * Add sorting order for field
+ *
+ * @param field BSON field path
+ * @param asc if <code>true</code> ascendong sorting order, otherwise - descinding
+ */
+ public OrderBy add(String field, boolean asc) {
+ orderBy.put(field, asc ? 1 : -1);
+ return this;
+ }
+
+ /**
+ * Clear all current sorting rules
+ */
+ public OrderBy clear() {
+ orderBy.clear();
+ return this;
+ }
+ }
+
+}
* @version $Id$
*/
public class EJDBResultSet implements Iterable<BSONObject>, Iterator<BSONObject> {
- private long rsPointer;
+ private transient long rsPointer;
private int position;
return position < this.length();
}
- public BSONObject next() {
+ public BSONObject next() throws EJDBException {
if (!hasNext()) {
throw new NoSuchElementException();
}
- try {
- return get(position++);
- } catch (EJDBException e) {
- // TODO: ?
- throw new RuntimeException(e);
- }
+ return get(position++);
}
public void remove() {
import org.ejdb.driver.EJDB;
import org.ejdb.driver.EJDBCollection;
import org.ejdb.driver.EJDBQuery;
+import org.ejdb.driver.EJDBQueryBuilder;
import org.ejdb.driver.EJDBResultSet;
import java.io.ByteArrayOutputStream;
BSONObject lobj = coll.load(oid);
assertNotNull(lobj);
assertEquals(obj, lobj);
-// assertEquals(lobj.get("_id"), oid);
-// assertEquals(obj.get("test"), lobj.get("test"));
-// assertEquals(obj.get("test2"), lobj.get("test2"));
- EJDBQuery query = coll.createQuery(new BSONObject());
+ EJDBQueryBuilder qb;
+
+ qb = new EJDBQueryBuilder();
+ EJDBQuery query = coll.createQuery(qb);
EJDBResultSet rs = query.find();
assertEquals(rs.length(), 1);
for (BSONObject r : rs) {
assertEquals(lobj, query.findOne());
- EJDBQuery query2 = db.getCollection("test2").createQuery(new BSONObject());
+ qb = new EJDBQueryBuilder();
+ EJDBQuery query2 = db.getCollection("test2").createQuery(qb);
assertNull(query2.findOne());
assertEquals(query.count(), 1);
assertEquals(query2.count(), 0);
+ qb = new EJDBQueryBuilder();
+ qb.field("test", "test")
+ .excludeField("test2");
+
EJDBQuery query3 = coll.createQuery(new BSONObject("test", "test"), new BSONObject("$fields", new BSONObject("test2", 0)));
assertEquals(query3.count(), 1);
assertEquals(ss.get(0), obj12.getId());
assertEquals(obj1, obj12);
- EJDBQuery query = parrots.createQuery(new BSONObject());
+ EJDBQueryBuilder qb;
+
+ qb = new EJDBQueryBuilder();
+ EJDBQuery query = parrots.createQuery(qb);
EJDBResultSet rs = query.find();
- assertEquals(rs.length(), 2);
+ assertEquals(2, rs.length());
rs.close();
- query = parrots.createQuery(new BSONObject("name", Pattern.compile("(grenny|bounty)", Pattern.CASE_INSENSITIVE)),
- new BSONObject("$orderby", new BSONObject("name", 1)));
+ qb = new EJDBQueryBuilder();
+ qb.field("name", Pattern.compile("(grenny|bounty)", Pattern.CASE_INSENSITIVE))
+ .orderBy().asc("name");
+
+ query = parrots.createQuery(qb);
rs = query.find();
assertEquals(rs.length(), 2);
BSONObject robj1 = rs.next();
- assertEquals(robj1.get("name"), "Bounty");
- assertEquals(robj1.get("age"), 15);
+ assertEquals("Bounty", robj1.get("name"));
+ assertEquals(15, robj1.get("age"));
rs.close();
- query = parrots.createQuery(new BSONObject(),
- new BSONObject[]{new BSONObject("name", "Grenny"), new BSONObject("name", "Bounty")},
- new BSONObject("$orderby", new BSONObject("name", 1)));
+ qb = new EJDBQueryBuilder();
+ qb
+ .or().field("name", "Grenny")
+ .or().field("name", "Bounty");
+ qb.orderBy().asc("name");
+ query = parrots.createQuery(qb);
rs = query.find();
- assertEquals(rs.length(), 2);
+ assertEquals(2, rs.length());
rs.close();
- query = parrots.createQuery(new BSONObject(),
- new BSONObject[]{new BSONObject("name", "Grenny")},
- new BSONObject("$orderby", new BSONObject("name", 1)));
-
- assertEquals(query.count(), 1);
+ qb = new EJDBQueryBuilder();
+ qb
+ .or().field("name", "Grenny");
+ qb.orderBy().asc("name");
+ query = parrots.createQuery(qb);
+ assertEquals(1, query.count());
}
public void testIndexes() throws Exception {
ByteArrayOutputStream log;
- EJDBQuery query = birds.createQuery(new BSONObject("name", "Molly"));
+ EJDBQuery query = birds.createQuery(new EJDBQueryBuilder().field("name", "Molly"));
query.find(log = new ByteArrayOutputStream());
assertTrue(log.toString().contains("RUN FULLSCAN"));