1
0
Fork 0

added collection.checksum()

This commit is contained in:
Jan Steemann 2013-07-10 11:10:00 +02:00
parent 25a8ab6c59
commit 3cc4405c18
12 changed files with 558 additions and 52 deletions

View File

@ -1,6 +1,13 @@
v1.4
----
* added collection.checksum(<withData>) method to calculate CRC checksums for
collections
This can be used to
- check if data in a collection has changed
- compare the contents of two collections on different ArangoDB instances
* issue #565: add description line to aal.listAvailable()
* fixed several out-of-memory situations when double freeing or invalid memory

View File

@ -380,9 +380,9 @@ describe ArangoDB do
r2 = doc.parsed_response['revision']
r2.should_not eq("");
r2.should_not eq(r1);
# truncate
doc = ArangoDB.log_put("#{prefix}-get-collection-revision", "/_api/collection/#{@cn}/truncate", :body => "")
# create another document
doc = ArangoDB.log_post("#{prefix}-get-collection-revision", "/_api/document/?collection=" + @cn, :body => body)
# fetch revision again
doc = ArangoDB.log_get("#{prefix}-get-collection-revision", cmd)
@ -397,6 +397,124 @@ describe ArangoDB do
r3.should_not eq("");
r3.should_not eq(r1);
r3.should_not eq(r2);
# truncate
doc = ArangoDB.log_put("#{prefix}-get-collection-revision", "/_api/collection/#{@cn}/truncate", :body => "")
# fetch revision again
doc = ArangoDB.log_get("#{prefix}-get-collection-revision", cmd)
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['revision'].should be_kind_of(String)
r4 = doc.parsed_response['revision']
r4.should_not eq("");
r4.should_not eq(r1);
r4.should_not eq(r2);
r4.should_not eq(r3);
end
# checksum
it "calculating the checksum for a collection" do
cmd = api + "/" + @cn + "/checksum"
doc = ArangoDB.log_get("#{prefix}-get-collection-checksum", cmd)
# empty collection
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['id'].should eq(@cid)
doc.parsed_response['name'].should eq(@cn)
doc.parsed_response['status'].should eq(3)
r1 = doc.parsed_response['revision']
r1.should be_kind_of(String)
r1.should_not eq("");
c1 = doc.parsed_response['checksum']
c1.should be_kind_of(Integer)
c1.should eq(0);
# create a new document
body = "{ \"test\" : 1 }"
doc = ArangoDB.log_post("#{prefix}-get-collection-checksum", "/_api/document/?collection=" + @cn, :body => body)
# fetch checksum again
doc = ArangoDB.log_get("#{prefix}-get-collection-checksum", cmd)
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)
r2 = doc.parsed_response['revision']
r2.should_not eq("");
r2.should_not eq(r1);
c2 = doc.parsed_response['checksum']
c2.should be_kind_of(Integer)
c2.should_not eq(0);
c2.should_not eq(c1);
# create another document
doc = ArangoDB.log_post("#{prefix}-get-collection-checksum", "/_api/document/?collection=" + @cn, :body => body)
# fetch checksum again
doc = ArangoDB.log_get("#{prefix}-get-collection-checksum", cmd)
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)
r3 = doc.parsed_response['revision']
r3.should_not eq("");
r3.should_not eq(r1);
r3.should_not eq(r2);
c3 = doc.parsed_response['checksum']
c3.should be_kind_of(Integer)
c3.should_not eq(0);
c3.should_not eq(c1);
c3.should_not eq(c2);
# fetch checksum <withData>
doc = ArangoDB.log_get("#{prefix}-get-collection-checksum", cmd + "?withData=true")
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)
r4 = doc.parsed_response['revision']
r4.should eq(r3);
c4 = doc.parsed_response['checksum']
c4.should be_kind_of(Integer)
c4.should_not eq(0);
c4.should_not eq(c1);
c4.should_not eq(c2);
c4.should_not eq(c3);
# truncate
doc = ArangoDB.log_put("#{prefix}-get-collection-checksum", "/_api/collection/#{@cn}/truncate", :body => "")
# fetch checksum again
doc = ArangoDB.log_get("#{prefix}-get-collection-checksum", cmd)
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)
r5 = doc.parsed_response['revision']
r5.should_not eq("");
r5.should_not eq(r1);
r5.should_not eq(r2);
r5.should_not eq(r3);
r5.should_not eq(r4);
c5 = doc.parsed_response['checksum']
c5.should be_kind_of(Integer)
c5.should eq(0);
end
end

