1
0
Fork 0

added documentation and test cases for db.collection.removeByExample

This commit is contained in:
Jan Steemann 2013-01-22 19:20:11 +01:00
parent 4e5a9e0f4e
commit 7c5203cfea
13 changed files with 406 additions and 34 deletions

View File

@ -1,6 +1,8 @@
v1.2.alpha (XXXX-XX-XX) v1.2.alpha (XXXX-XX-XX)
----------------------- -----------------------
* added documentation and tests for db.collection.removeByExample
* added --progress option for arangoimp. This will show the percentage of the input * added --progress option for arangoimp. This will show the percentage of the input
file that has been processed by arangoimp while the import is still running. It can file that has been processed by arangoimp while the import is still running. It can
be used as a rough indicator of progress for the entire import. be used as a rough indicator of progress for the entire import.

View File

@ -0,0 +1,11 @@
> curl --data @- -X PUT --dump - http://localhost:8529/_api/simple/remove-by-example
{ "collection" : "test", "example" : { "age" : 37, "likes" : "tennis" } }
HTTP/1.1 200 Ok
content-type: application/json
{
"code": 200,
"deleted": 4,
"error": false
}

View File

@ -394,6 +394,146 @@ describe ArangoDB do
end end
end end
################################################################################
## remove-by-example query
################################################################################
context "remove-by-example query:" do
before do
@cn = "UnitTestsCollectionByExample"
ArangoDB.drop_collection(@cn)
@cid = ArangoDB.create_collection(@cn, false)
(0...20).each{|i|
ArangoDB.post("/_api/document?collection=#{@cn}", :body => "{ \"value\" : #{i} }")
}
end
after do
ArangoDB.drop_collection(@cn)
end
it "removes the examples" do
cmd = api + "/remove-by-example"
body = "{ \"collection\" : \"#{@cn}\", \"example\" : { \"value\" : 1 } }"
doc = ArangoDB.log_put("#{prefix}-remove-by-example", cmd, :body => body)
# remove first
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['deleted'].should eq(1)
# remove again
doc = ArangoDB.log_put("#{prefix}-remove-by-example", cmd, :body => body)
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['deleted'].should eq(0)
# remove other doc
body = "{ \"collection\" : \"#{@cn}\", \"example\" : { \"value\" : 2 } }"
doc = ArangoDB.log_put("#{prefix}-remove-by-example", cmd, :body => body)
# remove first
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['deleted'].should eq(1)
# remove again
doc = ArangoDB.log_put("#{prefix}-remove-by-example", cmd, :body => body)
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['deleted'].should eq(0)
# remove others
(3...8).each{|i|
body = "{ \"collection\" : \"#{@cn}\", \"example\" : { \"value\" : #{i} } }"
doc = ArangoDB.log_put("#{prefix}-remove-by-example", cmd, :body => body)
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['deleted'].should eq(1)
doc = ArangoDB.log_put("#{prefix}-remove-by-example", cmd, :body => body)
doc.parsed_response['deleted'].should eq(0)
}
# remove non-existing values
[ 21, 22, 100, 101, 99, "\"meow\"", "\"\"", "\"null\"" ].each{|value|
body = "{ \"collection\" : \"#{@cn}\", \"example\" : { \"value\" : " + value.to_s + " } }"
doc = ArangoDB.log_put("#{prefix}-remove-by-example", cmd, :body => body)
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['deleted'].should eq(0)
}
# remove non-existing attributes
[ "value2", "value3", "fox", "meow" ].each{|value|
body = "{ \"collection\" : \"#{@cn}\", \"example\" : { \"" + value + "\" : 1 } }"
doc = ArangoDB.log_put("#{prefix}-remove-by-example", cmd, :body => body)
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['deleted'].should eq(0)
}
# insert 10 identical documents
(0...10).each{|i|
ArangoDB.post("/_api/document?collection=#{@cn}", :body => "{ \"value99\" : 7, \"value98\" : 1 }")
}
# miss them
body = "{ \"collection\" : \"#{@cn}\", \"example\" : { \"value99\" : 7, \"value98\" : 2} }"
doc = ArangoDB.log_put("#{prefix}-remove-by-example", cmd, :body => body)
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['deleted'].should eq(0)
# miss them again
body = "{ \"collection\" : \"#{@cn}\", \"example\" : { \"value99\" : 70, \"value98\" : 1} }"
doc = ArangoDB.log_put("#{prefix}-remove-by-example", cmd, :body => body)
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['deleted'].should eq(0)
# now remove them
body = "{ \"collection\" : \"#{@cn}\", \"example\" : { \"value99\" : 7, \"value98\" : 1} }"
doc = ArangoDB.log_put("#{prefix}-remove-by-example", cmd, :body => body)
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['deleted'].should eq(10)
# remove again
doc = ArangoDB.log_put("#{prefix}-remove-by-example", cmd, :body => body)
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['deleted'].should eq(0)
end
end
################################################################################ ################################################################################
## range query ## range query
################################################################################ ################################################################################

