1
0
Fork 0

Merge branch 'devel' of github.com:triAGENS/ArangoDB into devel

This commit is contained in:
Frank Celler 2013-01-22 19:39:48 +01:00
commit 3d3132dd87
16 changed files with 415 additions and 36 deletions

View File

@ -1,6 +1,8 @@
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
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.

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
################################################################################
## 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
################################################################################

View File

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

View File

@ -57,7 +57,7 @@
/// required, provided that the values conform to the following restrictions:
/// * the key must be at most 254 bytes long
/// * it must consist of the letters a-z (lower or upper case), the digits 0-9,
/// the underscore (_) or dash (-) characters only
/// the underscore (_), dash (-), or colon (:) characters only
/// * any other characters, especially multi-byte sequences, whitespace or
/// punctuation characters cannot be used inside key values
/// * the key must be unique within the collection it is used

View File

@ -75,6 +75,11 @@
/// <li>@ref SimpleQueryCount "query.count"</li>
/// </ul>
/// </li>
/// <li>@ref SimpleQueriesModify
/// <ul>
/// <li>@ref SimpleQueryRemoveByExample "collection.removeByExample"</li>
/// </ul>
/// </li>
/// </ul>
/// </li>
/// </ul>
@ -299,6 +304,22 @@
/// @anchor SimpleQueryCount
/// @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

View File

@ -56,6 +56,12 @@ using namespace std;
namespace triagens {
namespace v8client {
////////////////////////////////////////////////////////////////////////////////
/// initialise step value for progress reports
////////////////////////////////////////////////////////////////////////////////
const double ImportHelper::ProgressStep = 2.0;
////////////////////////////////////////////////////////////////////////////////
/// constructor and destructor
@ -545,3 +551,4 @@ namespace triagens {
}
}

View File

@ -240,7 +240,7 @@ namespace triagens {
bool _hasError;
string _errorMessage;
static const double ProgressStep = 2.0;
static const double ProgressStep;
};
}
}

View File

@ -569,9 +569,6 @@ ArangoCollection.prototype.fulltext = function (attribute, query, iid) {
/// @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) {
var probability = 1.0;
var limit = null;
@ -579,6 +576,9 @@ ArangoCollection.prototype.iterate = function (iterator, options) {
var cursor;
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.hasOwnProperty("probability")) {
probability = options.probability;
@ -658,7 +658,7 @@ ArangoCollection.prototype.iterate = function (iterator, options) {
///
/// @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})}
///
@ -681,18 +681,7 @@ ArangoCollection.prototype.iterate = function (iterator, options) {
////////////////////////////////////////////////////////////////////////////////
ArangoCollection.prototype.removeByExample = function (example, waitForSync) {
var documents;
// 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);
}
throw "cannot call abstract removeByExample function";
};
////////////////////////////////////////////////////////////////////////////////

View File

@ -1090,6 +1090,26 @@ ArangoCollection.prototype.outEdges = function (vertex) {
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");
};
////////////////////////////////////////////////////////////////////////////////
/// @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
////////////////////////////////////////////////////////////////////////////////
// TODO this is not optiomal for the client, there should a HTTP call handling
// everything on the server
ArangoCollection.prototype.iterate = function (iterator, options) {
var probability = 1.0;
var limit = null;
@ -578,6 +575,9 @@ ArangoCollection.prototype.iterate = function (iterator, options) {
var cursor;
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.hasOwnProperty("probability")) {
probability = options.probability;
@ -657,7 +657,7 @@ ArangoCollection.prototype.iterate = function (iterator, options) {
///
/// @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})}
///
@ -680,18 +680,7 @@ ArangoCollection.prototype.iterate = function (iterator, options) {
////////////////////////////////////////////////////////////////////////////////
ArangoCollection.prototype.removeByExample = function (example, waitForSync) {
var documents;
// 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);
}
throw "cannot call abstract removeByExample function";
};
////////////////////////////////////////////////////////////////////////////////

View File

@ -381,6 +381,93 @@ function SimpleQueryByExampleSuite () {
s = collection.firstExample("i", 2, "a.k", 27);
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 });
name = cn + "One";
collectionOne = db._create(cn, { waitForSync : false });
db._drop(name);
collectionOne = db._create(name, { waitForSync : false });
collectionOne.save({ age : 1 });
},

View File

@ -238,6 +238,27 @@ ArangoCollection.prototype.firstExample = function (example) {
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);
}
}
else {
else if (idx !== null) {
console.debug("found hash index %s", idx.id);
}