diff --git a/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return b/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return new file mode 100644 index 0000000000..6f830a1a4e --- /dev/null +++ b/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return @@ -0,0 +1,25 @@ +> curl --data @- -X POST --dump - http://localhost:8529/_api/cursor +{ "query" : "FOR u IN users LIMIT 5 RETURN u", "count" : true, "batchSize" : 2 } + +HTTP/1.1 201 Created +content-type: application/json + +{ + "hasMore": true, + "error": false, + "id": 26011191, + "result": [ + { + "n": 0, + "_rev": 25880119, + "_id": "23914039/25880119" + }, + { + "n": 1, + "_rev": 25880119, + "_id": "23914039/25880119" + } + ], + "code": 201, + "count": 5 +} diff --git a/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return-cont b/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return-cont new file mode 100644 index 0000000000..e7e9ffd3f3 --- /dev/null +++ b/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return-cont @@ -0,0 +1,24 @@ +> curl -X PUT --dump - http://localhost:8529/_api/cursor/26011191 + +HTTP/1.1 200 OK +content-type: application/json + +{ + "hasMore": true, + "error": false, + "id": 26011191, + "result": [ + { + "n": 2, + "_rev": 25880119, + "_id": "23914039/25880119" + }, + { + "n": 3, + "_rev": 25880119, + "_id": "23914039/25880119" + } + ], + "code": 200, + "count": 5 +} diff --git a/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return-cont2 b/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return-cont2 new file mode 100644 index 0000000000..36e2433897 --- /dev/null +++ b/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return-cont2 @@ -0,0 +1,18 @@ +> curl -X PUT --dump - http://localhost:8529/_api/cursor/26011191 + +HTTP/1.1 200 OK +content-type: application/json + +{ + "hasMore": false, + "error": false, + "result": [ + { + "n": 4, + "_rev": 25880119, + "_id": "23914039/25880119" + } + ], + "code": 200, + "count": 5 +} diff --git a/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return-cont3 b/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return-cont3 new file mode 100644 index 0000000000..edba13fd58 --- /dev/null +++ b/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return-cont3 @@ -0,0 +1,11 @@ +> curl -X PUT --dump - http://localhost:8529/_api/cursor/26011191 + +HTTP/1.1 400 Bad Request +content-type: application/json + +{ + "errorNum": 1600, + "errorMessage": "cursor not found: disposed or unknown cursor", + "error": true, + "code": 400 +} diff --git a/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return-single b/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return-single new file mode 100644 index 0000000000..26a828659e --- /dev/null +++ b/Doxygen/Examples.AvocadoDB/api-cursor-create-for-limit-return-single @@ -0,0 +1,24 @@ +> curl --data @- -X POST --dump - http://localhost:8529/_api/cursor +{ "query" : "FOR u IN users LIMIT 2 RETURN u", "count" : true, "batchSize" : 2 } + +HTTP/1.1 201 Created +content-type: application/json + +{ + "hasMore": false, + "error": false, + "result": [ + { + "n": 0, + "_rev": 21030455, + "_id": "19588663/21030455" + }, + { + "n": 1, + "_rev": 21030455, + "_id": "19588663/21030455" + } + ], + "code": 201, + "count": 2 +} diff --git a/Doxygen/Examples.AvocadoDB/api-cursor-delete b/Doxygen/Examples.AvocadoDB/api-cursor-delete new file mode 100644 index 0000000000..0ebc9fd1a2 --- /dev/null +++ b/Doxygen/Examples.AvocadoDB/api-cursor-delete @@ -0,0 +1,10 @@ +> curl -X DELETE --dump - http://localhost:8529/_api/cursor/8679702 + +HTTP/1.1 202 Accepted +content-type: application/json + +{ + "code": 202, + "id": "8679702", + "error": false +} diff --git a/Doxygen/Examples.AvocadoDB/api-cursor-invalid-cursor-identifier b/Doxygen/Examples.AvocadoDB/api-cursor-invalid-cursor-identifier new file mode 100644 index 0000000000..c38e6db1d9 --- /dev/null +++ b/Doxygen/Examples.AvocadoDB/api-cursor-invalid-cursor-identifier @@ -0,0 +1,11 @@ +> curl -X PUT --dump - http://localhost:8529/_api/cursor/123456 + +HTTP/1.1 400 Bad Request +content-type: application/json + +{ + "code": 400, + "errorNum": 1600, + "error": true, + "errorMessage": "cursor not found: disposed or unknown cursor" +} diff --git a/Doxygen/Examples.AvocadoDB/api-cursor-missing-body b/Doxygen/Examples.AvocadoDB/api-cursor-missing-body new file mode 100644 index 0000000000..08c3c499ac --- /dev/null +++ b/Doxygen/Examples.AvocadoDB/api-cursor-missing-body @@ -0,0 +1,11 @@ +> curl -X POST --dump - http://localhost:8529/_api/cursor + +HTTP/1.1 400 Bad Request +content-type: application/json + +{ + "errorNum": 1503, + "code": 400, + "error": true, + "errorMessage": "query specification invalid" +} diff --git a/Doxygen/Examples.AvocadoDB/api-cursor-missing-cursor-identifier b/Doxygen/Examples.AvocadoDB/api-cursor-missing-cursor-identifier new file mode 100644 index 0000000000..07400d75e9 --- /dev/null +++ b/Doxygen/Examples.AvocadoDB/api-cursor-missing-cursor-identifier @@ -0,0 +1,11 @@ +> curl -X PUT --dump - http://localhost:8529/_api/cursor + +HTTP/1.1 400 Bad Request +content-type: application/json + +{ + "code": 400, + "errorMessage": "bad parameter", + "errorNum": 400, + "error": true +} diff --git a/Doxygen/Examples.AvocadoDB/api-cursor-unknown-collection b/Doxygen/Examples.AvocadoDB/api-cursor-unknown-collection new file mode 100644 index 0000000000..a90106acab --- /dev/null +++ b/Doxygen/Examples.AvocadoDB/api-cursor-unknown-collection @@ -0,0 +1,12 @@ +> curl --data @- -X POST --dump - http://localhost:8529/_api/cursor +{ "query" : "FOR u IN unknowncollection LIMIT 2 RETURN u.n", "count" : true, "bindVars" : {}, "batchSize" : 2 } + +HTTP/1.1 400 Bad Request +content-type: application/json + +{ + "code": 400, + "error": true, + "errorMessage": "unable to open collection '%s': unable to open collection 'unknowncollection'", + "errorNum": 1510 +} diff --git a/Doxygen/Examples.AvocadoDB/api-query-invalid b/Doxygen/Examples.AvocadoDB/api-query-invalid new file mode 100644 index 0000000000..549a199256 --- /dev/null +++ b/Doxygen/Examples.AvocadoDB/api-query-invalid @@ -0,0 +1,12 @@ +> curl --data @- -X POST --dump - http://localhost:8529/_api/query +{ "query" : "FOR u IN users FILTER u.name = @name LIMIT 2 RETURN u.n" } + +HTTP/1.1 400 Bad Request +content-type: application/json + +{ + "errorNum": 1501, + "errorMessage": "parse error: %s: parse error: 1:29 syntax error, unexpected assignment near ' = @name LIMIT 2 RETURN u.n'", + "error": true, + "code": 400 +} diff --git a/Doxygen/Examples.AvocadoDB/api-query-valid b/Doxygen/Examples.AvocadoDB/api-query-valid new file mode 100644 index 0000000000..8b57637da3 --- /dev/null +++ b/Doxygen/Examples.AvocadoDB/api-query-valid @@ -0,0 +1,13 @@ +> curl --data @- -X POST --dump - http://localhost:8529/_api/query +{ "query" : "FOR u IN users FILTER u.name == @name LIMIT 2 RETURN u.n" } + +HTTP/1.1 200 OK +content-type: application/json + +{ + "error": false, + "bindVars": [ + "name" + ], + "code": 200 +} diff --git a/Makefile.doxygen b/Makefile.doxygen index d65f038846..63cad81cef 100644 --- a/Makefile.doxygen +++ b/Makefile.doxygen @@ -56,7 +56,6 @@ doxygen: Doxygen/avocado.doxy $(DOXYGEN) wiki: doxygen $(addsuffix .md,$(addprefix Doxygen/xml/,$(WIKI))) @test -d Doxygen/wiki || mkdir Doxygen/wiki @for w in $(WIKI); do @top_srcdir@/Doxygen/Scripts/pandoc.sh Doxygen/xml/$$w.md; done - @for w in $(WIKI); do @top_srcdir@/Doxygen/Scripts/md2html.sh Doxygen/wiki/$$w.md; done ################################################################################ ## CLEANUP diff --git a/Makefile.files b/Makefile.files index 84893eac44..595456bc30 100644 --- a/Makefile.files +++ b/Makefile.files @@ -374,6 +374,7 @@ WIKI = \ Graphs \ Home \ HttpCollection \ + HttpCursor \ HttpIndex \ HttpInterface \ HttpSystem \ diff --git a/Makefile.in b/Makefile.in index 3d0ae1b8b4..b0cc060c62 100644 --- a/Makefile.in +++ b/Makefile.in @@ -962,6 +962,7 @@ WIKI = \ Graphs \ Home \ HttpCollection \ + HttpCursor \ HttpIndex \ HttpInterface \ HttpSystem \ @@ -2939,7 +2940,6 @@ doxygen: Doxygen/avocado.doxy $(DOXYGEN) wiki: doxygen $(addsuffix .md,$(addprefix Doxygen/xml/,$(WIKI))) @test -d Doxygen/wiki || mkdir Doxygen/wiki @for w in $(WIKI); do @top_srcdir@/Doxygen/Scripts/pandoc.sh Doxygen/xml/$$w.md; done - @for w in $(WIKI); do @top_srcdir@/Doxygen/Scripts/md2html.sh Doxygen/wiki/$$w.md; done .setup-directories: @test -d js || mkdir js diff --git a/RestServer/api-cursor.dox b/RestServer/api-cursor.dox index 859295393a..bef0206dde 100644 --- a/RestServer/api-cursor.dox +++ b/RestServer/api-cursor.dox @@ -89,21 +89,39 @@ /// attribute of the result set. If it is set to false, then the client has /// fetched the complete result set from the server. /// +/// @EXAMPLES +/// +/// @verbinclude api-cursor-create-for-limit-return-single +/// /// @subsection HttpCursorResultsCursor Using a Cursor ////////////////////////////////////////////////////// /// /// If the result set contains more documents than should be transferred in a /// single roundtrip (i.e. as set via the @LIT{batchSize} attribute), the server /// will return the first few documents and create a temporary cursor. The -/// cursor id will also be returned to the client. The server will put the -/// cursor id in the @LIT{id} attribute of the response object. Furthermore, -/// the @LIT{hasMore} attribute of the response object will be set to -/// @LIT{true}. This is an indication for the client that there are additional -/// results to fetch from the server. +/// cursor identifier will also be returned to the client. The server will put +/// the cursor identifier in the @LIT{id} attribute of the response +/// object. Furthermore, the @LIT{hasMore} attribute of the response object will +/// be set to @LIT{true}. This is an indication for the client that there are +/// additional results to fetch from the server. /// /// @EXAMPLES /// -/// @verbinclude cursorput1 +/// Create and extract first batch: +/// +/// @verbinclude api-cursor-create-for-limit-return +/// +/// Extract next batch, still have more: +/// +/// @verbinclude api-cursor-create-for-limit-return-cont +/// +/// Extract next batch, done: +/// +/// @verbinclude api-cursor-create-for-limit-return-cont2 +/// +/// Do not do this: +/// +/// @verbinclude api-cursor-create-for-limit-return-cont3 /// /// @section HttpCursorHttp Accessing Cursors via Http ////////////////////////////////////////////////////// diff --git a/UnitTests/HttpInterface/api-cursor-spec.rb b/UnitTests/HttpInterface/api-cursor-spec.rb new file mode 100644 index 0000000000..7d639c1933 --- /dev/null +++ b/UnitTests/HttpInterface/api-cursor-spec.rb @@ -0,0 +1,226 @@ +# coding: utf-8 + +require 'rspec' +require './avocadodb.rb' + +describe AvocadoDB do + api = "/_api/cursor" + prefix = "api-cursor" + + context "dealing with cursors:" do + +################################################################################ +## error handling +################################################################################ + + context "error handling:" do + it "returns an errror if body is missing" do + cmd = api + doc = AvocadoDB.log_post("#{prefix}-missing-body", cmd) + + doc.code.should eq(400) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(true) + doc.parsed_response['code'].should eq(400) + doc.parsed_response['errorNum'].should eq(1503) + end + + it "returns an errror if collection is unknown" do + cmd = api + body = "{ \"query\" : \"FOR u IN unknowncollection LIMIT 2 RETURN u.n\", \"count\" : true, \"bindVars\" : {}, \"batchSize\" : 2 }" + doc = AvocadoDB.log_post("#{prefix}-unknown-collection", cmd, :body => body) + + doc.code.should eq(400) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(true) + doc.parsed_response['code'].should eq(400) + doc.parsed_response['errorNum'].should eq(1510) + end + + it "returns an errror if cursor identifier is missing" do + cmd = api + doc = AvocadoDB.log_put("#{prefix}-missing-cursor-identifier", cmd) + + doc.code.should eq(400) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(true) + doc.parsed_response['code'].should eq(400) + doc.parsed_response['errorNum'].should eq(400) + end + + it "returns an errror if cursor identifier is invalid" do + cmd = api + "/123456" + doc = AvocadoDB.log_put("#{prefix}-invalid-cursor-identifier", cmd) + + doc.code.should eq(400) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(true) + doc.parsed_response['code'].should eq(400) + doc.parsed_response['errorNum'].should eq(1600) + end + + end + +################################################################################ +## create and using cursors +################################################################################ + + context "handling a cursor:" do + before do + @cn = "users" + AvocadoDB.drop_collection(@cn) + @cid = AvocadoDB.create_collection(@cn, false) + + (0...10).each{|i| + AvocadoDB.post("/document?collection=#{@cid}", :body => "{ \"n\" : #{i} }") + } + end + + after do + AvocadoDB.drop_collection(@cn) + end + + it "creates a cursor single run" do + cmd = api + body = "{ \"query\" : \"FOR u IN #{@cn} LIMIT 2 RETURN u.n\", \"count\" : true, \"bindVars\" : {}, \"batchSize\" : 2 }" + doc = AvocadoDB.log_post("#{prefix}-create-for-limit-return-single", cmd, :body => body) + + doc.code.should eq(201) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(false) + doc.parsed_response['code'].should eq(201) + doc.parsed_response['hasMore'].should eq(false) + doc.parsed_response['count'].should eq(2) + doc.parsed_response['result'].length.should eq(2) + end + + it "creates a cursor single run, large batch size" do + cmd = api + body = "{ \"query\" : \"FOR u IN #{@cn} LIMIT 2 RETURN u.n\", \"count\" : true, \"batchSize\" : 5 }" + doc = AvocadoDB.log_post("#{prefix}-create-for-limit-return-single-larger", cmd, :body => body) + + doc.code.should eq(201) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(false) + doc.parsed_response['code'].should eq(201) + doc.parsed_response['hasMore'].should eq(false) + doc.parsed_response['count'].should eq(2) + doc.parsed_response['result'].length.should eq(2) + end + + it "creates a cursor" do + cmd = api + body = "{ \"query\" : \"FOR u IN #{@cn} LIMIT 5 RETURN u.n\", \"count\" : true, \"batchSize\" : 2 }" + doc = AvocadoDB.log_post("#{prefix}-create-for-limit-return", cmd, :body => body) + + doc.code.should eq(201) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(false) + doc.parsed_response['code'].should eq(201) + doc.parsed_response['id'].should be_kind_of(Integer) + doc.parsed_response['hasMore'].should eq(true) + doc.parsed_response['count'].should eq(5) + doc.parsed_response['result'].length.should eq(2) + + id = doc.parsed_response['id'] + + cmd = api + "/#{id}" + doc = AvocadoDB.log_put("#{prefix}-create-for-limit-return-cont", cmd) + + doc.code.should eq(200) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(false) + doc.parsed_response['code'].should eq(200) + doc.parsed_response['hasMore'].should eq(true) + doc.parsed_response['count'].should eq(5) + doc.parsed_response['result'].length.should eq(2) + + cmd = api + "/#{id}" + doc = AvocadoDB.log_put("#{prefix}-create-for-limit-return-cont2", cmd) + + doc.code.should eq(200) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(false) + doc.parsed_response['code'].should eq(200) + doc.parsed_response['hasMore'].should eq(false) + doc.parsed_response['count'].should eq(5) + doc.parsed_response['result'].length.should eq(1) + + cmd = api + "/#{id}" + doc = AvocadoDB.log_put("#{prefix}-create-for-limit-return-cont3", cmd) + + doc.code.should eq(400) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(true) + doc.parsed_response['errorNum'].should eq(1600) + doc.parsed_response['code'].should eq(400) + end + + it "deleting a cursor" do + cmd = api + body = "{ \"query\" : \"FOR u IN #{@cn} LIMIT 5 RETURN u.n\", \"count\" : true, \"batchSize\" : 2 }" + doc = AvocadoDB.post(cmd, :body => body) + + doc.code.should eq(201) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(false) + doc.parsed_response['code'].should eq(201) + doc.parsed_response['id'].should be_kind_of(Integer) + doc.parsed_response['hasMore'].should eq(true) + doc.parsed_response['count'].should eq(5) + doc.parsed_response['result'].length.should eq(2) + + id = doc.parsed_response['id'] + + cmd = api + "/#{id}" + doc = AvocadoDB.log_delete("#{prefix}-delete", cmd) + + doc.code.should eq(202) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(false) + doc.parsed_response['code'].should eq(202) + end + end + +################################################################################ +## checking a query +################################################################################ + + context "checking a query:" do + before do + @cn = "users" + AvocadoDB.drop_collection(@cn) + @cid = AvocadoDB.create_collection(@cn, false) + end + + after do + AvocadoDB.drop_collection(@cn) + end + + it "valid query" do + cmd = "/_api/query" + body = "{ \"query\" : \"FOR u IN #{@cn} FILTER u.name == @name LIMIT 2 RETURN u.n\" }" + doc = AvocadoDB.log_post("api-query-valid", cmd, :body => body) + + doc.code.should eq(200) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(false) + doc.parsed_response['code'].should eq(200) + doc.parsed_response['bindVars'].should eq(["name"]) + end + + it "invalid query" do + cmd = "/_api/query" + body = "{ \"query\" : \"FOR u IN #{@cn} FILTER u.name = @name LIMIT 2 RETURN u.n\" }" + doc = AvocadoDB.log_post("api-query-invalid", cmd, :body => body) + + doc.code.should eq(400) + doc.headers['content-type'].should eq("application/json") + doc.parsed_response['error'].should eq(true) + doc.parsed_response['code'].should eq(400) + doc.parsed_response['errorNum'].should eq(1501) + end + end + + end +end diff --git a/UnitTests/HttpInterface/api-index-spec.rb b/UnitTests/HttpInterface/api-index-spec.rb index 4f0e2586d6..5be5251f26 100644 --- a/UnitTests/HttpInterface/api-index-spec.rb +++ b/UnitTests/HttpInterface/api-index-spec.rb @@ -60,7 +60,7 @@ describe AvocadoDB do after do AvocadoDB.drop_collection(@cn) - end + end it "returns either 201 for new or 200 for old indexes" do cmd = api + "?collection=#{@cid}" diff --git a/UnitTests/HttpInterface/run-tests b/UnitTests/HttpInterface/run-tests index 06ff537858..d9ae6d139e 100755 --- a/UnitTests/HttpInterface/run-tests +++ b/UnitTests/HttpInterface/run-tests @@ -8,4 +8,5 @@ rspec --format d \ rest-read-document-spec.rb \ rest-update-document-spec.rb \ rest-delete-document-spec.rb \ - rest-edge-spec.rb + rest-edge-spec.rb \ + api-cursor-spec.rb diff --git a/V8/v8-vocbase.cpp b/V8/v8-vocbase.cpp index 777f3114d2..24f99cd1ab 100644 --- a/V8/v8-vocbase.cpp +++ b/V8/v8-vocbase.cpp @@ -1081,7 +1081,7 @@ static v8::Handle CreateVocBase (v8::Arguments const& argv, bool edge TRI_vocbase_col_t const* collection = TRI_CreateCollectionVocBase(vocbase, ¶meter); - if (collection == NULL) { + if (collection == 0) { return scope.Close(v8::ThrowException(CreateErrorObject(TRI_errno(), "cannot create collection"))); } @@ -2141,82 +2141,6 @@ static v8::Handle JS_WithinQuery (v8::Arguments const& argv) { return scope.Close(result); } -//////////////////////////////////////////////////////////////////////////////// -/// @brief generates a general cursor from a list -//////////////////////////////////////////////////////////////////////////////// - -static v8::Handle JS_CreateCursor (v8::Arguments const& argv) { - v8::HandleScope scope; - v8::TryCatch tryCatch; - - TRI_vocbase_t* vocbase = GetContextVocBase(); - - if (vocbase == 0) { - return scope.Close(v8::ThrowException(v8::String::New("corrupted vocbase"))); - } - - if (argv.Length() < 1) { - return scope.Close(v8::ThrowException(CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, - "usage: GENERAL_CURSOR(, , )"))); - } - - if (! argv[0]->IsArray()) { - return scope.Close(v8::ThrowException(CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, - " must be a list"))); - } - - // extract objects - v8::Handle array = v8::Handle::Cast(argv[0]); - TRI_json_t* json = TRI_JsonObject(array); - - if (json == 0) { - return scope.Close(v8::ThrowException(CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, - "cannot convert to JSON"))); - } - - // return number of total records in cursor? - bool doCount = false; - - if (argv.Length() >= 2) { - doCount = TRI_ObjectToBoolean(argv[1]); - } - - // maximum number of results to return at once - uint32_t batchSize = 1000; - - if (argv.Length() >= 3) { - double maxValue = TRI_ObjectToDouble(argv[2]); - - if (maxValue >= 1.0) { - batchSize = (uint32_t) maxValue; - } - } - - // create a cursor - TRI_general_cursor_t* cursor = 0; - TRI_general_cursor_result_t* cursorResult = TRI_CreateResultAql(json); - - if (cursorResult != 0) { - cursor = TRI_CreateGeneralCursor(cursorResult, doCount, batchSize); - - if (cursor == 0) { - TRI_Free(TRI_UNKNOWN_MEM_ZONE, cursorResult); - TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); - } - } - else { - TRI_Free(TRI_UNKNOWN_MEM_ZONE, cursorResult); - TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); - } - - if (cursor == 0) { - return scope.Close(v8::ThrowException(v8::String::New("cannot create cursor"))); - } - - TRI_StoreShadowData(vocbase->_cursors, (const void* const) cursor); - return scope.Close(WrapGeneralCursor(cursor)); -} - //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// @@ -2225,6 +2149,10 @@ static v8::Handle JS_CreateCursor (v8::Arguments const& argv) { // --SECTION-- GENERAL CURSORS // ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// --SECTION-- private functions +// ----------------------------------------------------------------------------- + //////////////////////////////////////////////////////////////////////////////// /// @addtogroup VocBase /// @{ @@ -2300,30 +2228,129 @@ static void* UnwrapGeneralCursor (v8::Handle cursorObject) { return TRI_UnwrapClass(cursorObject, WRP_GENERAL_CURSOR_TYPE); } +//////////////////////////////////////////////////////////////////////////////// +/// @} +//////////////////////////////////////////////////////////////////////////////// + +// ----------------------------------------------------------------------------- +// --SECTION-- javascript functions +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @addtogroup VocBase +/// @{ +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +/// @brief generates a general cursor from a list +//////////////////////////////////////////////////////////////////////////////// + +static v8::Handle JS_CreateCursor (v8::Arguments const& argv) { + v8::HandleScope scope; + + TRI_vocbase_t* vocbase = GetContextVocBase(); + + if (vocbase == 0) { + return scope.Close(v8::ThrowException(v8::String::New("corrupted vocbase"))); + } + + if (argv.Length() < 1) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + "usage: GENERAL_CURSOR(, , )"))); + } + + if (! argv[0]->IsArray()) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + " must be a list"))); + } + + // extract objects + v8::Handle array = v8::Handle::Cast(argv[0]); + TRI_json_t* json = TRI_JsonObject(array); + + if (json == 0) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + "cannot convert to JSON"))); + } + + // return number of total records in cursor? + bool doCount = false; + + if (argv.Length() >= 2) { + doCount = TRI_ObjectToBoolean(argv[1]); + } + + // maximum number of results to return at once + uint32_t batchSize = 1000; + + if (argv.Length() >= 3) { + double maxValue = TRI_ObjectToDouble(argv[2]); + + if (maxValue >= 1.0) { + batchSize = (uint32_t) maxValue; + } + } + + // create a cursor + TRI_general_cursor_t* cursor = 0; + TRI_general_cursor_result_t* cursorResult = TRI_CreateResultAql(json); + + if (cursorResult != 0) { + cursor = TRI_CreateGeneralCursor(cursorResult, doCount, batchSize); + + if (cursor == 0) { + TRI_Free(TRI_UNKNOWN_MEM_ZONE, cursorResult); + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); + } + } + else { + TRI_Free(TRI_UNKNOWN_MEM_ZONE, cursorResult); + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); + } + + if (cursor == 0) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_INTERNAL, + "cannot create cursor"))); + } + + TRI_StoreShadowData(vocbase->_cursors, (const void* const) cursor); + return scope.Close(WrapGeneralCursor(cursor)); +} + //////////////////////////////////////////////////////////////////////////////// /// @brief destroys a general cursor //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_DisposeGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; - v8::TryCatch tryCatch; if (argv.Length() != 0) { - return scope.Close(v8::ThrowException(v8::String::New("usage: dispose()"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + "usage: dispose()"))); } TRI_vocbase_t* vocbase = GetContextVocBase(); + if (!vocbase) { - return scope.Close(v8::ThrowException(v8::String::New("corrupted vocbase"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_INTERNAL, + "corrupted vocbase"))); } - if (TRI_DeleteDataShadowData(vocbase->_cursors, UnwrapGeneralCursor(argv.Holder()))) { - if (!tryCatch.HasCaught()) { - return scope.Close(v8::True()); - } + bool found = TRI_DeleteDataShadowData(vocbase->_cursors, UnwrapGeneralCursor(argv.Holder())); + + if (found) { + return scope.Close(v8::True()); } - return scope.Close(v8::ThrowException(v8::String::New("corrupted or already disposed cursor"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_CURSOR_NOT_FOUND, + "disposed or unknown cursor"))); } //////////////////////////////////////////////////////////////////////////////// @@ -2332,23 +2359,30 @@ static v8::Handle JS_DisposeGeneralCursor (v8::Arguments const& argv) static v8::Handle JS_IdGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; - v8::TryCatch tryCatch; if (argv.Length() != 0) { - return scope.Close(v8::ThrowException(v8::String::New("usage: id()"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + "usage: id()"))); } TRI_vocbase_t* vocbase = GetContextVocBase(); - if (!vocbase) { - return scope.Close(v8::ThrowException(v8::String::New("corrupted vocbase"))); + + if (vocbase == 0) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_INTERNAL, + "corrupted vocbase"))); } TRI_shadow_id id = TRI_GetIdDataShadowData(vocbase->_cursors, UnwrapGeneralCursor(argv.Holder())); - if (id && !tryCatch.HasCaught()) { + + if (id != 0) { return scope.Close(v8::Number::New((double) id)); } - return scope.Close(v8::ThrowException(v8::String::New("corrupted or already disposed cursor"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_CURSOR_NOT_FOUND, + "disposed or unknown cursor"))); } //////////////////////////////////////////////////////////////////////////////// @@ -2357,15 +2391,19 @@ static v8::Handle JS_IdGeneralCursor (v8::Arguments const& argv) { static v8::Handle JS_CountGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; - v8::TryCatch tryCatch; if (argv.Length() != 0) { - return scope.Close(v8::ThrowException(v8::String::New("usage: count()"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + "usage: count()"))); } TRI_vocbase_t* vocbase = GetContextVocBase(); - if (!vocbase) { - return scope.Close(v8::ThrowException(v8::String::New("corrupted vocbase"))); + + if (vocbase == 0) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_INTERNAL, + "corrupted vocbase"))); } TRI_general_cursor_t* cursor; @@ -2378,7 +2416,9 @@ static v8::Handle JS_CountGeneralCursor (v8::Arguments const& argv) { return scope.Close(v8::Number::New(length)); } - return scope.Close(v8::ThrowException(v8::String::New("corrupted or already freed cursor"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_CURSOR_NOT_FOUND, + "disposed or unknown cursor"))); } //////////////////////////////////////////////////////////////////////////////// @@ -2387,15 +2427,19 @@ static v8::Handle JS_CountGeneralCursor (v8::Arguments const& argv) { static v8::Handle JS_NextGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; - v8::TryCatch tryCatch; if (argv.Length() != 0) { - return scope.Close(v8::ThrowException(v8::String::New("usage: next()"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + "usage: count()"))); } TRI_vocbase_t* vocbase = GetContextVocBase(); - if (!vocbase) { - return scope.Close(v8::ThrowException(v8::String::New("corrupted vocbase"))); + + if (vocbase == 0) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_INTERNAL, + "corrupted vocbase"))); } bool result = false; @@ -2416,9 +2460,12 @@ static v8::Handle JS_NextGeneralCursor (v8::Arguments const& argv) { // exceptions must be caught in the following part because we hold an exclusive // lock that might otherwise not be freed + v8::TryCatch tryCatch; + try { TRI_general_cursor_row_t row = cursor->next(cursor); - if (!row) { + + if (row == 0) { value = v8::Undefined(); } else { @@ -2433,12 +2480,18 @@ static v8::Handle JS_NextGeneralCursor (v8::Arguments const& argv) { TRI_EndUsageDataShadowData(vocbase->_cursors, cursor); - if (result && !tryCatch.HasCaught()) { + if (result && ! tryCatch.HasCaught()) { return scope.Close(value); } + + if (tryCatch.HasCaught()) { + return scope.Close(v8::ThrowException(tryCatch.Exception())); + } } - return scope.Close(v8::ThrowException(v8::String::New("corrupted or already freed cursor"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_CURSOR_NOT_FOUND, + "disposed or unknown cursor"))); } //////////////////////////////////////////////////////////////////////////////// @@ -2447,23 +2500,30 @@ static v8::Handle JS_NextGeneralCursor (v8::Arguments const& argv) { static v8::Handle JS_PersistGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; - v8::TryCatch tryCatch; if (argv.Length() != 0) { - return scope.Close(v8::ThrowException(v8::String::New("usage: persist()"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + "usage: persist()"))); } TRI_vocbase_t* vocbase = GetContextVocBase(); - if (!vocbase) { - return scope.Close(v8::ThrowException(v8::String::New("corrupted vocbase"))); + + if (vocbase == 0) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_INTERNAL, + "corrupted vocbase"))); } bool result = TRI_PersistDataShadowData(vocbase->_cursors, UnwrapGeneralCursor(argv.Holder())); - if (result && !tryCatch.HasCaught()) { + + if (result) { return scope.Close(v8::True()); } - return scope.Close(v8::ThrowException(v8::String::New("corrupted or already freed cursor"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_CURSOR_NOT_FOUND, + "disposed or unknown cursor"))); } //////////////////////////////////////////////////////////////////////////////// @@ -2475,15 +2535,19 @@ static v8::Handle JS_PersistGeneralCursor (v8::Arguments const& argv) static v8::Handle JS_GetRowsGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; - v8::TryCatch tryCatch; if (argv.Length() != 0) { - return scope.Close(v8::ThrowException(v8::String::New("usage: getRows()"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + "usage: getRows()"))); } TRI_vocbase_t* vocbase = GetContextVocBase(); - if (!vocbase) { - return scope.Close(v8::ThrowException(v8::String::New("corrupted vocbase"))); + + if (vocbase == 0) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_INTERNAL, + "corrupted vocbase"))); } bool result = false; @@ -2497,6 +2561,8 @@ static v8::Handle JS_GetRowsGeneralCursor (v8::Arguments const& argv) // exceptions must be caught in the following part because we hold an exclusive // lock that might otherwise not be freed + v8::TryCatch tryCatch; + try { uint32_t max = (uint32_t) cursor->getBatchSize(cursor); @@ -2517,12 +2583,18 @@ static v8::Handle JS_GetRowsGeneralCursor (v8::Arguments const& argv) TRI_EndUsageDataShadowData(vocbase->_cursors, cursor); - if (result && !tryCatch.HasCaught()) { + if (result && ! tryCatch.HasCaught()) { return scope.Close(rows); } + + if (tryCatch.HasCaught()) { + return scope.Close(v8::ThrowException(tryCatch.Exception())); + } } - return scope.Close(v8::ThrowException(v8::String::New("corrupted or already freed cursor"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_CURSOR_NOT_FOUND, + "disposed or unknown cursor"))); } //////////////////////////////////////////////////////////////////////////////// @@ -2531,15 +2603,19 @@ static v8::Handle JS_GetRowsGeneralCursor (v8::Arguments const& argv) static v8::Handle JS_GetBatchSizeGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; - v8::TryCatch tryCatch; if (argv.Length() != 0) { - return scope.Close(v8::ThrowException(v8::String::New("usage: getBatchSize()"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + "usage: getBatchSize()"))); } TRI_vocbase_t* vocbase = GetContextVocBase(); - if (!vocbase) { - return scope.Close(v8::ThrowException(v8::String::New("corrupted vocbase"))); + + if (vocbase == 0) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_INTERNAL, + "corrupted vocbase"))); } TRI_general_cursor_t* cursor; @@ -2553,7 +2629,9 @@ static v8::Handle JS_GetBatchSizeGeneralCursor (v8::Arguments const& return scope.Close(v8::Number::New(max)); } - return scope.Close(v8::ThrowException(v8::String::New("corrupted or already freed cursor"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_CURSOR_NOT_FOUND, + "disposed or unknown cursor"))); } //////////////////////////////////////////////////////////////////////////////// @@ -2562,17 +2640,21 @@ static v8::Handle JS_GetBatchSizeGeneralCursor (v8::Arguments const& static v8::Handle JS_HasCountGeneralCursor (v8::Arguments const& argv) { v8::HandleScope scope; - v8::TryCatch tryCatch; if (argv.Length() != 0) { - return scope.Close(v8::ThrowException(v8::String::New("usage: hasCount()"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + "usage: hasCount()"))); } TRI_vocbase_t* vocbase = GetContextVocBase(); - if (!vocbase) { - return scope.Close(v8::ThrowException(v8::String::New("corrupted vocbase"))); - } + if (vocbase == 0) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_INTERNAL, + "corrupted vocbase"))); + } + TRI_general_cursor_t* cursor; cursor = (TRI_general_cursor_t*) TRI_BeginUsageDataShadowData(vocbase->_cursors, UnwrapGeneralCursor(argv.Holder())); @@ -2584,7 +2666,9 @@ static v8::Handle JS_HasCountGeneralCursor (v8::Arguments const& argv return scope.Close(hasCount ? v8::True() : v8::False()); } - return scope.Close(v8::ThrowException(v8::String::New("corrupted or already freed cursor"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_CURSOR_NOT_FOUND, + "disposed or unknown cursor"))); } //////////////////////////////////////////////////////////////////////////////// @@ -2596,12 +2680,17 @@ static v8::Handle JS_HasNextGeneralCursor (v8::Arguments const& argv) v8::TryCatch tryCatch; if (argv.Length() != 0) { - return scope.Close(v8::ThrowException(v8::String::New("usage: hasNext()"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + "usage: hasNext()"))); } TRI_vocbase_t* vocbase = GetContextVocBase(); - if (!vocbase) { - return scope.Close(v8::ThrowException(v8::String::New("corrupted vocbase"))); + + if (vocbase == 0) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_INTERNAL, + "corrupted vocbase"))); } TRI_general_cursor_t* cursor; @@ -2617,7 +2706,9 @@ static v8::Handle JS_HasNextGeneralCursor (v8::Arguments const& argv) return scope.Close(hasNext ? v8::True() : v8::False()); } - return scope.Close(v8::ThrowException(v8::String::New("corrupted or already freed cursor"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_CURSOR_NOT_FOUND, + "disposed or unknown cursor"))); } //////////////////////////////////////////////////////////////////////////////// @@ -2628,27 +2719,39 @@ static v8::Handle JS_Cursor (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() != 1) { - return scope.Close(v8::ThrowException(v8::String::New("usage: CURSOR()"))); + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + "usage: CURSOR()"))); } TRI_vocbase_t* vocbase = GetContextVocBase(); - if (!vocbase) { - return scope.Close(v8::ThrowException(v8::String::New("corrupted vocbase"))); + + if (vocbase == 0) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_INTERNAL, + "corrupted vocbase"))); } // get the id v8::Handle idArg = argv[0]->ToString(); - if (!idArg->IsString()) { - return scope.Close(v8::ThrowException(v8::String::New("expecting string for "))); + + if (! idArg->IsString()) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_ILLEGAL_OPTION, + "expecting a string for )"))); } + string idString = TRI_ObjectToString(idArg); uint64_t id = TRI_UInt64String(idString.c_str()); TRI_general_cursor_t* cursor; cursor = (TRI_general_cursor_t*) TRI_BeginUsageIdShadowData(vocbase->_cursors, id); - if (!cursor) { - return scope.Close(v8::ThrowException(v8::String::New("corrupted or already freed cursor"))); + + if (cursor == 0) { + return scope.Close(v8::ThrowException( + CreateErrorObject(TRI_ERROR_CURSOR_NOT_FOUND, + "disposed or unknown cursor"))); } return scope.Close(WrapGeneralCursor(cursor)); @@ -2727,7 +2830,7 @@ static v8::Handle JS_RunAhuacatl (v8::Arguments const& argv) { } // bind parameters - TRI_json_t* parameters = NULL; + TRI_json_t* parameters = 0; if (argv.Length() > 1) { parameters = TRI_JsonObject(argv[1]); } @@ -2976,7 +3079,7 @@ static v8::Handle JS_WhereHashConstAql (const v8::Arguments& argv) { for (int j = 1; j < argv.Length(); ++j) { v8::Handle parameter = argv[j]; TRI_json_t* jsonParameter = TRI_JsonObject(parameter); - if (jsonParameter == NULL) { // NOT the null json value! + if (jsonParameter == 0) { // NOT the null json value! return scope.Close(v8::ThrowException(v8::String::New("type value not currently supported for hash index"))); } TRI_PushBackListJson(TRI_UNKNOWN_MEM_ZONE, parameterList, jsonParameter); @@ -3052,7 +3155,7 @@ static v8::Handle JS_WherePQConstAql (const v8::Arguments& argv) { if (argv.Length() == 1) { TRI_json_t* jsonParameter = TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, 1); - if (jsonParameter == NULL) { // failure of some sort + if (jsonParameter == 0) { // failure of some sort return scope.Close(v8::ThrowException(v8::String::New("internal error in JS_WherePQConstAql"))); } TRI_PushBackListJson(TRI_UNKNOWN_MEM_ZONE, parameterList, jsonParameter); @@ -3062,7 +3165,7 @@ static v8::Handle JS_WherePQConstAql (const v8::Arguments& argv) { for (int j = 1; j < argv.Length(); ++j) { v8::Handle parameter = argv[j]; TRI_json_t* jsonParameter = TRI_JsonObject(parameter); - if (jsonParameter == NULL) { // NOT the null json value! + if (jsonParameter == 0) { // NOT the null json value! return scope.Close(v8::ThrowException(v8::String::New("type value not currently supported for priority queue index"))); } TRI_PushBackListJson(TRI_UNKNOWN_MEM_ZONE, parameterList, jsonParameter); @@ -3131,7 +3234,7 @@ static v8::Handle JS_WhereSkiplistConstAql (const v8::Arguments& argv // .......................................................................... if (haveOperators) { if (argv.Length() > 2) { - TRI_sl_operator_t* leftOp = NULL; + TRI_sl_operator_t* leftOp = 0; v8::Handle leftParameter = argv[1]; v8::Handle leftObject = leftParameter->ToObject(); leftOp = TRI_UnwrapClass(leftObject, WRP_SL_OPERATOR_TYPE); @@ -3147,7 +3250,7 @@ static v8::Handle JS_WhereSkiplistConstAql (const v8::Arguments& argv TRI_FreeSLOperator(leftOp); return scope.Close(v8::ThrowException(v8::String::New("either logical/relational operators or constants allowed, but not both"))); } - TRI_sl_operator_t* tempAndOperator = CreateSLOperator(TRI_SL_AND_OPERATOR,leftOp, rightOp, NULL, NULL, NULL, 2, NULL); + TRI_sl_operator_t* tempAndOperator = CreateSLOperator(TRI_SL_AND_OPERATOR,leftOp, rightOp, 0, 0, 0, 2, 0); leftOp = tempAndOperator; } where = TRI_CreateQueryWhereSkiplistConstant(iid, leftOp); @@ -3176,17 +3279,17 @@ static v8::Handle JS_WhereSkiplistConstAql (const v8::Arguments& argv for (int j = 1; j < argv.Length(); ++j) { v8::Handle parameter = argv[j]; TRI_json_t* jsonParameter = TRI_JsonObject(parameter); - if (jsonParameter == NULL) { // NOT the null json value! + if (jsonParameter == 0) { // NOT the null json value! return scope.Close(v8::ThrowException(v8::String::New("type value not currently supported for skiplist index"))); } TRI_PushBackListJson(TRI_UNKNOWN_MEM_ZONE, parameterList, jsonParameter); } - TRI_sl_operator_t* eqOperator = CreateSLOperator(TRI_SL_EQ_OPERATOR,NULL, NULL, parameterList, NULL, NULL, - parameterList->_value._objects._length, NULL); + TRI_sl_operator_t* eqOperator = CreateSLOperator(TRI_SL_EQ_OPERATOR,0, 0, parameterList, 0, 0, + parameterList->_value._objects._length, 0); where = TRI_CreateQueryWhereSkiplistConstant(iid, eqOperator); } - if (where == NULL) { + if (where == 0) { return scope.Close(v8::ThrowException(v8::String::New("Error detected in where statement"))); } @@ -3352,7 +3455,7 @@ static v8::Handle JS_PQSelectAql (v8::Arguments const& argv) { // ........................................................................... TRI_qry_where_priorityqueue_const_t* pqWhere = (TRI_qry_where_priorityqueue_const_t*)(where); TRI_priorityqueue_index_t* idx = (TRI_priorityqueue_index_t*)(TRI_LookupIndex(collection->_collection, pqWhere->_iid)); - if (idx == NULL) { + if (idx == 0) { ReleaseCollection(collection); return scope.Close(v8::ThrowException(v8::String::New("invalid index in where statement"))); } @@ -3460,7 +3563,7 @@ static v8::Handle JS_SkiplistSelectAql (v8::Arguments const& argv) { // ........................................................................... TRI_qry_where_skiplist_const_t* slWhere = (TRI_qry_where_skiplist_const_t*)(where); TRI_skiplist_index_t* idx = (TRI_skiplist_index_t*)(TRI_LookupIndex(collection->_collection, slWhere->_iid)); - if (idx == NULL) { + if (idx == 0) { ReleaseCollection(collection); return scope.Close(v8::ThrowException(v8::String::New("invalid index in where statement"))); } @@ -3539,17 +3642,17 @@ static v8::Handle WrapSLOperator (TRI_sl_operator_t* slOperator) { static TRI_json_t* parametersToJson(v8::Arguments const& argv, int startPos, int endPos) { TRI_json_t* result = TRI_CreateListJson(TRI_UNKNOWN_MEM_ZONE); - if (result == NULL) { + if (result == 0) { v8::ThrowException(v8::String::New("out of memory")); - return NULL; + return 0; } for (int j = startPos; j < endPos; ++j) { v8::Handle parameter = argv[j]; TRI_json_t* jsonParameter = TRI_JsonObject(parameter); - if (jsonParameter == NULL) { // NOT the null json value! + if (jsonParameter == 0) { // NOT the null json value! v8::ThrowException(v8::String::New("type value not currently supported for skiplist index")); - return NULL; + return 0; } TRI_PushBackListJson(TRI_UNKNOWN_MEM_ZONE, result, jsonParameter); } @@ -3567,7 +3670,7 @@ static v8::Handle JS_Operator_AND (v8::Arguments const& argv) { // ........................................................................... // We expect a list of constant values in the order in which the skip list - // index has been defined. An unknown value can have a NULL + // index has been defined. An unknown value can have a 0 // ........................................................................... if (argv.Length() != 2) { return scope.Close(v8::ThrowException(v8::String::New("usage: AND(, )"))); @@ -3603,7 +3706,7 @@ static v8::Handle JS_Operator_AND (v8::Arguments const& argv) { // ........................................................................... logicalOperator = (TRI_sl_logical_operator_t*)(CreateSLOperator(TRI_SL_AND_OPERATOR, CopySLOperator(leftOperator), - CopySLOperator(rightOperator),NULL, NULL, NULL, 2, NULL)); + CopySLOperator(rightOperator),0, 0, 0, 2, 0)); // ........................................................................... // Wrap it up for later use and return. // ........................................................................... @@ -3617,7 +3720,7 @@ static v8::Handle JS_Operator_OR (v8::Arguments const& argv) { // ........................................................................... // We expect a list of constant values in the order in which the skip list - // index has been defined. An unknown value can have a NULL + // index has been defined. An unknown value can have a 0 // ........................................................................... if (argv.Length() != 2) { return scope.Close(v8::ThrowException(v8::String::New("usage: OR(, )"))); @@ -3654,7 +3757,7 @@ static v8::Handle JS_Operator_OR (v8::Arguments const& argv) { // ........................................................................... logicalOperator = (TRI_sl_logical_operator_t*)(CreateSLOperator(TRI_SL_OR_OPERATOR, CopySLOperator(leftOperator), - CopySLOperator(rightOperator),NULL, NULL, NULL, 2, NULL)); + CopySLOperator(rightOperator),0, 0, 0, 2, 0)); return scope.Close(WrapSLOperator(&(logicalOperator->_base))); } @@ -3667,7 +3770,7 @@ static v8::Handle JS_Operator_EQ (v8::Arguments const& argv) { // ........................................................................... // We expect a list of constant values in the order in which the skip list - // index has been defined. An unknown value can have a NULL + // index has been defined. An unknown value can have a 0 // ........................................................................... if (argv.Length() < 1) { return scope.Close(v8::ThrowException(v8::String::New("usage: EQ(, ,..., )"))); @@ -3681,15 +3784,15 @@ static v8::Handle JS_Operator_EQ (v8::Arguments const& argv) { // ........................................................................... parameters = parametersToJson(argv,0,argv.Length()); - if (parameters == NULL) { + if (parameters == 0) { return scope.Close(v8::ThrowException(v8::String::New("unsupported type in EQ(...) parameter list"))); } // ........................................................................... // Allocate the storage for a relation (EQ) operator and assign it that type // ........................................................................... - relationOperator = (TRI_sl_relation_operator_t*)(CreateSLOperator(TRI_SL_EQ_OPERATOR, NULL, NULL, parameters, NULL, NULL, - parameters->_value._objects._length, NULL)); + relationOperator = (TRI_sl_relation_operator_t*)(CreateSLOperator(TRI_SL_EQ_OPERATOR, 0, 0, parameters, 0, 0, + parameters->_value._objects._length, 0)); return scope.Close(WrapSLOperator(&(relationOperator->_base))); } @@ -3701,7 +3804,7 @@ static v8::Handle JS_Operator_GE (v8::Arguments const& argv) { // ........................................................................... // We expect a list of constant values in the order in which the skip list - // index has been defined. An unknown value can have a NULL + // index has been defined. An unknown value can have a 0 // ........................................................................... if (argv.Length() < 1) { return scope.Close(v8::ThrowException(v8::String::New("usage: GE(, ,..., )"))); @@ -3715,15 +3818,15 @@ static v8::Handle JS_Operator_GE (v8::Arguments const& argv) { // ........................................................................... parameters = parametersToJson(argv,0,argv.Length()); - if (parameters == NULL) { + if (parameters == 0) { return scope.Close(v8::ThrowException(v8::String::New("unsupported type in GE(...) parameter list"))); } // ........................................................................... // Allocate the storage for a relation (GE) operator and assign it that type // ........................................................................... - relationOperator = (TRI_sl_relation_operator_t*)( CreateSLOperator(TRI_SL_GE_OPERATOR, NULL, NULL, parameters, NULL, NULL, - parameters->_value._objects._length, NULL) ); + relationOperator = (TRI_sl_relation_operator_t*)( CreateSLOperator(TRI_SL_GE_OPERATOR, 0, 0, parameters, 0, 0, + parameters->_value._objects._length, 0) ); return scope.Close(WrapSLOperator(&(relationOperator->_base))); } @@ -3736,7 +3839,7 @@ static v8::Handle JS_Operator_GT (v8::Arguments const& argv) { // ........................................................................... // We expect a list of constant values in the order in which the skip list - // index has been defined. An unknown value can have a NULL + // index has been defined. An unknown value can have a 0 // ........................................................................... if (argv.Length() < 1) { return scope.Close(v8::ThrowException(v8::String::New("usage: GT(, ,..., )"))); @@ -3750,15 +3853,15 @@ static v8::Handle JS_Operator_GT (v8::Arguments const& argv) { // ........................................................................... parameters = parametersToJson(argv,0,argv.Length()); - if (parameters == NULL) { + if (parameters == 0) { return scope.Close(v8::ThrowException(v8::String::New("unsupported type in GT(...) parameter list"))); } // ........................................................................... // Allocate the storage for a relation (GT) operator and assign it that type // ........................................................................... - relationOperator = (TRI_sl_relation_operator_t*)( CreateSLOperator(TRI_SL_GT_OPERATOR, NULL, NULL, parameters, NULL, NULL, - parameters->_value._objects._length, NULL) ); + relationOperator = (TRI_sl_relation_operator_t*)( CreateSLOperator(TRI_SL_GT_OPERATOR, 0, 0, parameters, 0, 0, + parameters->_value._objects._length, 0) ); return scope.Close(WrapSLOperator(&(relationOperator->_base))); } @@ -3771,7 +3874,7 @@ static v8::Handle JS_Operator_LE (v8::Arguments const& argv) { // ........................................................................... // We expect a list of constant values in the order in which the skip list - // index has been defined. An unknown value can have a NULL + // index has been defined. An unknown value can have a 0 // ........................................................................... if (argv.Length() < 1) { return scope.Close(v8::ThrowException(v8::String::New("usage: LE(, ,..., )"))); @@ -3785,15 +3888,15 @@ static v8::Handle JS_Operator_LE (v8::Arguments const& argv) { // ........................................................................... parameters = parametersToJson(argv,0,argv.Length()); - if (parameters == NULL) { + if (parameters == 0) { return scope.Close(v8::ThrowException(v8::String::New("unsupported type in LE(...) parameter list"))); } // ........................................................................... // Allocate the storage for a relation (LE) operator and assign it that type // ........................................................................... - relationOperator = (TRI_sl_relation_operator_t*)( CreateSLOperator(TRI_SL_LE_OPERATOR, NULL, NULL,parameters, NULL, NULL, - parameters->_value._objects._length, NULL) ); + relationOperator = (TRI_sl_relation_operator_t*)( CreateSLOperator(TRI_SL_LE_OPERATOR, 0, 0,parameters, 0, 0, + parameters->_value._objects._length, 0) ); return scope.Close(WrapSLOperator(&(relationOperator->_base))); } @@ -3806,7 +3909,7 @@ static v8::Handle JS_Operator_LT (v8::Arguments const& argv) { // ........................................................................... // We expect a list of constant values in the order in which the skip list - // index has been defined. An unknown value can have a NULL + // index has been defined. An unknown value can have a 0 // ........................................................................... if (argv.Length() < 1) { return scope.Close(v8::ThrowException(v8::String::New("usage: LT(, ,..., )"))); @@ -3820,15 +3923,15 @@ static v8::Handle JS_Operator_LT (v8::Arguments const& argv) { // ........................................................................... parameters = parametersToJson(argv,0,argv.Length()); - if (parameters == NULL) { + if (parameters == 0) { return scope.Close(v8::ThrowException(v8::String::New("unsupported type in LT(...) parameter list"))); } // ........................................................................... // Allocate the storage for a relation (LT) operator and assign it that type // ........................................................................... - relationOperator = (TRI_sl_relation_operator_t*)( CreateSLOperator(TRI_SL_LT_OPERATOR, NULL,NULL,parameters, NULL, NULL, - parameters->_value._objects._length, NULL) ); + relationOperator = (TRI_sl_relation_operator_t*)( CreateSLOperator(TRI_SL_LT_OPERATOR, 0,0,parameters, 0, 0, + parameters->_value._objects._length, 0) ); return scope.Close(WrapSLOperator(&(relationOperator->_base))); } @@ -3904,15 +4007,15 @@ static v8::Handle JS_SelectAql (v8::Arguments const& argv) { TRI_AddPartSelectJoinX(join, JOIN_TYPE_PRIMARY, - NULL, + 0, (char*) name.c_str(), (char*) "alias", - NULL); + 0); // create the query TRI_query_t* query = TRI_CreateQuery(vocbase, select, - NULL, + 0, join, skip, limit); @@ -4383,7 +4486,7 @@ static v8::Handle JS_EnsurePriorityQueueIndexVocbaseCol (v8::Argument v8::String::Utf8Value argumentString(argument); char* cArgument = (char*) (TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, argumentString.length() + 1, false)); - if (cArgument == NULL) { + if (cArgument == 0) { errorString = "insuffient memory to complete ensurePQIndex(...) command"; ok = false; break; @@ -4445,7 +4548,7 @@ static v8::Handle JS_EnsurePriorityQueueIndexVocbaseCol (v8::Argument TRI_DestroyVector(&attributes); - if (idx == NULL) { + if (idx == 0) { ReleaseCollection(collection); return scope.Close(v8::String::New("Priority Queue index could not be created")); } @@ -5722,8 +5825,8 @@ static v8::Handle MapGetShapedJson (v8::Local name, TRI_shape_access_t* acc = TRI_ShapeAccessor(shaper, sid, pid); - if (acc == NULL || acc->_shape == NULL) { - if (acc != NULL) { + if (acc == 0 || acc->_shape == 0) { + if (acc != 0) { TRI_FreeShapeAccessor(acc); } @@ -5869,8 +5972,8 @@ static v8::Handle PropertyQueryShapedJson (v8::Local na TRI_shape_access_t* acc = TRI_ShapeAccessor(shaper, sid, pid); // key not found - if (acc == NULL || acc->_shape == NULL) { - if (acc != NULL) { + if (acc == 0 || acc->_shape == 0) { + if (acc != 0) { TRI_FreeShapeAccessor(acc); } diff --git a/js/actions/system/api-cursor.js b/js/actions/system/api-cursor.js index 003d390456..c2d447469d 100644 --- a/js/actions/system/api-cursor.js +++ b/js/actions/system/api-cursor.js @@ -121,7 +121,7 @@ function getCursorResult (cursor) { /// - @LIT{count}: the total number of result documents available (only /// available if the query was executed with the @LIT{count} attribute set. /// -/// - @LIT{id}: id of temporary cursor created on the server (optional, see below) +/// - @LIT{id}: id of temporary cursor created on the server (optional, see above) /// /// If the JSON representation is malformed or the query specification is /// missing from the request, the server will respond with @LIT{HTTP 400}. @@ -145,19 +145,15 @@ function getCursorResult (cursor) { /// /// @EXAMPLES /// -/// @verbinclude cursor +/// Executes a query and extract the result in a single go: +/// +/// @verbinclude api-cursor-create-for-limit-return-single /// /// Bad queries: /// -/// @verbinclude cursor4001 +/// @verbinclude api-cursor-missing-body /// -/// @verbinclude cursor4002 -/// -/// @verbinclude cursor404 -/// -/// Valid query: -/// -/// @verbinclude cursor201 +/// @verbinclude api-cursor-unknown-collection //////////////////////////////////////////////////////////////////////////////// function POST_api_cursor(req, res) { @@ -166,15 +162,24 @@ function POST_api_cursor(req, res) { return; } - try { - var json = JSON.parse(req.requestBody); - - if (!json || !(json instanceof Object)) { - actions.resultBad(req, res, actions.ERROR_QUERY_SPECIFICATION_INVALID, actions.getErrorMessage(actions.ERROR_QUERY_SPECIFICATION_INVALID)); - return; - } + var json; + try { + json = JSON.parse(req.requestBody || "{}") || {}; + } + catch (err) { + actions.resultBad(req, res, actions.ERROR_HTTP_CORRUPTED_JSON, err); + return; + } + + if (! json || ! (json instanceof Object)) { + actions.resultBad(req, res, actions.ERROR_QUERY_SPECIFICATION_INVALID); + return; + } + + try { var cursor; + if (json.query != undefined) { cursor = AHUACATL_RUN(json.query, json.bindVars, @@ -182,12 +187,12 @@ function POST_api_cursor(req, res) { (json.batchSize != undefined ? json.batchSize : 1000)); } else { - actions.resultBad(req, res, actions.ERROR_QUERY_SPECIFICATION_INVALID, actions.getErrorMessage(actions.ERROR_QUERY_SPECIFICATION_INVALID)); + actions.resultBad(req, res, actions.ERROR_QUERY_SPECIFICATION_INVALID); return; } + // error occurred if (cursor instanceof AvocadoError) { - // error occurred actions.resultBad(req, res, cursor.errorNum, cursor.errorMessage); return; } @@ -195,6 +200,7 @@ function POST_api_cursor(req, res) { // this might dispose or persist the cursor var result = getCursorResult(cursor); + // return result to the client for first batch actions.resultOk(req, res, actions.HTTP_CREATED, result); } catch (err) { @@ -221,19 +227,27 @@ function POST_api_cursor(req, res) { /// @LIT{false}, the client can stop. /// /// The server will respond with @LIT{HTTP 200} in case of success. If the -/// cursor id is ommitted or somehow invalid, the server will respond with -/// @LIT{HTTP 404}. +/// cursor identifier is ommitted or somehow invalid, the server will respond +/// with @LIT{HTTP 404}. /// /// @EXAMPLES /// -/// @verbinclude cursorputfail +/// Valid request for next batch: /// -/// @verbinclude cursorput3 +/// @verbinclude api-cursor-create-for-limit-return-cont +/// +/// Missing identifier +/// +/// @verbinclude api-cursor-missing-cursor-identifier +/// +/// Unknown identifier +/// +/// @verbinclude api-cursor-invalid-cursor-identifier //////////////////////////////////////////////////////////////////////////////// function PUT_api_cursor(req, res) { if (req.suffix.length != 1) { - actions.resultNotFound(req, res, actions.ERROR_HTTP_NOT_FOUND); + actions.resultBad(req, res, actions.ERROR_HTTP_BAD_PARAMETER); return; } @@ -242,7 +256,7 @@ function PUT_api_cursor(req, res) { var cursor = CURSOR(cursorId); if (!(cursor instanceof AvocadoCursor)) { - actions.resultBad(req, res, actions.ERROR_CURSOR_NOT_FOUND, actions.getErrorMessage(actions.ERROR_CURSOR_NOT_FOUND)); + actions.resultBad(req, res, actions.ERROR_CURSOR_NOT_FOUND); return; } @@ -278,12 +292,12 @@ function PUT_api_cursor(req, res) { /// /// @EXAMPLES /// -/// @verbinclude cursordeletefail +/// @verbinclude api-cursor-delete //////////////////////////////////////////////////////////////////////////////// function DELETE_api_cursor(req, res) { if (req.suffix.length != 1) { - actions.resultNotFound(req, res, actions.ERROR_HTTP_NOT_FOUND); + actions.resultBad(req, res, actions.ERROR_HTTP_BAD_PARAMETER); return; } @@ -291,8 +305,8 @@ function DELETE_api_cursor(req, res) { var cursorId = decodeURIComponent(req.suffix[0]); var cursor = CURSOR(cursorId); - if (!(cursor instanceof AvocadoCursor)) { - actions.resultBad(req, res, actions.ERROR_CURSOR_NOT_FOUND, actions.getErrorMessage(actions.ERROR_CURSOR_NOT_FOUND)); + if (! (cursor instanceof AvocadoCursor)) { + actions.resultNotFound(req, res, actions.ERROR_CURSOR_NOT_FOUND); return; } diff --git a/js/actions/system/api-query.js b/js/actions/system/api-query.js index df33148bfb..a73b19b029 100644 --- a/js/actions/system/api-query.js +++ b/js/actions/system/api-query.js @@ -41,27 +41,29 @@ var actions = require("actions"); /// /// @REST{POST /_api/query} /// -/// To validate a query string without executing it, the query string can be +/// To validate a query string without executing it, the query string can be /// passed to the server via an HTTP POST request. /// -/// These query string needs to be passed in the attribute @LIT{query} of a -/// JSON object as the body of the POST request. +/// These query string needs to be passed in the attribute @LIT{query} of a JSON +/// object as the body of the POST request. /// -/// The server will respond with @LIT{HTTP 400} or @LIT{HTTP 500} in case of a -/// malformed request or a general error. -/// If the query contains a parse error, the server will respond with an -/// @LIT{HTTP 404} error. +/// If the query is valid, the server will respond with @LIT{HTTP 200} and +/// return the names of the bind parameters it found in the query (if any) in +/// the @LIT{"bindVars"} attribute of the response. /// -/// The body of the response will contain the error details embedded in a JSON -/// object. +/// The server will respond with @LIT{HTTP 400} in case of a malformed request, +/// or if the query contains a parse error. The body of the response will +/// contain the error details embedded in a JSON object. /// -/// @verbinclude querypostfail +/// @EXAMPLES /// -/// If the query is valid, the server will respond with @LIT{HTTP 200} and return -/// the names of the bind parameters it found in the query (if any) in the -/// @LIT{"bindVars"} attribute of the response. +/// Valid query: /// -/// @verbinclude querypost +/// @verbinclude api-query-valid +/// +/// Invalid query: +/// +/// @verbinclude api-query-invalid //////////////////////////////////////////////////////////////////////////////// function POST_api_query (req, res) { @@ -70,15 +72,24 @@ function POST_api_query (req, res) { return; } - try { - var json = JSON.parse(req.requestBody); - - if (!json || !(json instanceof Object) || json.query == undefined) { - actions.resultBad(req, res, actions.ERROR_QUERY_SPECIFICATION_INVALID, actions.getErrorMessage(actions.ERROR_QUERY_SPECIFICATION_INVALID)); - return; - } + var json; + try { + json = JSON.parse(req.requestBody || "{}") || {}; + } + catch (err) { + actions.resultBad(req, res, actions.ERROR_HTTP_CORRUPTED_JSON, err); + return; + } + + if (! json || ! (json instanceof Object)) { + actions.resultBad(req, res, actions.ERROR_QUERY_SPECIFICATION_INVALID); + return; + } + + try { var result = AHUACATL_PARSE(json.query); + if (result instanceof AvocadoError) { actions.resultBad(req, res, result.errorNum, result.errorMessage); return; diff --git a/js/server/modules/actions.js b/js/server/modules/actions.js index fac0badf84..0e06cadf7c 100644 --- a/js/server/modules/actions.js +++ b/js/server/modules/actions.js @@ -185,13 +185,15 @@ function DefineHttp (options) { //////////////////////////////////////////////////////////////////////////////// function GetErrorMessage (code) { - var error = internal.errors[code]; - - if (!error) { - return ""; + for (var key in internal.errors) { + if (internal.errors.hasOwnProperty(key)) { + if (internal.errors[key].code == code) { + return internal.errors[key].message; + } + } } - return error.message; + return ""; } //////////////////////////////////////////////////////////////////////////////// @@ -293,7 +295,14 @@ function ResultOk (req, res, httpReturnCode, result, headers) { //////////////////////////////////////////////////////////////////////////////// function ResultBad (req, res, code, msg, headers) { - ResultError(req, res, exports.HTTP_BAD, code, "" + msg, headers); + if (msg == null) { + msg = GetErrorMessage(code); + } + else { + msg = "" + msg; + } + + ResultError(req, res, exports.HTTP_BAD, code, msg, headers); } ////////////////////////////////////////////////////////////////////////////////