1
0
Fork 0

Work on the documentation to contain real examples.

This commit is contained in:
Willi Goesgens 2015-08-11 14:18:08 +02:00
parent 3c988dc7f1
commit 3bb3d6b623
8 changed files with 606 additions and 332 deletions

View File

@ -19,25 +19,17 @@ about the optimizer's view of the query.
Here's an example that shows the execution plan for a simple query, using the `explain`
method of `ArangoStatement`:
```
arangosh> query = "FOR i IN test FILTER i.value > 97 SORT i.value RETURN i.value";
arangosh> db._create("test");
arangosh> for (i = 0; i < 100; ++i) { db.test.save({ value: i }); }
arangosh> db.test.ensureSkiplist("value");
arangosh> stmt = db._createStatement(query);
arangosh> stmt.explain();
{
"plan" : {
...
},
"warnings" : [
...
],
"stats" : {
...
}
}
```
@startDocuBlockInline AQLEXP_01_explainCreate
@EXAMPLE_ARANGOSH_OUTPUT{AQLEXP_01_explainCreate}
~addIgnoreCollection("test")
db._create("test");
for (i = 0; i < 100; ++i) { db.test.save({ value: i }); }
db.test.ensureSkiplist("value");
stmt = db._createStatement("FOR i IN test FILTER i.value > 97 SORT i.value RETURN i.value");
stmt.explain();
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock AQLEXP_01_explainCreate
The result details will be very verbose so they are not shown here in full. Instead,
let's take a closer look at the results step by step.
@ -49,17 +41,13 @@ Each processing step is carried out by a so-called *execution node*
The `nodes` attribute of the `explain` result contains these *execution nodes* in
the *execution plan*. The output is still very verbose, so here's a shorted form of it:
```
arangosh> stmt.explain().plan.nodes.map(function (node) { return node.type; });
[
"SingletonNode",
"IndexRangeNode",
"CalculationNode",
"FilterNode",
"CalculationNode",
"ReturnNode"
]
```
@startDocuBlockInline AQLEXP_02_explainOverview
@EXAMPLE_ARANGOSH_OUTPUT{AQLEXP_02_explainOverview}
~var stmt = db._createStatement("FOR i IN test FILTER i.value > 97 SORT i.value RETURN i.value");
stmt.explain().plan.nodes.map(function (node) { return node.type; });
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock AQLEXP_02_explainOverview
*Note that the list of nodes might slightly change in future versions of ArangoDB if
new execution node types get added or the optimizer will create somewhat more
@ -108,17 +96,12 @@ Additionally, the optimizer has done more work to generate an execution plan tha
avoids as much expensive operations as possible. Here is the list of optimizer rules
that were applied to the plan:
arangosh> stmt.explain().plan.rules;
[
"move-calculations-up",
"move-filters-up",
"remove-redundant-calculations",
"remove-unnecessary-calculations",
"move-calculations-up-2",
"move-filters-up-2",
"use-index-range",
"use-index-for-sort"
]
@startDocuBlockInline AQLEXP_03_explainRules
@EXAMPLE_ARANGOSH_OUTPUT{AQLEXP_03_explainRules}
~var stmt = db._createStatement("FOR i IN test FILTER i.value > 97 SORT i.value RETURN i.value");
stmt.explain().plan.rules;
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock AQLEXP_03_explainRules
Here is the meaning of these rules in context of this query:
* `move-calculations-up`: moves a *CalculationNode* as far up in the processing pipeline
@ -148,15 +131,12 @@ in the optimizer pipeline.
The list of collections used in a plan (and query) is contained in the `collections`
attribute of a plan:
```
arangosh> stmt.explain().plan.collections
[
{
"name" : "test",
"type" : "read"
}
]
```
@startDocuBlockInline AQLEXP_04_explainCollections
@EXAMPLE_ARANGOSH_OUTPUT{AQLEXP_04_explainCollections}
~var stmt = db._createStatement("FOR i IN test FILTER i.value > 97 SORT i.value RETURN i.value");
stmt.explain().plan.collections
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock AQLEXP_04_explainCollections
The `name` attribute contains the name of the `collection`, and `type` is the
access type, which can be either `read` or `write`.
@ -186,17 +166,12 @@ generated, set the option `allPlans` to `true`:
This will return a list of all plans in the `plans` attribute instead of in the
`plan` attribute:
```
arangosh> stmt.explain({ allPlans: true });
{
"plans" : [
...
],
"warnings" : [
...
]
}
```
@startDocuBlockInline AQLEXP_05_explainAllPlans
@EXAMPLE_ARANGOSH_OUTPUT{AQLEXP_05_explainAllPlans}
~var stmt = db._createStatement("FOR i IN test FILTER i.value > 97 SORT i.value RETURN i.value");
stmt.explain({ allPlans: true });
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock AQLEXP_05_explainAllPlans
!SUBSECTION Retrieving the plan as it was generated by the parser / lexer
@ -206,15 +181,13 @@ the explain on a cluster coordinator) set the option `rules` to `-all`:
This will return an unoptimized plan in the `plan`:
```
arangosh> stmt.explain({ optimizer: { rules: [ "-all" ] } });
{
"plan" : {
...
},
...
}
```
@startDocuBlockInline AQLEXP_06_explainUnoptimizedPlans
@EXAMPLE_ARANGOSH_OUTPUT{AQLEXP_06_explainUnoptimizedPlans}
~var stmt = db._createStatement("FOR i IN test FILTER i.value > 97 SORT i.value RETURN i.value");
stmt.explain({ optimizer: { rules: [ "-all" ] } });
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock AQLEXP_06_explainUnoptimizedPlans
Note that some optimisations are already done at parse time (i.e. evaluate simple constant
calculation as `1 + 1`)
@ -229,32 +202,32 @@ pseudo-rule `all` matches all rules.
Rules specified in `rules` are evaluated from left to right, so the following works to
turn on just the one specific rule:
```
arangosh> stmt.explain({ optimizer: { rules: [ "-all", "+use-index-range" ] } });
{
...
}
```
@startDocuBlockInline AQLEXP_07_explainSingleRulePlans
@EXAMPLE_ARANGOSH_OUTPUT{AQLEXP_07_explainSingleRulePlans}
~var stmt = db._createStatement("FOR i IN test FILTER i.value > 97 SORT i.value RETURN i.value");
stmt.explain({ optimizer: { rules: [ "-all", "+use-index-range" ] } });
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock AQLEXP_07_explainSingleRulePlans
By default, all rules are turned on. To turn off just a few specific rules, use something
like this:
```
arangosh> stmt.explain({ optimizer: { rules: [ "-use-index-range", "-use-index-for-sort" ] } });
{
...
}
```
@startDocuBlockInline AQLEXP_08_explainDisableSingleRulePlans
@EXAMPLE_ARANGOSH_OUTPUT{AQLEXP_08_explainDisableSingleRulePlans}
~var stmt = db._createStatement("FOR i IN test FILTER i.value > 97 SORT i.value RETURN i.value");
stmt.explain({ optimizer: { rules: [ "-use-index-range", "-use-index-for-sort" ] } });
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock AQLEXP_08_explainDisableSingleRulePlans
The maximum number of plans created by the optimizer can also be limited using the
`maxNumberOfPlans` attribute:
```
arangosh> stmt.explain({ maxNumberOfPlans: 1 });
{
...
}
```
@startDocuBlockInline AQLEXP_09_explainMaxNumberOfPlans
@EXAMPLE_ARANGOSH_OUTPUT{AQLEXP_09_explainMaxNumberOfPlans}
~var stmt = db._createStatement("FOR i IN test FILTER i.value > 97 SORT i.value RETURN i.value");
stmt.explain({ maxNumberOfPlans: 1 });
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock AQLEXP_09_explainMaxNumberOfPlans
!SUBSECTION Optimizer statistics
@ -273,16 +246,14 @@ The following attributes will be returned in the `stats` attribute of an `explai
For some queries, the optimizer may produce warnings. These will be returned in
the `warnings` attribute of the `explain` result:
```
arangosh> stmt = db._createStatement("FOR i IN 1..10 RETURN 1 / 0")
arangosh> stmt.explain().warnings;
[
{
"code" : 1562,
"message" : "division by zero"
}
]
```
@startDocuBlockInline AQLEXP_10_explainWarn
@EXAMPLE_ARANGOSH_OUTPUT{AQLEXP_10_explainWarn}
var stmt = db._createStatement("FOR i IN 1..10 RETURN 1 / 0")
stmt.explain().warnings;
~db._drop("test")
~removeIgnoreCollection("test")
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock AQLEXP_10_explainWarn
There is an upper bound on the number of warning a query may produce. If that
bound is reached, no further warnings will be returned.

View File

@ -28,28 +28,43 @@ describes how to deal with a particular request path.
For the above example, add the following document to the _routing collection:
```js
arangosh> db._routing.save({
url: {
match: "/hello/world"
},
content: {
contentType: "text/html",
body: "<html><body>Hello World</body></html>"
}
});
```
@startDocuBlockInline HTML_01_routingCreateHtml
@EXAMPLE_ARANGOSH_OUTPUT{HTML_01_routingCreateHtml}
|db._routing.save({
| url: {
| match: "/hello/world"
| },
| content: {
| contentType: "text/html",
| body: "<html><body>Hello World</body></html>"
| }
});
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock HTML_01_routingCreateHtml
In order to activate the new routing, you must either restart the server or call
the internal reload function.
```js
arangosh> require("internal").reloadRouting()
```
@startDocuBlockInline HTML_02_routingReload
@EXAMPLE_ARANGOSH_OUTPUT{HTML_02_routingReload}
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock HTML_02_routingReload
Now use the browser and access http:// localhost:8529/hello/world
You should see the *Hello World* in our browser.
You should see the *Hello World* in our browser:
@startDocuBlockInline HTML_03_routingCurlHtml
@EXAMPLE_ARANGOSH_RUN{HTML_03_routingCurlHtml}
var url = "/hello/world";
var response = logCurlRequest('GET', url);
assert(response.code === 200);
logRawResponse(response);
db._query("FOR route IN _routing FILTER route.url.match == '/hello/world' REMOVE route in _routing")
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock HTML_03_routingCurlHtml
!SECTION Matching a URL
@ -251,4 +266,4 @@ to test the above examples.
{ url: { match: "/:something/world" }, content: "route 3" },
{ url: { match: "/hello/*" }, content: "route 4" },
]
}
}

View File

@ -2,20 +2,20 @@
If you change the example slightly, then a JSON object will be delivered.
@startDocuBlockInline routingCreateHelloWorld
@EXAMPLE_ARANGOSH_OUTPUT{routingCreateHelloWorld}
@startDocuBlockInline JSON_01_routingCreateJsonHelloWorld
@EXAMPLE_ARANGOSH_OUTPUT{JSON_01_routingCreateJsonHelloWorld}
|db._routing.save({
| url: "/hello/json",
| content: {
| contentType: "application/json",
| body: "{ \"hello\" : \"world\" }"
| body: "{ 'hello' : 'world'}"
| }
});
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock routingCreateHelloWorld
@endDocuBlock JSON_01_routingCreateJsonHelloWorld
Again check with your browser http:// localhost:8529/hello/json
Again check with your browser or cURL http://localhost:8529/hello/json
Depending on your browser and installed add-ons you will either see the JSON
object or a download dialog. If your browser wants to open an external
@ -23,15 +23,21 @@ application to display the JSON object, you can change the *contentType* to
*"text/plain"* for the example. This makes it easier to check the example using
a browser. Or use *curl* to access the server.
@startDocuBlockInline routingCurlHelloWorld
@EXAMPLE_ARANGOSH_RUN{routingCurlHelloWorld}
@startDocuBlockInline JSON_02_routingCurlJsonHelloWorld
@EXAMPLE_ARANGOSH_RUN{JSON_02_routingCurlJsonHelloWorld}
var url = "/hello/json";
var response = logCurlRequest('GET', url);
assert(response.code === 200);
logJsonResponse(response);
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock routingCurlHelloWorld
@endDocuBlock JSON_02_routingCurlJsonHelloWorld
@startDocuBlockInline JSON_03_routingCleanupJsonHelloWorld
@EXAMPLE_ARANGOSH_OUTPUT{JSON_03_routingCleanupJsonHelloWorld}
~db._query("FOR route IN _routing FILTER route.url == '/hello/json' REMOVE route in _routing")
~require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock JSON_03_routingCleanupJsonHelloWorld
!SECTION Delivering Content
@ -43,14 +49,34 @@ starts when delivering dynamic content.
You can specify a body and a content-type.
```js
{
content: {
contentType: "text/html",
body: "<html><body>Hello World</body></html>"
}
}
```
@startDocuBlockInline JSON_05a_routingCreateContentTypeHelloWorld
@EXAMPLE_ARANGOSH_OUTPUT{JSON_05a_routingCreateContentTypeHelloWorld}
|db._routing.save({
| url: "/hello/contentType",
| content: {
| contentType: "text/html",
| body: "<html><body>Hello World</body></html>"
| }
});
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock JSON_05a_routingCreateContentTypeHelloWorld
@startDocuBlockInline JSON_05b_routingCurlContentTypeHelloWorld
@EXAMPLE_ARANGOSH_RUN{JSON_05b_routingCurlContentTypeHelloWorld}
var url = "/hello/contentType";
var response = logCurlRequest('GET', url);
assert(response.code === 200);
logRawResponse(response);
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock JSON_05b_routingCurlContentTypeHelloWorld
@startDocuBlockInline JSON_05c_routingCleanupContentTypeHelloWorld
@EXAMPLE_ARANGOSH_OUTPUT{JSON_05c_routingCleanupContentTypeHelloWorld}
~db._query("FOR route IN _routing FILTER route.url == '/hello/contentType' REMOVE route in _routing")
~require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock JSON_05c_routingCleanupContentTypeHelloWorld
If the content type is *text/plain* then you can use the short-cut
@ -90,8 +116,8 @@ function (req, res, options, next)
*Examples*
@startDocuBlockInline routingCreateHelloEcho
@EXAMPLE_ARANGOSH_OUTPUT{routingCreateHelloEcho}
@startDocuBlockInline JSON_06_routingCreateHelloEcho
@EXAMPLE_ARANGOSH_OUTPUT{JSON_06_routingCreateHelloEcho}
|db._routing.save({
| url: "/hello/echo",
| action: {
@ -100,17 +126,24 @@ function (req, res, options, next)
});
~require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock routingCreateHelloEcho
@endDocuBlock JSON_06_routingCreateHelloEcho
Reload the routing and check http:// 127.0.0.1:8529/hello/echo
You should see something like
@startDocuBlockInline fetchroutingCreateHelloEcho
@EXAMPLE_ARANGOSH_OUTPUT{fetchroutingCreateHelloEcho}
arango.GET("hello/echo")
@startDocuBlockInline JSON_07_fetchroutingCreateHelloEcho
@EXAMPLE_ARANGOSH_OUTPUT{JSON_07_fetchroutingCreateHelloEcho}
arango.GET("/hello/echo")
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock fetchroutingCreateHelloEcho
@endDocuBlock JSON_07_fetchroutingCreateHelloEcho
@startDocuBlockInline JSON_08_routingCleanupHelloEcho
@EXAMPLE_ARANGOSH_OUTPUT{JSON_08_routingCleanupHelloEcho}
~db._query("FOR route IN _routing FILTER route.url == '/hello/echo' REMOVE route in _routing")
~require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock JSON_08_routingCleanupHelloEcho
The request might contain *path*, *prefix*, *suffix*, and *urlParameters*
attributes. *path* is the complete path as supplied by the user and always
@ -148,8 +181,8 @@ called.
*Examples*
@startDocuBlockInline routingCreateEchoController
@EXAMPLE_ARANGOSH_OUTPUT{routingCreateEchoController}
@startDocuBlockInline JSON_09_routingCreateEchoController
@EXAMPLE_ARANGOSH_OUTPUT{JSON_09_routingCreateEchoController}
|db._routing.save({
| url: "/hello/echo",
| action: {
@ -158,7 +191,23 @@ called.
});
~require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock routingCreateEchoController
@endDocuBlock JSON_09_routingCreateEchoController
Reload the routing and check http:// 127.0.0.1:8529/hello/echo:
@startDocuBlockInline JSON_10_fetchroutingCreateEchoController
@EXAMPLE_ARANGOSH_OUTPUT{JSON_10_fetchroutingCreateEchoController}
arango.GET("/hello/echo")
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock JSON_10_fetchroutingCreateEchoController
@startDocuBlockInline JSON_11_routingCleanupEchoController
@EXAMPLE_ARANGOSH_OUTPUT{JSON_11_routingCleanupEchoController}
~db._query("FOR route IN _routing FILTER route.url == '/hello/echo' REMOVE route in _routing")
~require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock JSON_11_routingCleanupEchoController
!SUBSECTION Prefix Action Controller
@ -206,8 +255,8 @@ You can also store a function directly in the routing table.
*Examples*
@startDocuBlockInline routingCreateEchoFunction
@EXAMPLE_ARANGOSH_OUTPUT{routingCreateEchoFunction}
@startDocuBlockInline JSON_12a_routingCreateEchoFunction
@EXAMPLE_ARANGOSH_OUTPUT{JSON_12a_routingCreateEchoFunction}
|db._routing.save({
| url: "/hello/echo",
| action: {
@ -216,7 +265,15 @@ You can also store a function directly in the routing table.
});
~require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock routingCreateEchoFunction
@endDocuBlock JSON_12a_routingCreateEchoFunction
@startDocuBlockInline JSON_12b_fetchroutingEchoFunction
@EXAMPLE_ARANGOSH_OUTPUT{JSON_12b_fetchroutingEchoFunction}
arango.GET("hello/echo")
db._query("FOR route IN _routing FILTER route.url == '/hello/echo' REMOVE route in _routing")
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock JSON_12b_fetchroutingEchoFunction
!SUBSECTION Requests and Responses
@ -240,8 +297,8 @@ function (req, res, options, next) {
Install it via:
@startDocuBlockInline routingCreateEchoAction
@EXAMPLE_ARANGOSH_OUTPUT{routingCreateEchoAction}
@startDocuBlockInline JSON_13_routingCreateEchoAction
@EXAMPLE_ARANGOSH_OUTPUT{JSON_13_routingCreateEchoAction}
|db._routing.save({
| url: "/echo",
| action: {
@ -250,22 +307,24 @@ Install it via:
})
~require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock routingCreateEchoAction
@endDocuBlock JSON_13_routingCreateEchoAction
Reload the routing and check http:// 127.0.0.1:8529/hello/echo
You should see something like
@startDocuBlockInline fetchroutingRequestHelloEcho
@EXAMPLE_ARANGOSH_OUTPUT{fetchroutingRequestHelloEcho}
arango.GET("hello/echo")
@startDocuBlockInline JSON_14_fetchroutingRequestHelloEcho
@EXAMPLE_ARANGOSH_OUTPUT{JSON_14_fetchroutingRequestHelloEcho}
arango.GET("/hello/echo")
db._query("FOR route IN _routing FILTER route.url == '/hello/echo' REMOVE route in _routing")
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock fetchroutingRequestHelloEcho
@endDocuBlock JSON_14_fetchroutingRequestHelloEcho
You may also pass options to the called function:
@startDocuBlockInline routingCreateEchoRequestOptions
@EXAMPLE_ARANGOSH_OUTPUT{routingCreateEchoRequestOptions}
@startDocuBlockInline JSON_15_routingCreateEchoRequestOptions
@EXAMPLE_ARANGOSH_OUTPUT{JSON_15_routingCreateEchoRequestOptions}
|db._routing.save({
| url: "/echo",
| action: {
@ -277,17 +336,14 @@ You may also pass options to the called function:
});
~require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock routingCreateEchoRequestOptions
@endDocuBlock JSON_15_routingCreateEchoRequestOptions
You should now see the options in the result.
You now see the options in the result:
```js
{
"request": {
...
},
"options": {
"Hello": "World"
}
}
````
@startDocuBlockInline JSON_16_fetchroutingEchoRequestOptions
@EXAMPLE_ARANGOSH_OUTPUT{JSON_16_fetchroutingEchoRequestOptions}
arango.GET("/echo")
db._query("FOR route IN _routing FILTER route.url == '/echo' REMOVE route in _routing")
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock JSON_16_fetchroutingEchoRequestOptions

