1
0
Fork 0

fixed read

This commit is contained in:
Frank Celler 2012-03-18 23:30:41 +01:00
parent d4303479a8
commit d13c6813d0
14 changed files with 383 additions and 79 deletions

1
.gitignore vendored
View File

@ -71,3 +71,4 @@ UnitTests/Philadelphia/Runner.cpp
UnitTests/test_suite
.v8-build
VC++
UnitTests/RestDocuments/*.log

View File

@ -0,0 +1,12 @@
> curl --data @- -X POST --dump - http://localhost:8529/document?collection=173347
{ 1 : 2 }
HTTP/1.1 400 Bad Request
content-type: application/json; charset=utf-8
{
"errorMessage": "expecting attribute name",
"errorNum": 502,
"code": 400,
"error": true
}

View File

@ -0,0 +1,11 @@
> curl -X GET --dump - http://localhost:8529/document/161039/11108289
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
etag: "11108289"
{
"_rev": 11108289,
"_id": "161039/11108289",
"Hallo": "World"
}

View File

@ -0,0 +1,5 @@
> curl -X GET --dump - http://localhost:8529/document/161039/11173825
HTTP/1.1 304 Not Modified
content-type: text/plain;charset=utf-8
etag: "11173825"

View File

@ -0,0 +1,11 @@
> curl -X GET --dump - http://localhost:8529/document/161039/234567
HTTP/1.1 404 Not Found
content-type: application/json; charset=utf-8
{
"errorMessage": "document /document/161039 not found",
"errorNum": 1200,
"code": 404,
"error": true
}

View File

@ -64,6 +64,7 @@ namespace triagens {
case NOT_FOUND: return "404 Not Found";
case METHOD_NOT_ALLOWED: return "405 Method";
case CONFLICT: return "409 Conflict";
case PRECONDITION_FAILED: return "412 Precondition Failed";
case UNPROCESSABLE_ENTITY: return "422 Unprocessable Entity";
case SERVER_ERROR: return "500 Internal Error";
@ -101,6 +102,7 @@ namespace triagens {
case 404: return NOT_FOUND;
case 405: return METHOD_NOT_ALLOWED;
case 409: return CONFLICT;
case 412: return PRECONDITION_FAILED;
case 422: return UNPROCESSABLE_ENTITY;
case 500: return SERVER_ERROR;

View File

@ -115,6 +115,7 @@ namespace triagens {
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
CONFLICT = 409,
PRECONDITION_FAILED = 412,
UNPROCESSABLE_ENTITY = 422,
SERVER_ERROR = 500,

View File

@ -194,7 +194,7 @@ HttpHandler::status_e RestDocumentHandler::execute () {
///
/// @verbinclude rest_create-document
///
/// Create a document in collection where @LIT{waitForSync} is @LIT{false}.
/// Create a document in a collection where @LIT{waitForSync} is @LIT{false}.
///
/// @verbinclude rest_create-document-accept
///
@ -208,15 +208,11 @@ HttpHandler::status_e RestDocumentHandler::execute () {
///
/// Unknown collection identifier:
///
/// @verbinclude rest4
/// @verbinclude rest_create-document-unknown-cid
///
/// Illegal document:
///
/// @verbinclude rest5
///
/// Create a document given a collection name:
///
/// @verbinclude rest6
/// @verbinclude rest_create-document-bad-json
////////////////////////////////////////////////////////////////////////////////
bool RestDocumentHandler::createDocument () {
@ -315,19 +311,19 @@ bool RestDocumentHandler::createDocument () {
////////////////////////////////////////////////////////////////////////////////
bool RestDocumentHandler::readDocument () {
#ifdef FIXME
switch (request->suffix().size()) {
case 0:
return readAllDocuments();
case 1:
case 2:
return readSingleDocument(true);
default:
generateError(HttpResponse::BAD, "expecting URI /document/<document-handle>");
generateError(HttpResponse::BAD,
TRI_REST_ERROR_SUPERFLUOUS_SUFFICES,
"expecting GET /document/<document-handle>");
return false;
}
#endif
}
////////////////////////////////////////////////////////////////////////////////
@ -345,41 +341,45 @@ bool RestDocumentHandler::readDocument () {
/// If the @FA{document-handle} points to a non-existing document, then a
/// @LIT{HTTP 404} is returned and the body contains an error document.
///
/// If the "If-None-Match" header is given, then it must contain exactly one
/// etag. The document is returned, if it has a different revision than the
/// given etag. Otherwise a @LIT{HTTP 304} is returned.
///
/// If the "If-Match" header is given, then it must contain exactly one
/// etag. The document is returned, if it has the same revision ad the
/// given etag. Otherwise a @LIT{HTTP 412} is returned.
///
/// @EXAMPLES
///
/// Use a collection and document identfier:
/// Use a document handle:
///
/// @verbinclude rest1
/// @verbinclude rest_read-document
///
/// Use a collection name and document reference:
/// Use a document handle and an etag:
///
/// @verbinclude rest18
/// @verbinclude rest_read-document-if-none-match
///
/// Unknown document identifier:
/// Unknown document handle:
///
/// @verbinclude rest2
///
/// Unknown collection identifier:
///
/// @verbinclude rest17
/// @verbinclude rest_read-document-unknown-handle
////////////////////////////////////////////////////////////////////////////////
bool RestDocumentHandler::readSingleDocument (bool generateBody) {
#ifdef FIXME
vector<string> const& suffix = request->suffix();
/// check for an etag
string ifNoneMatch = request->header("if-none-match");
TRI_voc_rid_t ifNoneRid = parseEtag(ifNoneMatch);
string ifMatch = request->header("if-match");
TRI_voc_rid_t ifRid = parseEtag(ifMatch);
// split the document reference
string cid;
string did;
bool ok = splitDocumentReference(suffix[0], cid, did);
if (! ok) {
return false;
}
string cid = suffix[0];
string did = suffix[1];
// find and load collection given by name oder identifier
ok = findCollection(cid) && loadCollection();
bool ok = findCollection(cid) && loadCollection();
if (! ok) {
return false;
@ -391,6 +391,7 @@ bool RestDocumentHandler::readSingleDocument (bool generateBody) {
_documentCollection->beginRead(_documentCollection);
// FIXME FIXME
TRI_doc_mptr_t const* document = findDocument(did);
_documentCollection->endRead(_documentCollection);
@ -404,8 +405,42 @@ bool RestDocumentHandler::readSingleDocument (bool generateBody) {
return false;
}
generateDocument(document, generateBody);
#endif
TRI_voc_rid_t rid = document->_rid;
if (ifNoneRid == 0) {
if (ifRid == 0) {
generateDocument(document, generateBody);
}
else if (ifRid == rid) {
generateDocument(document, generateBody);
}
else {
generatePreconditionFailed();
}
}
else if (ifNoneRid == rid) {
if (ifRid == 0) {
generateNotModified(StringUtils::itoa(rid));
}
else if (ifRid == rid) {
generateNotModified(StringUtils::itoa(rid));
}
else {
generatePreconditionFailed();
}
}
else {
if (ifRid == 0) {
generateDocument(document, generateBody);
}
else if (ifRid == rid) {
generateDocument(document, generateBody);
}
else {
generatePreconditionFailed();
}
}
return true;
}

View File

@ -240,6 +240,24 @@ void RestVocbaseBaseHandler::generateNotImplemented (string const& path) {
"'" + path + "' not implemented");
}
////////////////////////////////////////////////////////////////////////////////
/// @brief generates precondition failed
////////////////////////////////////////////////////////////////////////////////
void RestVocbaseBaseHandler::generatePreconditionFailed () {
response = new HttpResponse(HttpResponse::PRECONDITION_FAILED);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief generates not modified
////////////////////////////////////////////////////////////////////////////////
void RestVocbaseBaseHandler::generateNotModified (string const& etag) {
response = new HttpResponse(HttpResponse::NOT_MODIFIED);
response->setHeader("ETag", "\"" + etag + "\"");
}
////////////////////////////////////////////////////////////////////////////////
/// @brief generates next entry from a result set
////////////////////////////////////////////////////////////////////////////////
@ -301,26 +319,6 @@ void RestVocbaseBaseHandler::generateDocument (TRI_doc_mptr_t const* document,
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief splits a document reference into to parts
////////////////////////////////////////////////////////////////////////////////
bool RestVocbaseBaseHandler::splitDocumentReference (string const& name, string& cid, string& did) {
vector<string> doc = StringUtils::split(name, TRI_DOCUMENT_HANDLE_SEPARATOR_STR);
if (doc.size() != 2) {
generateError(HttpResponse::BAD,
TRI_VOC_ERROR_DOCUMENT_HANDLE_BAD,
"missing or illegal document handle");
return false;
}
cid = doc[0];
did = doc[1];
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief extracts the revision
////////////////////////////////////////////////////////////////////////////////
@ -539,6 +537,21 @@ TRI_doc_mptr_t const* RestVocbaseBaseHandler::findDocument (string const& doc) {
return document;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief converts an etag field
////////////////////////////////////////////////////////////////////////////////
TRI_voc_rid_t RestVocbaseBaseHandler::parseEtag (string const& etag) {
string trim = StringUtils::trim(etag);
size_t len = trim.size();
if (len < 2 || trim[0] != '"' || trim[len-1] != '"') {
return 0;
}
return StringUtils::int64(trim.substr(1, len-2));
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

View File

@ -252,18 +252,24 @@ namespace triagens {
void generateNotImplemented (string const& path);
////////////////////////////////////////////////////////////////////////////////
/// @brief generates precondition failed
////////////////////////////////////////////////////////////////////////////////
void generatePreconditionFailed ();
////////////////////////////////////////////////////////////////////////////////
/// @brief generates not modified
////////////////////////////////////////////////////////////////////////////////
void generateNotModified (string const& etag);
////////////////////////////////////////////////////////////////////////////////
/// @brief generates first entry from a result set
////////////////////////////////////////////////////////////////////////////////
void generateDocument (TRI_doc_mptr_t const*, bool generateBody);
////////////////////////////////////////////////////////////////////////////////
/// @brief splits a document reference into to parts
////////////////////////////////////////////////////////////////////////////////
bool splitDocumentReference (string const& name, string& cid, string& did);
////////////////////////////////////////////////////////////////////////////////
/// @brief extracts the revision
////////////////////////////////////////////////////////////////////////////////
@ -300,6 +306,12 @@ namespace triagens {
TRI_doc_mptr_t const* findDocument (string const& doc);
////////////////////////////////////////////////////////////////////////////////
/// @brief converts an etag field
////////////////////////////////////////////////////////////////////////////////
TRI_voc_rid_t parseEtag (string const& etag);
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

View File

@ -26,7 +26,11 @@ class AvocadoDB
end
def self.log (args)
logfile = File.new("output.log", "a")
if args.key?(:output)
logfile = File.new("#{args[:output]}.log", "a")
else
logfile = File.new("output.log", "a")
end
method = args[:method] || :get
url = args[:url]
@ -37,7 +41,8 @@ class AvocadoDB
logfile.puts '-' * 80
if method == :get
logfile.puts "MISSING"
logfile.puts "> curl -X GET --dump - http://localhost:8529#{url}"
logfile.puts
elsif method == :post
if body == nil
logfile.puts "> curl -X POST --dump - http://localhost:8529#{url}"

View File

@ -19,7 +19,7 @@ describe AvocadoDB do
doc.parsed_response['code'].should eq(400)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
AvocadoDB.log(:method => :post, :url => cmd, :result => doc)
AvocadoDB.log(:method => :post, :url => cmd, :result => doc, :output => "rest_create-document-missing-cid")
end
it "returns an error if url contains a suffix" do
@ -32,7 +32,7 @@ describe AvocadoDB do
doc.parsed_response['code'].should eq(400)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
AvocadoDB.log(:method => :post, :url => cmd, :result => doc)
AvocadoDB.log(:method => :post, :url => cmd, :result => doc, :output => "rest_create.document-superfluous-suffix")
end
it "returns an error if the collection identifier is unknown" do
@ -45,7 +45,7 @@ describe AvocadoDB do
doc.parsed_response['code'].should eq(404)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
AvocadoDB.log(:method => :post, :url => cmd, :result => doc)
AvocadoDB.log(:method => :post, :url => cmd, :result => doc, :output => "rest_create-document-unknown-cid")
end
it "returns an error if the collection name is unknown" do
@ -58,7 +58,7 @@ describe AvocadoDB do
doc.parsed_response['code'].should eq(404)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
AvocadoDB.log(:method => :post, :url => cmd, :result => doc)
AvocadoDB.log(:method => :post, :url => cmd, :result => doc, :output => "rest_create-document-unknown-name")
end
it "returns an error if the JSON body is corrupted" do
@ -77,7 +77,7 @@ describe AvocadoDB do
doc.parsed_response['code'].should eq(400)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
AvocadoDB.log(:method => :post, :url => cmd, :body => body, :result => doc)
AvocadoDB.log(:method => :post, :url => cmd, :body => body, :result => doc, :output => "rest_create-document-bad-json")
AvocadoDB.drop_collection("UnitTestsCollectionBasics")
end
end
@ -124,7 +124,7 @@ describe AvocadoDB do
etag.should eq("\"#{rev}\"")
location.should eq("/document/#{did}")
AvocadoDB.log(:method => :post, :url => cmd, :body => body, :result => doc)
AvocadoDB.log(:method => :post, :url => cmd, :body => body, :result => doc, :output => "rest_create-document")
AvocadoDB.delete(location)
end
@ -172,7 +172,7 @@ describe AvocadoDB do
etag.should eq("\"#{rev}\"")
location.should eq("/document/#{did}")
AvocadoDB.log(:method => :post, :url => cmd, :body => body, :result => doc)
AvocadoDB.log(:method => :post, :url => cmd, :body => body, :result => doc, :output => "rest_create-document-accept")
AvocadoDB.delete(location)
end
@ -220,7 +220,7 @@ describe AvocadoDB do
etag.should eq("\"#{rev}\"")
location.should eq("/document/#{did}")
AvocadoDB.log(:method => :post, :url => cmd, :body => body, :result => doc)
AvocadoDB.log(:method => :post, :url => cmd, :body => body, :result => doc, :output => "rest_create-document-new-named-collection")
AvocadoDB.delete(location)
end
@ -277,7 +277,7 @@ describe AvocadoDB do
etag.should eq("\"#{rev}\"")
location.should eq("/document/#{did}")
AvocadoDB.log(:method => :post, :url => cmd, :body => body, :result => doc)
AvocadoDB.log(:method => :post, :url => cmd, :body => body, :result => doc, :output => "rest_create-document-create-collection")
AvocadoDB.delete(location)
end

View File

@ -0,0 +1,201 @@
require 'rspec'
require './avocadodb.rb'
describe AvocadoDB do
context "reading a document in a collection" do
################################################################################
## error handling
################################################################################
context "error handling" do
before do
@cn = "UnitTestsCollectionBasics"
@cid = AvocadoDB.create_collection(@cn)
end
after do
AvocadoDB.drop_collection(@cn)
end
it "returns an error if document handle is corrupted" do
cmd = "/document/123456"
doc = AvocadoDB.get(cmd)
doc.code.should eq(400)
doc.parsed_response['error'].should eq(true)
doc.parsed_response['errorNum'].should eq(503)
doc.parsed_response['code'].should eq(400)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
AvocadoDB.log(:method => :get, :url => cmd, :result => doc, :output => "rest_read-document-bad-handle")
end
it "returns an error if collection identifier is unknown" do
cmd = "/document/123456/234567"
doc = AvocadoDB.get(cmd)
doc.code.should eq(404)
doc.parsed_response['error'].should eq(true)
doc.parsed_response['errorNum'].should eq(1201)
doc.parsed_response['code'].should eq(404)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
AvocadoDB.log(:method => :get, :url => cmd, :result => doc, :output => "rest_read-document-unknown-cid")
end
it "returns an error if document handle is unknown" do
cmd = "/document/#{@cid}/234567"
doc = AvocadoDB.get(cmd)
doc.code.should eq(404)
doc.parsed_response['error'].should eq(true)
doc.parsed_response['errorNum'].should eq(1200)
doc.parsed_response['code'].should eq(404)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
AvocadoDB.log(:method => :get, :url => cmd, :result => doc, :output => "rest_read-document-unknown-handle")
end
end
################################################################################
## reading documents
################################################################################
context "reading documents" do
before do
@cn = "UnitTestsCollectionBasics"
@cid = AvocadoDB.create_collection(@cn)
end
after do
AvocadoDB.drop_collection(@cn)
end
it "create a document and read it" do
cmd = "/document?collection=#{@cid}"
body = "{ \"Hallo\" : \"World\" }"
doc = AvocadoDB.post(cmd, :body => body)
doc.code.should eq(201)
location = doc.headers['location']
location.should be_kind_of(String)
did = doc.parsed_response['_id']
rev = doc.parsed_response['_rev']
# get document
cmd = "/document/#{did}"
doc = AvocadoDB.get(cmd)
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
did2 = doc.parsed_response['_id']
did2.should be_kind_of(String)
did2.should eq(did)
rev2 = doc.parsed_response['_rev']
rev2.should be_kind_of(Integer)
rev2.should eq(rev)
etag = doc.headers['etag']
etag.should be_kind_of(String)
etag.should eq("\"#{rev}\"")
AvocadoDB.log(:method => :get, :url => cmd, :result => doc, :output => "rest_read-document")
AvocadoDB.delete(location)
end
it "create a document and read it, use if-none-match" do
cmd = "/document?collection=#{@cid}"
body = "{ \"Hallo\" : \"World\" }"
doc = AvocadoDB.post(cmd, :body => body)
doc.code.should eq(201)
location = doc.headers['location']
location.should be_kind_of(String)
did = doc.parsed_response['_id']
rev = doc.parsed_response['_rev']
# get document, if-none-match with same rev
cmd = "/document/#{did}"
doc = AvocadoDB.get(cmd, :headers => { "if-none-match" => "\"#{rev}\"" })
doc.code.should eq(304)
etag = doc.headers['etag']
etag.should be_kind_of(String)
etag.should eq("\"#{rev}\"")
AvocadoDB.log(:method => :get, :url => cmd, :result => doc, :output => "rest_read-document-if-none-match")
# get document, if-none-match with different rev
cmd = "/document/#{did}"
doc = AvocadoDB.get(cmd, :headers => { "if-none-match" => "\"#{rev-1}\"" })
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
etag = doc.headers['etag']
etag.should be_kind_of(String)
etag.should eq("\"#{rev}\"")
did2 = doc.parsed_response['_id']
did2.should be_kind_of(String)
did2.should eq(did)
rev2 = doc.parsed_response['_rev']
rev2.should be_kind_of(Integer)
rev2.should eq(rev)
etag = doc.headers['etag']
etag.should be_kind_of(String)
etag.should eq("\"#{rev}\"")
AvocadoDB.log(:method => :get, :url => cmd, :result => doc, :output => "rest_read-document-if-none-match-other")
# get document, if-match with same rev
cmd = "/document/#{did}"
doc = AvocadoDB.get(cmd, :headers => { "if-match" => "\"#{rev}\"" })
doc.code.should eq(200)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
did2 = doc.parsed_response['_id']
did2.should be_kind_of(String)
did2.should eq(did)
rev2 = doc.parsed_response['_rev']
rev2.should be_kind_of(Integer)
rev2.should eq(rev)
etag = doc.headers['etag']
etag.should be_kind_of(String)
etag.should eq("\"#{rev}\"")
AvocadoDB.log(:method => :get, :url => cmd, :result => doc, :output => "rest_read-document-if-match")
# get document, if-match with different rev
cmd = "/document/#{did}"
doc = AvocadoDB.get(cmd, :headers => { "if-match" => "\"#{rev-1}\"" })
doc.code.should eq(412)
AvocadoDB.log(:method => :get, :url => cmd, :result => doc, :output => "rest_read-document-if-match-other")
AvocadoDB.delete(location)
end
end
end
end

View File

@ -1316,15 +1316,6 @@ static v8::Handle<v8::Value> JS_WithinQuery (v8::Arguments const& argv) {
return scope.Close(result);
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup VocBase
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief get a (persistent) cursor by its id
////////////////////////////////////////////////////////////////////////////////
@ -1358,6 +1349,10 @@ static v8::Handle<v8::Value> JS_Cursor (v8::Arguments const& argv) {
return scope.Close(WrapQueryCursor(cursor));
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- AVOCADO QUERY LANGUAGE
// -----------------------------------------------------------------------------