View File

@ -52,6 +52,7 @@
/// <li>@ref HttpSimpleNear "POST /_api/simple/near"</li> /// <li>@ref HttpSimpleNear "POST /_api/simple/near"</li>
/// <li>@ref HttpSimpleWithin "POST /_api/simple/within"</li> /// <li>@ref HttpSimpleWithin "POST /_api/simple/within"</li>
/// <li>@ref HttpSimpleFulltext "POST /_api/simple/fulltext"</li> /// <li>@ref HttpSimpleFulltext "POST /_api/simple/fulltext"</li>
/// <li>@ref HttpSimpleRemoveByExample "PUT /_api/simple/remove-by-example"</li>
/// </ul> /// </ul>
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -111,6 +112,9 @@
/// ///
/// @anchor HttpSimpleFulltext /// @anchor HttpSimpleFulltext
/// @copydetails JSA_PUT_api_simple_fulltext /// @copydetails JSA_PUT_api_simple_fulltext
///
/// @anchor HttpSimpleRemoveByExample
/// @copydetails JSA_PUT_api_simple_remove_by_example
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Local Variables: // Local Variables:

View File

@ -75,6 +75,11 @@
/// <li>@ref SimpleQueryCount "query.count"</li> /// <li>@ref SimpleQueryCount "query.count"</li>
/// </ul> /// </ul>
/// </li> /// </li>
/// <li>@ref SimpleQueriesModify
/// <ul>
/// <li>@ref SimpleQueryRemoveByExample "collection.removeByExample"</li>
/// </ul>
/// </li>
/// </ul> /// </ul>
/// </li> /// </li>
/// </ul> /// </ul>
@ -299,6 +304,22 @@
/// @anchor SimpleQueryCount /// @anchor SimpleQueryCount
/// @copydetails JSF_SimpleQuery_prototype_count /// @copydetails JSF_SimpleQuery_prototype_count
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
///
/// @CLEARPAGE
/// @section SimpleQueriesModify Modification Queries
/////////////////////////////////////////////////////
///
/// ArangoDB also allows removing documents based on an example document.
/// Every document in the collection will be compared against the specified
/// example document and be deleted if all attributes match.
///
/// This method should be used with caution as it intended to remove lots of
/// documents from a collection.
///
/// @CLEARPAGE
/// @anchor SimpleQueryRemoveByExample
/// @copydetails JSF_ArangoCollection_prototype_removeByExample
////////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE // --SECTION-- END-OF-FILE

View File