View File

@ -73,9 +73,9 @@ namespace triagens {
/// @brief create the barrier
////////////////////////////////////////////////////////////////////////////////
Barrier (TRI_primary_collection_t* const doc) :
Barrier (TRI_primary_collection_t* const primary) :
_barrier(0) {
_barrier = TRI_CreateBarrierElement(&doc->_barrierList);
_barrier = TRI_CreateBarrierElement(&primary->_barrierList);
}
////////////////////////////////////////////////////////////////////////////////

View File

@ -29,6 +29,7 @@
#include "BasicsC/logging.h"
#include "BasicsC/random.h"
#include "BasicsC/string-buffer.h"
#include "GeoIndex/geo-index.h"
#include "HashIndex/hash-index.h"
#include "FulltextIndex/fulltext-index.h"
@ -36,6 +37,7 @@
#include "FulltextIndex/fulltext-query.h"
#include "SkipLists/skiplistIndex.h"
#include "Utilities/ResourceHolder.h"
#include "Utils/Barrier.h"
#include "Utils/CollectionNameResolver.h"
#include "Utils/EmbeddableTransaction.h"
#include "Utils/SingleCollectionReadOnlyTransaction.h"
@ -1019,7 +1021,7 @@ static v8::Handle<v8::Value> ExecuteSkiplistQuery (v8::Arguments const& argv,
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot execute skiplist query");
TRI_V8_EXCEPTION(scope, res);
}
v8::Handle<v8::Object> err;
@ -1210,7 +1212,7 @@ static v8::Handle<v8::Value> ExecuteBitarrayQuery (v8::Arguments const& argv,
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot fetch documents");
TRI_V8_EXCEPTION(scope, res);
}
TRI_primary_collection_t* primary = trx.primaryCollection();
@ -1504,7 +1506,7 @@ static v8::Handle<v8::Value> EdgesQuery (TRI_edge_direction_e direction, v8::Arg
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot fetch edges");
TRI_V8_EXCEPTION(scope, res);
}
TRI_primary_collection_t* primary = trx.primaryCollection();
@ -1713,14 +1715,14 @@ static v8::Handle<v8::Value> JS_AllQuery (v8::Arguments const& argv) {
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot fetch documents");
TRI_V8_EXCEPTION(scope, res);
}
res = trx.read(docs, &barrier, skip, limit, &total);
res = trx.finish(res);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot fetch documents");
TRI_V8_EXCEPTION(scope, res);
}
const size_t n = docs.size();
@ -1791,14 +1793,14 @@ static v8::Handle<v8::Value> JS_OffsetQuery (v8::Arguments const& argv) {
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot fetch documents");
TRI_V8_EXCEPTION(scope, res);
}
res = trx.readOffset(docs, &barrier, internalSkip, batchSize, skip, &total);
res = trx.finish(res);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot fetch documents");
TRI_V8_EXCEPTION(scope, res);
}
const size_t n = docs.size();
@ -1869,7 +1871,7 @@ static v8::Handle<v8::Value> JS_AnyQuery (v8::Arguments const& argv) {
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot fetch document");
TRI_V8_EXCEPTION(scope, res);
}
res = trx.readRandom(&document, &barrier);
@ -1879,7 +1881,8 @@ static v8::Handle<v8::Value> JS_AnyQuery (v8::Arguments const& argv) {
if (barrier != 0) {
TRI_FreeBarrier(barrier);
}
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot fetch document");
TRI_V8_EXCEPTION(scope, res);
}
if (document._data == 0 || document._key == 0) {
@ -1929,7 +1932,7 @@ static v8::Handle<v8::Value> JS_ByExampleQuery (v8::Arguments const& argv) {
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot query by example");
TRI_V8_EXCEPTION(scope, res);
}
TRI_primary_collection_t* primary = trx.primaryCollection();
@ -2156,7 +2159,7 @@ static v8::Handle<v8::Value> JS_ByExampleHashIndex (v8::Arguments const& argv) {
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot query by example");
TRI_V8_EXCEPTION(scope, res);
}
v8::Handle<v8::Object> err;
@ -2218,6 +2221,135 @@ static v8::Handle<v8::Value> JS_ByConditionBitarray (v8::Arguments const& argv)
return ExecuteBitarrayQuery(argv, signature, QUERY_CONDITION);
}
typedef struct collection_checksum_s {
uint32_t _checksum;
TRI_string_buffer_t _buffer;
}
collection_checksum_t;
////////////////////////////////////////////////////////////////////////////////
/// @brief callback for checksum calculation, WD = with data
////////////////////////////////////////////////////////////////////////////////
template<bool WD> static bool ChecksumCalculator (TRI_doc_mptr_t const* mptr,
TRI_primary_collection_t* primary,
void* data) {
TRI_df_marker_t const* marker = static_cast<TRI_df_marker_t const*>(mptr->_data);
collection_checksum_t* helper = static_cast<collection_checksum_t*>(data);
uint32_t localCrc;
if (marker->_type == TRI_DOC_MARKER_KEY_DOCUMENT) {
// must convert _rid into string for portability (little vs. big endian etc.)
const string key = string(mptr->_key) + StringUtils::itoa(mptr->_rid);
localCrc = TRI_Crc32HashPointer(key.c_str(), key.size());
}
else if (marker->_type == TRI_DOC_MARKER_KEY_EDGE) {
TRI_doc_edge_key_marker_t const* e = (TRI_doc_edge_key_marker_t const*) marker;
// must convert _rid, _fromCid, _toCid into strings for portability
const string key = string(mptr->_key) + StringUtils::itoa(mptr->_rid) +
StringUtils::itoa(e->_toCid) + string(((char*) marker) + e->_offsetToKey) +
StringUtils::itoa(e->_fromCid) + string(((char*) marker) + e->_offsetFromKey);
localCrc = TRI_Crc32HashPointer(key.c_str(), key.size());
}
else {
return true;
}
if (WD) {
// with data
TRI_doc_document_key_marker_t const* d = (TRI_doc_document_key_marker_t const*) marker;
TRI_shaped_json_t shaped;
TRI_EXTRACT_SHAPED_JSON_MARKER(shaped, d);
TRI_StringifyArrayShapedJson(primary->_shaper, &helper->_buffer, &shaped, false);
localCrc += TRI_Crc32HashPointer(TRI_BeginStringBuffer(&helper->_buffer), TRI_LengthStringBuffer(&helper->_buffer));
TRI_ResetStringBuffer(&helper->_buffer);
}
helper->_checksum += localCrc;
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief calculates a checksum for the data in a collection
///
/// @FUN{@FA{collection}.checksum(@FA{withData})}
///
/// The @FN{checksum} operation calculates a CRC32 checksum of the meta-data
/// (keys and revision ids) contained in collection @FA{collection}.
///
/// If the optional argument @FA{withData} is set to @LIT{true}, then the
/// actual document data is also checksummed. Including the document data in
/// checksumming will make the calculation slower, but is more accurate.
////////////////////////////////////////////////////////////////////////////////
static v8::Handle<v8::Value> JS_ChecksumCollection (v8::Arguments const& argv) {
v8::HandleScope scope;
TRI_vocbase_col_t const* col;
col = TRI_UnwrapClass<TRI_vocbase_col_t>(argv.Holder(), TRI_GetVocBaseColType());
if (col == 0) {
TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection");
}
bool withData = false;
if (argv.Length() > 0) {
withData = TRI_ObjectToBoolean(argv[0]);
}
CollectionNameResolver resolver(col->_vocbase);
ReadTransactionType trx(col->_vocbase, resolver, col->_cid);
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION(scope, res);
}
TRI_primary_collection_t* primary = trx.primaryCollection();
Barrier barrier(primary);
collection_checksum_t helper;
helper._checksum = 0;
// .............................................................................
// inside a read transaction
// .............................................................................
trx.lockRead();
// get last tick
const string tick = StringUtils::itoa(primary->base._info._tick);
if (withData) {
TRI_InitStringBuffer(&helper._buffer, TRI_CORE_MEM_ZONE);
TRI_DocumentIteratorPrimaryCollection(primary, &helper, &ChecksumCalculator<true>);
TRI_DestroyStringBuffer(&helper._buffer);
}
else {
TRI_DocumentIteratorPrimaryCollection(primary, &helper, &ChecksumCalculator<false>);
}
trx.finish(res);
// .............................................................................
// outside a write transaction
// .............................................................................
v8::Handle<v8::Object> result = v8::Object::New();
result->Set(v8::String::New("checksum"), v8::Number::New(helper._checksum));
result->Set(v8::String::New("revision"), v8::String::New(tick.c_str(), tick.size()));
return scope.Close(result);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief selects all edges for a set of vertices
///
@ -2304,7 +2436,7 @@ static v8::Handle<v8::Value> FulltextQuery (ReadTransactionType& trx,
if (res != TRI_ERROR_NO_ERROR) {
TRI_FreeQueryFulltextIndex(query);
TRI_V8_EXCEPTION_MESSAGE(scope, res, "invalid value for <query>");
TRI_V8_EXCEPTION(scope, res);
}
TRI_fulltext_index_t* fulltextIndex = (TRI_fulltext_index_t*) idx;
@ -2312,7 +2444,7 @@ static v8::Handle<v8::Value> FulltextQuery (ReadTransactionType& trx,
if (isSubstringQuery && ! fulltextIndex->_indexSubstrings) {
TRI_FreeQueryFulltextIndex(query);
TRI_V8_EXCEPTION_MESSAGE(scope, res, "index does not support substring matching");
TRI_V8_EXCEPTION(scope, TRI_ERROR_NOT_IMPLEMENTED);
}
TRI_fulltext_result_t* queryResult = TRI_QueryFulltextIndex(fulltextIndex->_fulltextIndex, query);
@ -2393,7 +2525,7 @@ static v8::Handle<v8::Value> JS_FulltextQuery (v8::Arguments const& argv) {
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot execute fulltext query");
TRI_V8_EXCEPTION(scope, res);
}
v8::Handle<v8::Object> err;
@ -2465,7 +2597,7 @@ static v8::Handle<v8::Value> NearQuery (ReadTransactionType& trx,
int res = StoreGeoResult(trx, collection, cors, documents, distances);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot add document to geo-index");
TRI_V8_EXCEPTION(scope, res);
}
}
@ -2492,7 +2624,7 @@ static v8::Handle<v8::Value> JS_NearQuery (v8::Arguments const& argv) {
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot execute near query");
TRI_V8_EXCEPTION(scope, res);
}
v8::Handle<v8::Object> err;
@ -2560,7 +2692,7 @@ static v8::Handle<v8::Value> JS_TopQuery (v8::Arguments const& argv) {
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot execute pqueue query");
TRI_V8_EXCEPTION(scope, res);
}
v8::Handle<v8::Object> err;
@ -2655,7 +2787,7 @@ static v8::Handle<v8::Value> WithinQuery (ReadTransactionType& trx,
int res = StoreGeoResult(trx, collection, cors, documents, distances);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot add document to geo-index");
TRI_V8_EXCEPTION(scope, res);
}
}
@ -2682,7 +2814,7 @@ static v8::Handle<v8::Value> JS_WithinQuery (v8::Arguments const& argv) {
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_EXCEPTION_MESSAGE(scope, res, "cannot execute within query");
TRI_V8_EXCEPTION(scope, res);
}
v8::Handle<v8::Object> err;
@ -2749,6 +2881,7 @@ void TRI_InitV8Queries (v8::Handle<v8::Context> context) {
TRI_AddMethodVocbase(rt, "BY_EXAMPLE_BITARRAY", JS_ByExampleBitarray);
TRI_AddMethodVocbase(rt, "BY_EXAMPLE_HASH", JS_ByExampleHashIndex);
TRI_AddMethodVocbase(rt, "BY_EXAMPLE_SKIPLIST", JS_ByExampleSkiplist);
TRI_AddMethodVocbase(rt, "checksum", JS_ChecksumCollection);
TRI_AddMethodVocbase(rt, "edges", JS_EdgesQuery);
TRI_AddMethodVocbase(rt, "FULLTEXT", JS_FulltextQuery);
TRI_AddMethodVocbase(rt, "inEdges", JS_InEdgesQuery);

View File

@ -196,33 +196,27 @@ static int32_t const WRP_SHAPED_JSON_TYPE = 4;
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief create a v8 string value from an internal uint64_t id value
/// @brief create a v8 tick id value from the internal tick id
////////////////////////////////////////////////////////////////////////////////
static inline v8::Handle<v8::Value> V8StringId (const uint64_t id) {
static inline v8::Handle<v8::Value> V8TickId (const TRI_voc_tick_t tick) {
v8::HandleScope scope;
const string idStr = StringUtils::itoa(id);
const string id = StringUtils::itoa(tick);
v8::Handle<v8::Value> result = v8::String::New(idStr.c_str(), idStr.size());
return scope.Close(result);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a v8 collection id value from the internal collection id
////////////////////////////////////////////////////////////////////////////////
static inline v8::Handle<v8::Value> V8CollectionId (const uint64_t cid) {
return V8StringId(cid);
return scope.Close(v8::String::New(id.c_str(), id.size()));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a v8 revision id value from the internal revision id
////////////////////////////////////////////////////////////////////////////////
static inline v8::Handle<v8::Value> V8RevisionId (const uint64_t rid) {
return V8StringId(rid);
static inline v8::Handle<v8::Value> V8RevisionId (const TRI_voc_rid_t rid) {
v8::HandleScope scope;
const string id = StringUtils::itoa(rid);
return scope.Close(v8::String::New(id.c_str(), id.size()));
}
////////////////////////////////////////////////////////////////////////////////
@ -235,9 +229,7 @@ static inline v8::Handle<v8::Value> V8DocumentId (const string& collectionName,
const string id = DocumentHelper::assembleDocumentId(collectionName, key);
v8::Handle<v8::Value> result = v8::String::New(id.c_str(), id.size());
return scope.Close(result);
return scope.Close(v8::String::New(id.c_str(), id.size()));
}
////////////////////////////////////////////////////////////////////////////////
@ -2595,7 +2587,7 @@ static v8::Handle<v8::Value> JS_IdGeneralCursor (v8::Arguments const& argv) {
TRI_shadow_id id = TRI_GetIdDataShadowData(vocbase->_cursors, UnwrapGeneralCursor(argv.Holder()));
if (id != 0) {
return scope.Close(V8StringId(id));
return scope.Close(V8TickId(id));
}
TRI_V8_EXCEPTION(scope, TRI_ERROR_CURSOR_NOT_FOUND);
@ -7724,7 +7716,9 @@ v8::Handle<v8::Object> TRI_WrapCollection (TRI_vocbase_col_t const* collection)
const_cast<TRI_vocbase_col_t*>(collection));
if (! result.IsEmpty()) {
result->Set(v8g->_IdKey, V8CollectionId(collection->_cid), v8::ReadOnly);
const string cidString = StringUtils::itoa(collection->_cid);
result->Set(v8g->_IdKey, v8::String::New(cidString.c_str(), cidString.size()), v8::ReadOnly);
}
return scope.Close(result);

View File

@ -761,8 +761,7 @@ void TRI_DebugDatafileInfoPrimaryCollection (TRI_primary_collection_t* primary)
size_t TRI_DocumentIteratorPrimaryCollection (TRI_primary_collection_t* primary,
void* data,
bool (*callback)(TRI_doc_mptr_t const*,
TRI_primary_collection_t*, void*)) {
bool (*callback)(TRI_doc_mptr_t const*, TRI_primary_collection_t*, void*)) {
if (primary->_primaryIndex._nrUsed > 0) {
void** ptr = primary->_primaryIndex._table;
void** end = ptr + primary->_primaryIndex._nrAlloc;

View File

@ -379,6 +379,22 @@ ArangoCollection.prototype.figures = function () {
return requestResult.figures;
};
////////////////////////////////////////////////////////////////////////////////
/// @brief gets the checksum of a collection
////////////////////////////////////////////////////////////////////////////////
ArangoCollection.prototype.checksum = function (withData) {
var append = withData ? "?withData=true" : "";
var requestResult = this._database._connection.GET(this._baseurl("checksum") + append);
arangosh.checkRequestResult(requestResult);
return {
checksum: requestResult.checksum,
revision: requestResult.revision
};
};
////////////////////////////////////////////////////////////////////////////////
/// @brief gets the revision id of a collection
////////////////////////////////////////////////////////////////////////////////

View File

@ -312,7 +312,7 @@ function get_api_collections (req, res) {
excludeSystem = false;
if (req.parameters.hasOwnProperty('excludeSystem')) {
var value = req.parameters.excludeSystem;
var value = req.parameters.excludeSystem.toLowerCase();
if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') {
excludeSystem = true;
}
@ -586,6 +586,50 @@ function get_api_collections (req, res) {
///
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///
/// @RESTHEADER{GET /_api/collection/{collection-name}/checksum, returns a checksum for the collection
///
/// @RESTURLPARAMETERS
///
/// @RESTURLPARAM{collection-name,string,required}
///
/// @RESTURLPARAM{withData,boolean,optional}
///
/// @RESTDESCRIPTION
/// Will calculate checksum of the meta-data (keys and revision ids) and
/// optionally document data in the collection.
///
/// The checksum can be used to compare if two collections on different ArangoDB
/// instances contain the same contents. The current revision of the collection is
/// returned too so one can make sure the checksums are calculated for the same
/// state of data.
///
/// By default, the checksum will only be calculated on the `_key` and `_rev`
/// system attributes of the documents contained in the collection.
/// For edge collections, the system attributes `_from` and `_to` will also be
/// included in the calculation.
///
/// By providing the optional URL parameter `withData` with a value of `true`,
/// the user-defined document attributes will be included in the calculation too.
/// Note that including user-defined attributes will make the checksumming slower.
///
/// - `checksum`: The calculated checksum as a number.
///
/// - `revision`: The collection revision id as a string.
///
/// @RESTRETURNCODES
///
/// @RESTRETURNCODE{400}
/// If the `collection-name` is missing, then a `HTTP 400` is
/// returned.
///
/// @RESTRETURNCODE{404}
/// If the `collection-name` is unknown, then a `HTTP 404`
/// is returned.
///
////////////////////////////////////////////////////////////////////////////////
function get_api_collection (req, res) {
var name;
var result;
@ -629,11 +673,31 @@ function get_api_collection (req, res) {
if (req.suffix.length === 2) {
sub = decodeURIComponent(req.suffix[1]);
// .............................................................................
// /_api/collection/<identifier>/checksum
// .............................................................................
if (sub === "checksum") {
var withData = false;
if (req.parameters.hasOwnProperty('withData')) {
var value = req.parameters.withData.toLowerCase();
if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') {
withData = true;
}
}
result = collectionRepresentation(collection, false, false, false);
var checksum = collection.checksum(withData);
result.checksum = checksum.checksum;
result.revision = checksum.revision;
actions.resultOk(req, res, actions.HTTP_OK, result);
}
// .............................................................................
// /_api/collection/<identifier>/figures
// .............................................................................
if (sub === "figures") {
else if (sub === "figures") {
result = collectionRepresentation(collection, true, true, true);
headers = { location : "/" + API + "/" + collection.name() + "/figures" };
actions.resultOk(req, res, actions.HTTP_OK, result, headers);

View File

@ -378,6 +378,22 @@ ArangoCollection.prototype.figures = function () {
return requestResult.figures;
};
////////////////////////////////////////////////////////////////////////////////
/// @brief gets the checksum of a collection
////////////////////////////////////////////////////////////////////////////////
ArangoCollection.prototype.checksum = function (withData) {
var append = withData ? "?withData=true" : "";
var requestResult = this._database._connection.GET(this._baseurl("checksum") + append);
arangosh.checkRequestResult(requestResult);
return {
checksum: requestResult.checksum,
revision: requestResult.revision
};
};
////////////////////////////////////////////////////////////////////////////////
/// @brief gets the revision id of a collection
////////////////////////////////////////////////////////////////////////////////

View File

@ -830,6 +830,143 @@ function CollectionSuite () {
db._drop(cn2);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test checksum
////////////////////////////////////////////////////////////////////////////////
testChecksum : function () {
var cn = "example";
db._drop(cn);
var c1 = db._create(cn);
// empty collection, checksum should be 0
var r1 = c1.checksum();
assertTypeOf("string", r1.revision);
assertTrue(r1.revision !== "");
assertTrue(r1.revision.match(/^[0-9]+$/));
assertTypeOf("number", r1.checksum);
assertEqual(0, r1.checksum);
// inserting a doc, checksum should change
c1.save({ a : 1 });
var r2 = c1.checksum();
assertNotEqual(r1.revision, r2.revision);
assertTypeOf("string", r2.revision);
assertTrue(r2.revision !== "");
assertTrue(r2.revision.match(/^[0-9]+$/));
assertTypeOf("number", r2.checksum);
assertNotEqual(0, r2.checksum);
// inserting another doc, checksum should change
c1.save({ a : 2 });
var r3 = c1.checksum();
assertNotEqual(r1.revision, r3.revision);
assertNotEqual(r2.revision, r3.revision);
assertTypeOf("string", r3.revision);
assertTrue(r3.revision !== "");
assertTrue(r3.revision.match(/^[0-9]+$/));
assertTypeOf("number", r3.checksum);
assertNotEqual(0, r3.checksum);
assertNotEqual(r2.checksum, r3.checksum);
// test after unloading
c1.unload();
var r4 = c1.checksum();
assertTypeOf("string", r4.revision);
assertEqual(r3.revision, r4.revision);
assertTypeOf("number", r4.checksum);
assertNotEqual(0, r4.checksum);
assertEqual(r3.checksum, r4.checksum);
// test withData
var r5 = c1.checksum(true);
assertTypeOf("string", r5.revision);
assertEqual(r4.revision, r5.revision);
assertTypeOf("number", r5.checksum);
assertNotEqual(0, r5.checksum);
assertNotEqual(r4.checksum, r5.checksum);
// test after truncation
c1.truncate();
var r6 = c1.checksum();
assertTypeOf("string", r6.revision);
assertNotEqual(r4.revision, r6.revision);
assertNotEqual(r5.revision, r6.revision);
assertTypeOf("number", r6.checksum);
assertEqual(0, r6.checksum);
db._drop(cn);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test checksum
////////////////////////////////////////////////////////////////////////////////
testChecksumEdge : function () {
var cn = "example";
var vn = "example2";
db._drop(cn);
db._drop(vn);
db._create(vn);
var c1 = db._createEdgeCollection(cn);
var r1 = c1.checksum();
assertTypeOf("string", r1.revision);
assertTrue(r1.revision !== "");
assertTrue(r1.revision.match(/^[0-9]+$/));
assertTypeOf("number", r1.checksum);
assertEqual(0, r1.checksum);
c1.save(vn + "/1", vn + "/2", { a : 1 });
var r2 = c1.checksum();
assertNotEqual(r1.revision, r2.revision);
assertTypeOf("string", r2.revision);
assertTrue(r2.revision !== "");
assertTrue(r2.revision.match(/^[0-9]+$/));
assertTypeOf("number", r2.checksum);
assertNotEqual(0, r2.checksum);
c1.save(vn + "/1", vn + "/2", { a : 2 });
var r3 = c1.checksum();
assertNotEqual(r1.revision, r3.revision);
assertNotEqual(r2.revision, r3.revision);
assertTypeOf("string", r3.revision);
assertTrue(r3.revision !== "");
assertTrue(r3.revision.match(/^[0-9]+$/));
assertTypeOf("number", r3.checksum);
assertNotEqual(0, r3.checksum);
assertNotEqual(r2.checksum, r3.checksum);
c1.unload();
var r4 = c1.checksum();
assertTypeOf("string", r4.revision);
assertEqual(r3.revision, r4.revision);
assertTypeOf("number", r4.checksum);
assertEqual(r3.checksum, r4.checksum);
// test withData
var r5 = c1.checksum(true);
assertTypeOf("string", r5.revision);
assertEqual(r4.revision, r5.revision);
assertTypeOf("number", r5.checksum);
assertNotEqual(0, r5.checksum);
assertNotEqual(r4.checksum, r5.checksum);
// test after truncation
c1.truncate();
var r6 = c1.checksum();
assertTypeOf("string", r6.revision);
assertNotEqual(r4.revision, r6.revision);
assertNotEqual(r5.revision, r6.revision);
assertTypeOf("number", r6.checksum);
assertEqual(0, r6.checksum);
db._drop(cn);
db._drop(vn);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test revision id
////////////////////////////////////////////////////////////////////////////////
@ -864,6 +1001,8 @@ function CollectionSuite () {
var r4 = c1.revision();
assertTypeOf("string", r4);
assertEqual(r3, r4);
db._drop(cn);
},
////////////////////////////////////////////////////////////////////////////////

View File

@ -300,7 +300,7 @@ void TRI_IncreaseLengthStringBuffer (TRI_string_buffer_t * self, size_t n) {
/// @brief returns true if buffer is empty
////////////////////////////////////////////////////////////////////////////////
bool TRI_EmptyStringBuffer (TRI_string_buffer_t const * self) {
bool TRI_EmptyStringBuffer (TRI_string_buffer_t const* self) {
return self->_buffer == self->_current;
}
@ -308,13 +308,27 @@ bool TRI_EmptyStringBuffer (TRI_string_buffer_t const * self) {
/// @brief clears the buffer
////////////////////////////////////////////////////////////////////////////////
void TRI_ClearStringBuffer (TRI_string_buffer_t * self) {
void TRI_ClearStringBuffer (TRI_string_buffer_t* self) {
if (self->_buffer != NULL) {
self->_current = self->_buffer;
memset(self->_buffer, 0, self->_len + 1);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief resets the buffer (without clearing)
////////////////////////////////////////////////////////////////////////////////
void TRI_ResetStringBuffer (TRI_string_buffer_t* self) {
if (self->_buffer != NULL) {
self->_current = self->_buffer;
if (self->_len > 0) {
*self->_current = '\0';
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the character at the end of the string buffer
////////////////////////////////////////////////////////////////////////////////

View File

@ -180,7 +180,13 @@ bool TRI_EmptyStringBuffer (TRI_string_buffer_t const * self);
/// @brief clears the buffer
////////////////////////////////////////////////////////////////////////////////
void TRI_ClearStringBuffer (TRI_string_buffer_t * self);
void TRI_ClearStringBuffer (TRI_string_buffer_t*);
////////////////////////////////////////////////////////////////////////////////
/// @brief resets the buffer (without clearing)
////////////////////////////////////////////////////////////////////////////////
void TRI_ResetStringBuffer (TRI_string_buffer_t*);
////////////////////////////////////////////////////////////////////////////////
/// @brief return the character at the end of the string buffer