View File

@ -18,10 +18,10 @@ object is an action's only way to return data to the caller of the action.
We've already seen how to set the HTTP status code, the content type, and the
result body. The *res* object has the following properties for these:
- *contentType*: MIME type of the body as defined in the HTTP standard (e.g.
- *contentType*: MIME type of the body as defined in the HTTP standard (e.g.
*text/html*, *text/plain*, *application/json*, ...)
- *responsecode*: the HTTP status code of the response as defined in the HTTP
standard. Common values for actions that succeed are *200* or *201*.
standard. Common values for actions that succeed are *200* or *201*.
Please refer to the HTTP standard for more information.
- *body*: the actual response data
@ -67,53 +67,69 @@ To write your own dynamic action handlers, you must put them into modules.
Modules are a means of organizing action handlers and making them loadable under
specific names.
To start, we'll define a simple action handler in a module */own/test*:
To start, we'll define a simple action handler in a module */ownTest*:
```js
arangosh> db._modules.save({
path: "/own/test",
content: "exports.do = function(req, res, options, next) { res.body = 'test'; res.responseCode = 200; res.contentType = 'text/html'; };"
});
```
@startDocuBlockInline MOD_01a_routingCreateOwnTest
@EXAMPLE_ARANGOSH_OUTPUT{MOD_01a_routingCreateOwnTest}
|db._modules.save({
| path: "/db:/ownTest",
| content:
| "exports.do = function(req, res, options, next) {"+
| " res.body = 'test';" +
| " res.responseCode = 200;" +
| " res.contentType = 'text/plain';" +
| "};"
});
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock MOD_01a_routingCreateOwnTest
This does nothing but register a do action handler in a module */own/test*. The
This does nothing but register a do action handler in a module */ownTest*. The
action handler is not yet callable, but must be mapped to a route first. To map
the action to the route */ourtest*, execute the following command:
```js
arangosh> db._routing.save({
url: "/ourtest",
action: {
controller: "/own/test"
}
});
```
@startDocuBlockInline MOD_01b_routingEnableOwnTest
@EXAMPLE_ARANGOSH_OUTPUT{MOD_01b_routingEnableOwnTest}
|db._routing.save({
| url: "/ourtest",
| action: {
| controller: "db://ownTest"
| }
});
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock MOD_01b_routingEnableOwnTest
In order to see the module in action, you must either restart the server or call
the internal reload function.
Now use the browser or cURL and access http://localhost:8529/ourtest :
```js
arangosh> require("internal").reloadRouting()
```
@startDocuBlockInline MOD_01c_routingCurlOwnTest
@EXAMPLE_ARANGOSH_RUN{MOD_01c_routingCurlOwnTest}
var url = "/ourtest";
var response = logCurlRequest('GET', url);
assert(response.code === 200);
assert(response.body === 'test');
logRawResponse(response);
db._query("FOR route IN _routing FILTER route.url == '/ourtest' REMOVE route in _routing")
db._query("FOR module IN _modules FILTER module.path == '/db:/ownTest' REMOVE module in _modules")
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_RUN
@endDocuBlock MOD_01c_routingCurlOwnTest
Now use the browser and access http:// localhost:8529/ourtest
You will see that the module's do function has been executed.
!SECTION A Word about Caching
Sometimes it might seem that your change do not take effect. In this case the
culprit could be one of the caches. With dynamic actions there are two caches
involved:
!SUBSECTION The Routing Cache
culprit could be the routing caches:
The routing cache stores the routing information computed from the *_routing*
collection. Whenever you change this collection manually, you need to call
```js
arangosh> require("internal").reloadRouting();
```
@startDocuBlockInline MOD_05_routingModifyReload
@EXAMPLE_ARANGOSH_OUTPUT{MOD_05_routingModifyReload}
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock MOD_05_routingModifyReload
in order to rebuild the cache.
@ -126,93 +142,222 @@ For detailed information see the reference manual.
Use the following for a permanent redirect:
```js
arangosh> db._routing.save({
url: "/",
action: {
do: "org/arangodb/actions/redirectRequest",
options: {
permanently: true,
destination: "http://somewhere.else/"
}
}
});
```
@startDocuBlockInline MOD_06a_routingRedirect
@EXAMPLE_ARANGOSH_OUTPUT{MOD_06a_routingRedirect}
| db._routing.save({
| url: "/redirectMe",
| action: {
| do: "org/arangodb/actions/redirectRequest",
| options: {
| permanently: true,
| destination: "/somewhere.else/"
| }
| }
});
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock MOD_06a_routingRedirect
@startDocuBlockInline MOD_06b_routingCurlRedirect
@EXAMPLE_ARANGOSH_RUN{MOD_06b_routingCurlRedirect}
var url = "/redirectMe";
var response = logCurlRequest('GET', url);
assert(response.code === 301);
logRawResponse(response);
db._query("FOR route IN _routing FILTER route.url == '/redirectMe' REMOVE route in _routing")
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_RUN
@endDocuBlock MOD_06b_routingCurlRedirect
!SUBSECTION Routing Bundles
Instead of adding all routes for package separately, you can
specify a bundle.
specify a bundle:
```js
{
routes: [
{ url: "/url1", content: "..." },
{ url: "/url2", content: "..." },
{ url: "/url3", content: "..." },
...
]
}
```
@startDocuBlockInline MOD_07a_routingMulti
@EXAMPLE_ARANGOSH_OUTPUT{MOD_07a_routingMulti}
| db._routing.save({
| routes: [
| {
| url: "/url1",
| content: "route 1"
| },
| {
| url: "/url2",
| content: "route 2"
| },
| {
| url: "/url3",
| content: "route 3"
| }
| ]
});
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock MOD_07a_routingMulti
@startDocuBlockInline MOD_07b_routingCurlMulti
@EXAMPLE_ARANGOSH_RUN{MOD_07b_routingCurlMulti}
var url = ["/url1", "/url2", "/url3"];
var reply = ["route 1", "route 2", "route 3"]
for (var i = 1; i < 3; i++) {
var response = logCurlRequest('GET', url[i]);
assert(response.code === 200);
assert(response.body === reply[i])
logRawResponse(response);
}
db._query("FOR route IN _routing FILTER route.routes[0].url == '/url1' REMOVE route in _routing")
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_RUN
@endDocuBlock MOD_07b_routingCurlMulti
The advantage is, that you can put all your routes into one document
and use a common prefix.
```js
{
urlPrefix: "/test",
routes: [
{ url: "/url1", content: "..." },
{ url: "/url2", content: "..." },
{ url: "/url3", content: "..." },
...
]
}
```
will define the URL */test/url1*, */test/url2*, and */test/url3*.
@startDocuBlockInline MOD_07c_routingMulti
@EXAMPLE_ARANGOSH_OUTPUT{MOD_07c_routingMulti}
| db._routing.save({
| urlPrefix: "/test",
| routes: [
| {
| url: "/url1",
| content: "route 1"
| },
| {
| url: "/url2",
| content: "route 2"
| },
| {
| url: "/url3",
| content: "route 3"
| }
| ]
});
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock MOD_07c_routingMulti
will define the URL */test/url1*, */test/url2*, and */test/url3*:
@startDocuBlockInline MOD_07d_routingCurlMulti
@EXAMPLE_ARANGOSH_RUN{MOD_07d_routingCurlMulti}
var url = ["/test/url1", "/test/url2", "/test/url3"];
var reply = ["route 1", "route 2", "route 3"]
for (var i = 0; i < 3; i++) {
var response = logCurlRequest('GET', url[i]);
assert(response.code === 200);
assert(response.body === reply[i])
logRawResponse(response);
}
db._query("FOR route IN _routing FILTER route.routes[0].url == '/url1' REMOVE route in _routing")
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_RUN
@endDocuBlock MOD_07d_routingCurlMulti
!SUBSECTION Writing Middleware
Assume, you want to log every request. In this case you can easily define an
action for the whole url-space */*. This action simply logs the requests, calls
the next in line, and logs the response.
Assume, you want to log every request in your namespace to the console. *(if ArangoDB is running
as a daemon, this will end up in the logfile)*. In this case you can easily define an
action for the URL */subdirectory*. This action simply logs
the requests, calls the next in line, and logs the response:
```js
exports.logRequest = function (req, res, options, next) {
console.log("received request: %s", JSON.stringify(req));
next();
console.log("produced response: %s", JSON.stringify(res));
};
```
@startDocuBlockInline MOD_08a_routingCreateOwnConsoleLog
@EXAMPLE_ARANGOSH_OUTPUT{MOD_08a_routingCreateOwnConsoleLog}
|db._modules.save({
| path: "/db:/OwnMiddlewareTest",
| content:
| "exports.logRequest = function (req, res, options, next) {" +
| " console = require('console'); " +
| " console.log('received request: %s', JSON.stringify(req));" +
| " next();" +
| " console.log('produced response: %s', JSON.stringify(res));" +
| "};"
});
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock MOD_08a_routingCreateOwnConsoleLog
This function is available as *org/arangodb/actions/logRequest*. You need to
This function will now be available as *db://OwnMiddlewareTest/logRequest*. You need to
tell ArangoDB that it is should use a prefix match and that the shortest match
should win in this case:
```js
arangosh> db._routing.save({
middleware: [
{
url: {
match: "/*"
},
action: {
do: "org/arangodb/actions/logRequest"
}
}
]
@startDocuBlockInline MOD_08b_routingCreateRouteToOwnConsoleLog
@EXAMPLE_ARANGOSH_OUTPUT{MOD_08b_routingCreateRouteToOwnConsoleLog}
|db._routing.save({
| middleware: [
| {
| url: {
| match: "/subdirectory/*"
| },
| action: {
| do: "db://OwnMiddlewareTest/logRequest"
| }
| }
| ]
});
```
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock MOD_08b_routingCreateRouteToOwnConsoleLog
If you call *next()*, the next specific routing will be used for the
original URL. Even if you modify the URL in the request object *req*,
this will not cause the *next()* to jump to the routing defined for
this next URL. If proceeds occurring the origin URL. However, if you
use *next(true)*, the routing will stop and request handling is
When you call *next()* in that action, the next specific routing will
be used for the original URL. Even if you modify the URL in the request
object *req*, this will not cause the *next()* to jump to the routing
defined for this next URL. If proceeds occurring the origin URL. However,
if you use *next(true)*, the routing will stop and request handling is
started with the new URL. You must ensure that *next(true)* is never
called without modifying the URL in the request object
*req*. Otherwise an endless loop will occur.
Now we add some other simple routings to test all this:
@startDocuBlockInline MOD_08c_routingCreateRouteToOwnConsoleLog
@EXAMPLE_ARANGOSH_OUTPUT{MOD_08c_routingCreateRouteToOwnConsoleLog}
|db._routing.save({
| url: "/subdirectory/ourtest/1",
| action: {
| do: "org/arangodb/actions/echoRequest"
| }
});
|db._routing.save({
| url: "/subdirectory/ourtest/2",
| action: {
| do: "org/arangodb/actions/echoRequest"
| }
});
|db._routing.save({
| url: "/subdirectory/ourtest/3",
| action: {
| do: "org/arangodb/actions/echoRequest"
| }
});
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock MOD_08c_routingCreateRouteToOwnConsoleLog
Then we send some curl requests to these sample routes:
@startDocuBlockInline MOD_08d_routingCurlToOwnConsoleLog
@EXAMPLE_ARANGOSH_RUN{MOD_08d_routingCurlToOwnConsoleLog}
var url = ["/subdirectory/ourtest/1",
"/subdirectory/ourtest/2",
"/subdirectory/ourtest/3"];
for (var i = 0; i < 3; i++) {
var response = logCurlRequest('GET', url[i]);
assert(response.code === 200);
logJsonResponse(response);
}
db._query("FOR route IN _routing FILTER route.middleware[0].url.match == '/subdirectory/*' REMOVE route in _routing");
db._query("FOR route IN _routing FILTER route.url == '/subdirectory/ourtest/1' REMOVE route in _routing");
db._query("FOR route IN _routing FILTER route.url == '/subdirectory/ourtest/2' REMOVE route in _routing");
db._query("FOR route IN _routing FILTER route.url == '/subdirectory/ourtest/3' REMOVE route in _routing");
db._query("FOR module IN _modules FILTER module.path == '/db:/OwnMiddlewareTest' REMOVE module in _modules");
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_RUN
@endDocuBlock MOD_08d_routingCurlToOwnConsoleLog
and the console (and / or the logfile) will show requests and replies.
*Note that logging doesn't warant the sequence in which these lines
will appear.*
!SECTION Application Deployment
Using single routes or [bundles](#routing_bundles) can be
@ -222,19 +367,21 @@ Note that there is also [Foxx](../Foxx/README.md) for building applications
with ArangoDB.
!SECTION Common Pitfalls when using Actions
!SUBSECTION Caching
If you made any changes to the routing but the changes do not have any effect
when calling the modified action's URL, you might have been hit by some
caching issues.
If you made any changes to the routing but the changes does not have any effect
when calling the modified actions URL, you might have been hit by some
caching issues.
After any modification to the routing or actions, it is thus recommended to
make the changes "live" by calling the following functions from within arangosh:
```js
arangosh> require("internal").reloadRouting();
```
@startDocuBlockInline MOD_09_routingReload
@EXAMPLE_ARANGOSH_RUN{MOD_09_routingReload}
require("internal").reloadRouting();
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock MOD_09_routingReload
You might also be affected by client-side caching.
Browsers tend to cache content and also redirection URLs. You might need to
@ -261,37 +408,68 @@ then the header *X-My-Value* will have a value of (string) *5* and not (number)
!SUBSECTION 501 Not Implemented
If you defined a URL in the routing and the URL is accessible fine via
HTTP *GET* but returns an HTTP 501 (not implemented) for other HTTP methods
If you defined a URL in the routing and the URL is accessible fine via
HTTP *GET* but returns an HTTP 501 (not implemented) for other HTTP methods
such as *POST*, *PUT* or *DELETE*, then you might have been hit by some
defaults.
By default, URLs defined like this (simple string *url* attribute) are
By default, URLs defined like this (simple string *url* attribute) are
accessible via HTTP *GET* and *HEAD* only. To make such URLs accessible via
other HTTP methods, extend the URL definition with the *methods* attribute.
For example, this definition only allows access via *GET* and *HEAD*:
```js
{
url: "/hello/world"
{
url: "/hello/world"
}
```
whereas this definition allows HTTP *GET*, *POST*, and *PUT*:
```js
{
url: {
match: "/hello/world",
methods: [ "get", "post", "put" ]
}
}
```
@startDocuBlockInline MOD_09a_routingSpecifyMethods
@EXAMPLE_ARANGOSH_OUTPUT{MOD_09a_routingSpecifyMethods}
|db._routing.save({
| url: {
| match: "/hello/world",
| methods: [ "get", "post", "put" ]
| },
| action: {
| do: "org/arangodb/actions/echoRequest"
| }
});
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock MOD_09a_routingSpecifyMethods
@startDocuBlockInline MOD_09b_routingCurlSpecifyMethods
@EXAMPLE_ARANGOSH_RUN{MOD_09b_routingCurlSpecifyMethods}
var url = "/hello/world"
var postContent = "{hello: 'world'}";
var response = logCurlRequest('GET', url);
assert(response.code === 200);
logJsonResponse(response);
var response = logCurlRequest('POST', url, postContent);
assert(response.code === 200);
logJsonResponse(response);
var response = logCurlRequest('PUT', url, postContent);
assert(response.code === 200);
logJsonResponse(response);
var response = logCurlRequest('DELETE', url);
assert(response.code === 404); //// TODO: should be 405 - method not allowed
logJsonResponse(response);
db._query("FOR route IN _routing FILTER route.url.match == '/hello/world' REMOVE route in _routing");
require("internal").reloadRouting()
@END_EXAMPLE_ARANGOSH_RUN
@endDocuBlock MOD_09b_routingCurlSpecifyMethods
The former definition (defining *url* as an object with a *match* attribute)
will result in the URL being accessible via all supported HTTP methods (e.g.
*GET*, *POST*, *PUT*, *DELETE*, ...), whereas the latter definition (providing a string
*url* attribute) will result in the URL being accessible via HTTP *GET* and
*url* attribute) will result in the URL being accessible via HTTP *GET* and
HTTP *HEAD* only, with all other HTTP methods being disabled. Calling a URL
with an unsupported or disabled HTTP method will result in an HTTP 501 error.
with an unsupported or disabled HTTP method will result in an HTTP 404 error.

View File

@ -12,11 +12,11 @@ fashion. The collection *_users* contains all users and a salted SHA256 hash
of their passwords. A user can be active or inactive. A typical document of this
collection is
@startDocuBlockInline authFetch
@EXAMPLE_ARANGOSH_OUTPUT{authFetch}
@startDocuBlockInline USER_01_authFetch
@EXAMPLE_ARANGOSH_OUTPUT{USER_01_authFetch}
db._users.toArray()
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock authFetch
@endDocuBlock USER_01_authFetch
!SUBSECTION Command-Line Options for the Authentication and Authorization
@ -62,9 +62,32 @@ the server authentication cache is [reloaded](#examples_reload).
*Examples*
```js
arangosh> require("org/arangodb/users").save("my-user", "my-secret-password");
```
@startDocuBlockInline USER_02_saveUser
@EXAMPLE_ARANGOSH_OUTPUT{USER_02_saveUser}
require("org/arangodb/users").save("my-user", "my-secret-password");
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock USER_02_saveUser
!SUBSECTION Reload
`users.reload()`
Reloads the user authentication data on the server
All user authentication data is loaded by the server once on startup only and is
cached after that. When users get added or deleted, a cache flush is required,
and this can be performed by called this method.
*Examples*
@startDocuBlockInline USER_03_reloadUser
@EXAMPLE_ARANGOSH_OUTPUT{USER_03_reloadUser}
require("org/arangodb/users").reload();
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock USER_03_reloadUser
**Note**: this function will not work from within the web interface
!SUBSECTION Document
@ -76,6 +99,14 @@ The username must be specified in *user*.
This method will fail if the user cannot be found in the database.
*Examples*
@startDocuBlockInline USER_04_documentUser
@EXAMPLE_ARANGOSH_OUTPUT{USER_04_documentUser}
require("org/arangodb/users").document("my-user");
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock USER_04_documentUser
**Note**: this function will not work from within the web interface
!SUBSECTION Replace
@ -104,9 +135,11 @@ database.
*Examples*
```js
arangosh> require("org/arangodb/users").replace("my-user", "my-changed-password");
```
@startDocuBlockInline USER_03_replaceUser
@EXAMPLE_ARANGOSH_OUTPUT{USER_03_replaceUser}
require("org/arangodb/users").replace("my-user", "my-changed-password");
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock USER_03_replaceUser
!SUBSECTION Update
@ -131,9 +164,46 @@ database.
*Examples*
```js
arangosh> require("org/arangodb/users").update("my-user", "my-secret-password");
```
@startDocuBlockInline USER_04_updateUser
@EXAMPLE_ARANGOSH_OUTPUT{USER_04_updateUser}
require("org/arangodb/users").update("my-user", "my-secret-password");
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock USER_04_updateUser
!SUBSECTION isValid
`users.isValid(user, password)`
Checks whether the given combination of username and password is valid. The
function will return a boolean value if the combination of username and password
is valid.
Each call to this function is penalized by the server sleeping a random
amount of time.
*Examples*
@startDocuBlockInline USER_05_isValidUser
@EXAMPLE_ARANGOSH_OUTPUT{USER_05_isValidUser}
require("org/arangodb/users").isValid("my-user", "my-secret-password");
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock USER_05_isValidUser
**Note**: this function will not work from within the web interface
!SUBSECTION all()
`users.all()`
Fetches all existing ArangoDB users from the database.
*Examples*
@startDocuBlockInline USER_06_AllUsers
@EXAMPLE_ARANGOSH_OUTPUT{USER_06_AllUsers}
require("org/arangodb/users").all();
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock USER_06_AllUsers
!SUBSECTION Remove
@ -150,37 +220,9 @@ This method will fail if the user cannot be found in the database.
*Examples*
```js
arangosh> require("org/arangodb/users").remove("my-user");
```
@startDocuBlockInline USER_07_removeUser
@EXAMPLE_ARANGOSH_OUTPUT{USER_07_removeUser}
require("org/arangodb/users").remove("my-user");
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock USER_07_removeUser
!SUBSECTION Reload
`users.reload()`
Reloads the user authentication data on the server
All user authentication data is loaded by the server once on startup only and is
cached after that. When users get added or deleted, a cache flush is required,
and this can be performed by called this method.
**Note**: this function will not work from within the web interface
!SUBSECTION isValid
`users.isvalid(user, password)`
Checks whether the given combination of username and password is valid. The
function will return a boolean value if the combination of username and password
is valid.
Each call to this function is penalized by the server sleeping a random
amount of time.
**Note**: this function will not work from within the web interface
!SUBSECTION all()
`users.all()`
Fetches all existing ArangoDB users from the database.

View File

@ -169,7 +169,7 @@ the `_id` using *byExample*:
@EXAMPLE_ARANGOSH_OUTPUT{07_workWithColl_remove}
db.example.remove(db.example.byExample({ name: "John Doe" }).toArray()[0]._id)
db.example.toArray()
~addIgnoreCollection("example")
~removeIgnoreCollection("example")
~db._drop("example")
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock 07_workWithColl_remove

View File

@ -150,10 +150,12 @@ arangosh> test1.func1();
The values in `__filename` and `__dirname` can be used for generic debugging and for
creating filename relative to the required file, e.g.
```js
var fs = require("fs");
var relativeFile = fs.join(__dirname, "scripts", "test.js");
```
@startDocuBlockInline MODJS_fsDir
@EXAMPLE_ARANGOSH_OUTPUT{MODJS_fsDir}
var files = require("fs");
relativeFile = files.join(__dirname, "scripts", "test.js");
@END_EXAMPLE_ARANGOSH_OUTPUT
@endDocuBlock MODJS_fsDir
*require* can also be used to load JSON data. If the name of the required module ends

View File

@ -30,3 +30,13 @@ Returns the current query tracking configuration.
`require("org/arangodb/aql/queries").kill();`
Kills a running AQL query.
var tasks = require("org/arangodb/tasks");
tasks.register({
id: "mytask-1",
name: "this is a snippet task",
period: 15,
command: "require('console').log('hello from snippet task');"
});