@ -569,9 +569,6 @@ ArangoCollection.prototype.fulltext = function (attribute, query, iid) {
/// @endcode /// @endcode
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// TODO this is not optiomal for the client, there should a HTTP call handling
// everything on the server
ArangoCollection.prototype.iterate = function (iterator, options) { ArangoCollection.prototype.iterate = function (iterator, options) {
var probability = 1.0; var probability = 1.0;
var limit = null; var limit = null;
@ -579,6 +576,9 @@ ArangoCollection.prototype.iterate = function (iterator, options) {
var cursor; var cursor;
var pos; var pos;
// TODO: this is not optimal for the client, there should be an HTTP call handling
// everything on the server
if (options !== undefined) { if (options !== undefined) {
if (options.hasOwnProperty("probability")) { if (options.hasOwnProperty("probability")) {
probability = options.probability; probability = options.probability;
@ -658,7 +658,7 @@ ArangoCollection.prototype.iterate = function (iterator, options) {
/// ///
/// @FUN{@FA{collection}.removeByExample(@FA{example})} /// @FUN{@FA{collection}.removeByExample(@FA{example})}
/// ///
/// Removes all document matching an example. /// Removes all documents matching an example.
/// ///
/// @FUN{@FA{collection}.removeByExample(@FA{document}, @FA{waitForSync})} /// @FUN{@FA{collection}.removeByExample(@FA{document}, @FA{waitForSync})}
/// ///
@ -681,18 +681,7 @@ ArangoCollection.prototype.iterate = function (iterator, options) {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
ArangoCollection.prototype.removeByExample = function (example, waitForSync) { ArangoCollection.prototype.removeByExample = function (example, waitForSync) {
var documents; throw "cannot call abstract removeByExample function";
// TODO this is not optiomal for the client, there should be an HTTP call handling
// everything on the server
documents = this.byExample(example);
while (documents.hasNext()) {
var document = documents.next();
this.remove(document, true, waitForSync);
}
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@ -1090,6 +1090,26 @@ ArangoCollection.prototype.outEdges = function (vertex) {
return this._edgesQuery(vertex, "out"); return this._edgesQuery(vertex, "out");
}; };
////////////////////////////////////////////////////////////////////////////////
/// @brief removes documents matching an example
////////////////////////////////////////////////////////////////////////////////
ArangoCollection.prototype.removeByExample = function (example, waitForSync) {
var data = {
collection: this._name,
example: example,
waitForSync: waitForSync
};
var requestResult = this._database._connection.PUT(
"/_api/simple/remove-by-example",
JSON.stringify(data));
arangosh.checkRequestResult(requestResult);
return requestResult.deleted;
};
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @} /// @}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@ -809,6 +809,73 @@ actions.defineHttp({
} }
}); });
////////////////////////////////////////////////////////////////////////////////
/// @fn JSA_PUT_api_simple_remove_by_example
/// @brief removes all documents of a collection that match an example
///
/// @RESTHEADER{PUT /_api/simple/remove-by-example,removes documents by example}
///
/// @REST{PUT /_api/simple/remove-by-example}
///
/// This will find all documents in the collection that match the specified
/// example object.
///
/// The call expects a JSON hash array as body with the following attributes:
///
/// - @LIT{collection}: The name of the collection to remove from.
///
/// - @LIT{example}: An example object that all collection objects are compared
/// against.
///
/// - @LIT{waitForSync}: if set to true, then all removal operations will
/// instantly be synchronised to disk. If this is not specified, then the
/// collection's default sync behavior will be applied.
///
/// Returns the number of documents that were deleted
///
/// @EXAMPLES
///
/// @verbinclude api-simple-remove-by-example
////////////////////////////////////////////////////////////////////////////////
actions.defineHttp({
url : API + "remove-by-example",
context : "api",
callback : function (req, res) {
try {
var body = actions.getJsonBody(req, res);
if (body === undefined) {
return;
}
if (req.requestType != actions.PUT) {
actions.resultUnsupported(req, res);
}
else {
var example = body.example;
var name = body.collection;
var collection = db._collection(name);
if (collection === null) {
actions.collectionNotFound(req, res, name);
}
else if (typeof example !== "object") {
actions.badParameter(req, res, "example");
}
else {
var result = collection.removeByExample(example, body.waitForSync);
actions.resultOk(req, res, actions.HTTP_OK, { deleted: result });
}
}
}
catch (err) {
actions.resultException(req, res, err);
}
}
});
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @} /// @}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@ -1089,6 +1089,26 @@ ArangoCollection.prototype.outEdges = function (vertex) {
return this._edgesQuery(vertex, "out"); return this._edgesQuery(vertex, "out");
}; };
////////////////////////////////////////////////////////////////////////////////
/// @brief removes documents matching an example
////////////////////////////////////////////////////////////////////////////////
ArangoCollection.prototype.removeByExample = function (example, waitForSync) {
var data = {
collection: this._name,
example: example,
waitForSync: waitForSync
};
var requestResult = this._database._connection.PUT(
"/_api/simple/remove-by-example",
JSON.stringify(data));
arangosh.checkRequestResult(requestResult);
return requestResult.deleted;
};
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @} /// @}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@ -568,9 +568,6 @@ ArangoCollection.prototype.fulltext = function (attribute, query, iid) {
/// @endcode /// @endcode
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// TODO this is not optiomal for the client, there should a HTTP call handling
// everything on the server
ArangoCollection.prototype.iterate = function (iterator, options) { ArangoCollection.prototype.iterate = function (iterator, options) {
var probability = 1.0; var probability = 1.0;
var limit = null; var limit = null;
@ -578,6 +575,9 @@ ArangoCollection.prototype.iterate = function (iterator, options) {
var cursor; var cursor;
var pos; var pos;
// TODO: this is not optimal for the client, there should be an HTTP call handling
// everything on the server
if (options !== undefined) { if (options !== undefined) {
if (options.hasOwnProperty("probability")) { if (options.hasOwnProperty("probability")) {
probability = options.probability; probability = options.probability;
@ -657,7 +657,7 @@ ArangoCollection.prototype.iterate = function (iterator, options) {
/// ///
/// @FUN{@FA{collection}.removeByExample(@FA{example})} /// @FUN{@FA{collection}.removeByExample(@FA{example})}
/// ///
/// Removes all document matching an example. /// Removes all documents matching an example.
/// ///
/// @FUN{@FA{collection}.removeByExample(@FA{document}, @FA{waitForSync})} /// @FUN{@FA{collection}.removeByExample(@FA{document}, @FA{waitForSync})}
/// ///
@ -680,18 +680,7 @@ ArangoCollection.prototype.iterate = function (iterator, options) {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
ArangoCollection.prototype.removeByExample = function (example, waitForSync) { ArangoCollection.prototype.removeByExample = function (example, waitForSync) {
var documents; throw "cannot call abstract removeByExample function";
// TODO this is not optiomal for the client, there should be an HTTP call handling
// everything on the server
documents = this.byExample(example);
while (documents.hasNext()) {
var document = documents.next();
this.remove(document, true, waitForSync);
}
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@ -381,6 +381,93 @@ function SimpleQueryByExampleSuite () {
s = collection.firstExample("i", 2, "a.k", 27); s = collection.firstExample("i", 2, "a.k", 27);
assertEqual(null, s); assertEqual(null, s);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test: removeByExample
////////////////////////////////////////////////////////////////////////////////
testRemoveByExample : function () {
var deleted;
for (var i = 0; i < 50; ++i) {
collection.save({ value : i });
}
deleted = collection.removeByExample({ value : 2 });
assertEqual(1, deleted);
deleted = collection.removeByExample({ value : 2 });
assertEqual(0, deleted);
deleted = collection.removeByExample({ value : 20 });
assertEqual(1, deleted);
deleted = collection.removeByExample({ value : 20 });
assertEqual(0, deleted);
deleted = collection.removeByExample({ value : 1 });
assertEqual(1, deleted);
// not existing documents
deleted = collection.removeByExample({ value : 50 });
assertEqual(0, deleted);
deleted = collection.removeByExample({ value : null });
assertEqual(0, deleted);
deleted = collection.removeByExample({ value : "5" });
assertEqual(0, deleted);
deleted = collection.removeByExample({ peter : "meow" });
assertEqual(0, deleted);
collection.truncate();
deleted = collection.removeByExample({ value : 4 });
assertEqual(0, deleted);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test: removeByExample
////////////////////////////////////////////////////////////////////////////////
testRemoveByExampleMult : function () {
var deleted;
for (var i = 0; i < 5; ++i) {
for (var j = 0; j < 5; ++j) {
collection.save({ value1 : i, value2: j });
}
}
deleted = collection.removeByExample({ value2 : 2 });
assertEqual(5, deleted);
deleted = collection.removeByExample({ value2 : 2 });
assertEqual(0, deleted);
deleted = collection.removeByExample({ value1 : 4 });
assertEqual(4, deleted);
deleted = collection.removeByExample({ value1 : 4 });
assertEqual(0, deleted);
// not existing documents
deleted = collection.removeByExample({ value1 : 99 });
assertEqual(0, deleted);
deleted = collection.removeByExample({ value1 : "0" });
assertEqual(0, deleted);
deleted = collection.removeByExample({ meow : 1 });
assertEqual(0, deleted);
deleted = collection.removeByExample({ meow : "peter" });
assertEqual(0, deleted);
collection.truncate();
deleted = collection.removeByExample({ value1: 3 });
assertEqual(0, deleted);
} }
}; };
} }
@ -516,7 +603,8 @@ function SimpleQueryAnySuite () {
collectionEmpty = db._create(name, { waitForSync : false }); collectionEmpty = db._create(name, { waitForSync : false });
name = cn + "One"; name = cn + "One";
collectionOne = db._create(cn, { waitForSync : false }); db._drop(name);
collectionOne = db._create(name, { waitForSync : false });
collectionOne.save({ age : 1 }); collectionOne.save({ age : 1 });
}, },

View File

@ -238,6 +238,27 @@ ArangoCollection.prototype.firstExample = function (example) {
return null; return null;
}; };
////////////////////////////////////////////////////////////////////////////////
/// @brief removes documents matching an example
////////////////////////////////////////////////////////////////////////////////
ArangoCollection.prototype.removeByExample = function (example, waitForSync) {
var deleted = 0;
var documents;
documents = this.byExample(example);
while (documents.hasNext()) {
var document = documents.next();
if (this.remove(document, true, waitForSync)) {
deleted++;
}
}
return deleted;
};
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @} /// @}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@ -141,7 +141,7 @@ function byExample (collection, example, skip, limit) {
console.debug("found unique constraint %s", idx.id); console.debug("found unique constraint %s", idx.id);
} }
} }
else { else if (idx !== null) {
console.debug("found hash index %s", idx.id); console.debug("found hash index %s", idx.id);
} }