3.1. 查询服务器协议¶
A Query Server 是一个外部进程,它通过stdin/stdout上的简单自定义JSON协议与CouchDB通信。它用于处理所有设计函数调用: views , shows , lists , filters , updates 和 validate_doc_update .
CouchDB通过stdin/stdout与查询服务器进程通信,JSON消息以换行符结尾。发送到查询服务器的消息始终 array -按模式打字 [<command>, <*arguments>]\n
.
注解
在文档示例中,我们省略了尾部 \n
以提高可读性。此外,示例包含格式化的JSON值,而实际数据以压缩模式传输,而不使用格式化空格。
3.1.1. reset
¶
命令: | reset |
---|---|
争论: | Query server state (可选) |
返回: | true |
这将重置查询服务器的状态,并使其忘记以前的所有输入。如果适用,这是运行垃圾回收的点。
CouchDB发送:
["reset"]
查询服务器回答:
true
要设置新的查询服务器状态,第二个参数用于对象数据。
CouchDB发送:
["reset", {"reduce_limit": true, "timeout": 5000}]
查询服务器回答:
true
3.1.2. add_lib
¶
命令: | add_lib |
---|---|
争论: | CommonJS库对象 views/lib 路径 |
返回: | true |
添加 CommonJS 用于查询服务器状态以在中进一步使用的库 map 功能。
CouchDB发送:
[
"add_lib",
{
"utils": "exports.MAGIC = 42;"
}
]
查询服务器回答:
true
注解
这个库不应该有任何副作用,也不应该跟踪它自己的状态,否则如果出现问题,您将有很多愉快的调试时间。请记住,完整的索引重建是一项繁重的操作,这是修复共享状态错误的唯一方法。
3.1.3. add_fun
¶
命令: | add_fun |
---|---|
争论: | 映射函数源代码。 |
返回: | true |
在创建或更新视图时,这是向查询服务器发送视图函数进行计算的方式。查询服务器应该解析、编译和计算它接收到的函数,以便以后可以调用它。如果查询失败,服务器将返回错误。CouchDB可以在发送任何文档之前存储多个函数。
CouchDB发送:
[
"add_fun",
"function(doc) { if(doc.score > 50) emit(null, {'player_name': doc.name}); }"
]
查询服务器回答:
true
3.1.4. map_doc
¶
命令: | map_doc |
---|---|
争论: | 文档对象 |
返回: | 每个应用的键值对数组 function |
当view函数存储在查询服务器中时,CouchDB开始发送数据库中的所有文档,一次发送一个文档。查询服务器使用文档逐个调用先前存储的函数并存储其结果。调用了所有函数后,结果将以JSON字符串的形式返回。
CouchDB发送:
[
"map_doc",
{
"_id": "8877AFF9789988EE",
"_rev": "3-235256484",
"name": "John Smith",
"score": 60
}
]
如果上面的函数是唯一存储的函数,则查询服务器将回答:
[
[
[null, {"player_name": "John Smith"}]
]
]
也就是说,一个数组包含给定文档的每个函数的结果。
如果要从视图中排除文档,则数组应为空。
CouchDB发送:
[
"map_doc",
{
"_id": "9590AEB4585637FE",
"_rev": "1-674684684",
"name": "Jane Parker",
"score": 43
}
]
查询服务器回答:
[[]]
3.1.5. reduce
¶
命令: |
|
---|---|
争论: |
|
返回: | 具有对值的数组: |
如果视图定义了reduce函数,CouchDB将进入reduce阶段。查询服务器将收到reduce函数的列表和一些映射结果,可以在这些结果上应用它们。
CouchDB发送:
[
"reduce",
[
"function(k, v) { return sum(v); }"
],
[
[[1, "699b524273605d5d3e9d4fd0ff2cb272"], 10],
[[2, "c081d0f69c13d2ce2050d684c7ba2843"], 20],
[[null, "foobar"], 3]
]
]
查询服务器回答:
[
true,
[33]
]
请注意,即使视图服务器接收表单中的映射结果 [[key, id-of-doc], value]
,函数可以以不同的形式接收它们。例如,JavaScript查询服务器对键列表和值列表应用函数。
3.1.6. rereduce
¶
命令: |
|
---|---|
争论: |
|
在构建视图时,CouchDB将直接将reduce步骤应用于map步骤的输出,并将rereduce步骤应用于前一个reduce步骤的输出。
CouchDB将向rereduce步骤发送一个reduce函数列表和一个值列表,没有键或文档id。
CouchDB发送:
[
"rereduce",
[
"function(k, v, r) { return sum(v); }"
],
[
33,
55,
66
]
]
查询服务器回答:
[
true,
[154]
]
3.1.7. ddoc
¶
命令: |
|
---|---|
争论: | 对象数组。
|
返回: |
|
此命令分为两个阶段: ddoc 注册和 design function 执行。
在第一阶段,CouchDB将完整的设计文档内容发送到查询服务器,让它通过 _id
用于进一步执行函数的值。
为此,CouchDB发送:
[
"ddoc",
"new",
"_design/temp",
{
"_id": "_design/temp",
"_rev": "8-d7379de23a751dc2a19e5638a7bbc5cc",
"language": "javascript",
"shows": {
"request": "function(doc,req){ return {json: req}; }",
"hello": "function(doc,req){ return {body: 'Hello, ' + (doc || {})._id + '!'}; }"
}
}
]
查询服务器回答:
true
在此之后,设计文档将准备好在第二阶段为子命令提供服务。
注解
各 ddoc
subcommand是根设计文档键,因此它们实际上不是子命令,而是JSON路径中可以处理和处理的第一个元素。
执行子命令的模式很常见:
["ddoc", <design_doc_id>, [<subcommand>, <funcname>], [<argument1>, <argument2>, ...]]
3.1.7.1. shows
¶
警告
Show函数在couchdb3.0中被弃用,并将在couchdb4.0中被删除。
命令: |
|
---|---|
SubCommand: |
|
争论: |
|
返回: | 包含两个元素的数组:
|
执行 show function .
Couchdb发送:
[
"ddoc",
"_design/temp",
[
"shows",
"doc"
],
[
null,
{
"info": {
"db_name": "test",
"doc_count": 8,
"doc_del_count": 0,
"update_seq": 105,
"purge_seq": 0,
"compact_running": false,
"sizes": {
"active": 1535048,
"disk": 15818856,
"external": 15515850
},
"instance_start_time": "1359952188595857",
"disk_format_version": 6,
"committed_update_seq": 105
},
"id": null,
"uuid": "169cb4cc82427cc7322cb4463d0021bb",
"method": "GET",
"requested_path": [
"api",
"_design",
"temp",
"_show",
"request"
],
"path": [
"api",
"_design",
"temp",
"_show",
"request"
],
"raw_path": "/api/_design/temp/_show/request",
"query": {},
"headers": {
"Accept": "*/*",
"Host": "localhost:5984",
"User-Agent": "curl/7.26.0"
},
"body": "undefined",
"peer": "127.0.0.1",
"form": {},
"cookie": {},
"userCtx": {
"db": "api",
"name": null,
"roles": [
"_admin"
]
},
"secObj": {}
}
]
]
查询服务器发送:
[
"resp",
{
"body": "Hello, undefined!"
}
]
3.1.7.2. lists
¶
警告
列表函数在couchdb3.0中被弃用,并将在couchdb4.0中被删除。
命令: |
|
---|---|
SubCommand: |
|
争论: | |
返回: | 数组。详见下文。 |
执行 list function .
的通信协议 list 函数有点复杂,所以让我们用一个例子来说明。
假设我们有一个函数 id-rev 对::
function(doc) {
emit(doc._id, doc._rev);
}
我们想效仿 _all_docs
带有list函数的JSON响应。我们的 第一 列表函数的版本如下所示:
function(head, req){
start({'headers': {'Content-Type': 'application/json'}});
var resp = head;
var rows = [];
while(row=getRow()){
rows.push(row);
}
resp.rows = rows;
return toJSON(resp);
}
列表函数执行期间的整个通信会话可分为三个部分:
初始化
从list函数返回的第一个对象是具有以下结构的数组::
["start", <chunks>, <headers>]
哪里
<chunks>
是将发送到客户端的文本块数组,并且<headers>
是具有响应HTTP标头的对象。此消息从查询服务器发送到
start()
初始化对客户端的HTTP响应的调用:[ "start", [], { "headers": { "Content-Type": "application/json" } } ]
在此之后,list函数可能开始处理视图行。
视图处理
由于视图结果可能非常大,所以在一个命令中传递它的所有行是不明智的。相反,CouchDB可以一个接一个地将视图行发送到查询服务器,允许将视图处理和输出生成作为流处理。
CouchDB发送一个携带视图行数据的特殊数组:
[ "list_row", { "id": "0cb42c267fe32d4b56b3500bc503e030", "key": "0cb42c267fe32d4b56b3500bc503e030", "value": "1-967a00dff5e02add41819138abb3284d" } ]
如果查询服务器对此有要返回的内容,它将返回一个数组
"chunks"
项在头部,数组在尾部。对于本例,它没有要返回的内容,因此响应将是::[ "chunks", [] ]
当没有更多的视图行要处理时,CouchDB发送一个 list_end 表示没有更多数据要发送的消息:
["list_end"]
定案
沟通过程的最后一个阶段是返回 列表尾部 :最后一个数据块。在此之后,列表函数的处理将完成,客户端将收到完整的响应。
对于我们的示例,最后一条消息是:
[ "end", [ "{\"total_rows\":2,\"offset\":0,\"rows\":[{\"id\":\"0cb42c267fe32d4b56b3500bc503e030\",\"key\":\"0cb42c267fe32d4b56b3500bc503e030\",\"value\":\"1-967a00dff5e02add41819138abb3284d\"},{\"id\":\"431926a69504bde41851eb3c18a27b1f\",\"key\":\"431926a69504bde41851eb3c18a27b1f\",\"value\":\"1-967a00dff5e02add41819138abb3284d\"}]}" ] ]
在本例中,我们在查询服务器的一条消息中返回了结果。对于少量的行,这是可以接受的,但是对于大型数据集(可能有数百万个文档或数百万个视图行),这是不可接受的。
让我们修复列表函数并查看通信中的更改:
function(head, req){
start({'headers': {'Content-Type': 'application/json'}});
send('{');
send('"total_rows":' + toJSON(head.total_rows) + ',');
send('"offset":' + toJSON(head.offset) + ',');
send('"rows":[');
if (row=getRow()){
send(toJSON(row));
}
while(row=getRow()){
send(',' + toJSON(row));
}
send(']');
return '}';
}
“等等,什么?”-你想问问。是的,我们将通过字符串块手动构建JSON响应,但是让我们看看日志:
[Wed, 24 Jul 2013 05:45:30 GMT] [debug] [<0.19191.1>] OS Process #Port<0.4444> Output :: ["start",["{","\"total_rows\":2,","\"offset\":0,","\"rows\":["],{"headers":{"Content-Type":"application/json"}}]
[Wed, 24 Jul 2013 05:45:30 GMT] [info] [<0.18963.1>] 127.0.0.1 - - GET /blog/_design/post/_list/index/all_docs 200
[Wed, 24 Jul 2013 05:45:30 GMT] [debug] [<0.19191.1>] OS Process #Port<0.4444> Input :: ["list_row",{"id":"0cb42c267fe32d4b56b3500bc503e030","key":"0cb42c267fe32d4b56b3500bc503e030","value":"1-967a00dff5e02add41819138abb3284d"}]
[Wed, 24 Jul 2013 05:45:30 GMT] [debug] [<0.19191.1>] OS Process #Port<0.4444> Output :: ["chunks",["{\"id\":\"0cb42c267fe32d4b56b3500bc503e030\",\"key\":\"0cb42c267fe32d4b56b3500bc503e030\",\"value\":\"1-967a00dff5e02add41819138abb3284d\"}"]]
[Wed, 24 Jul 2013 05:45:30 GMT] [debug] [<0.19191.1>] OS Process #Port<0.4444> Input :: ["list_row",{"id":"431926a69504bde41851eb3c18a27b1f","key":"431926a69504bde41851eb3c18a27b1f","value":"1-967a00dff5e02add41819138abb3284d"}]
[Wed, 24 Jul 2013 05:45:30 GMT] [debug] [<0.19191.1>] OS Process #Port<0.4444> Output :: ["chunks",[",{\"id\":\"431926a69504bde41851eb3c18a27b1f\",\"key\":\"431926a69504bde41851eb3c18a27b1f\",\"value\":\"1-967a00dff5e02add41819138abb3284d\"}"]]
[Wed, 24 Jul 2013 05:45:30 GMT] [debug] [<0.19191.1>] OS Process #Port<0.4444> Input :: ["list_end"]
[Wed, 24 Jul 2013 05:45:30 GMT] [debug] [<0.19191.1>] OS Process #Port<0.4444> Output :: ["end",["]","}"]]
注意,现在查询服务器通过轻量级块发送响应,如果我们的通信过程非常慢,客户端将看到响应数据如何显示在屏幕上。一块一块,不需要等待完整的结果,就像前面的列表函数一样。
3.1.7.3. updates
¶
命令: |
|
---|---|
SubCommand: |
|
争论: |
|
返回: | 包含以下元素的数组:
|
执行 update function .
CouchDB发送:
[
"ddoc",
"_design/id",
[
"updates",
"nothing"
],
[
null,
{
"info": {
"db_name": "test",
"doc_count": 5,
"doc_del_count": 0,
"update_seq": 16,
"purge_seq": 0,
"compact_running": false,
"sizes": {
"active": 7979745,
"disk": 8056936,
"external": 8024930
},
"instance_start_time": "1374612186131612",
"disk_format_version": 6,
"committed_update_seq": 16
},
"id": null,
"uuid": "7b695cb34a03df0316c15ab529002e69",
"method": "POST",
"requested_path": [
"test",
"_design",
"1139",
"_update",
"nothing"
],
"path": [
"test",
"_design",
"1139",
"_update",
"nothing"
],
"raw_path": "/test/_design/1139/_update/nothing",
"query": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "identity, gzip, deflate, compress",
"Content-Length": "0",
"Host": "localhost:5984"
},
"body": "",
"peer": "127.0.0.1",
"form": {},
"cookie": {},
"userCtx": {
"db": "test",
"name": null,
"roles": [
"_admin"
]
},
"secObj": {}
}
]
]
查询服务器回答:
[
"up",
null,
{"body": "document id wasn't provided"}
]
或者,如果更新成功:
[
"up",
{
"_id": "7b695cb34a03df0316c15ab529002e69",
"hello": "world!"
},
{"body": "document was updated"}
]
3.1.7.4. filters
¶
命令: |
|
---|---|
SubCommand: |
|
争论: |
|
返回: | 两个元素的数组:
|
执行 filter function .
CouchDB发送:
[
"ddoc",
"_design/test",
[
"filters",
"random"
],
[
[
{
"_id": "431926a69504bde41851eb3c18a27b1f",
"_rev": "1-967a00dff5e02add41819138abb3284d",
"_revisions": {
"start": 1,
"ids": [
"967a00dff5e02add41819138abb3284d"
]
}
},
{
"_id": "0cb42c267fe32d4b56b3500bc503e030",
"_rev": "1-967a00dff5e02add41819138abb3284d",
"_revisions": {
"start": 1,
"ids": [
"967a00dff5e02add41819138abb3284d"
]
}
}
],
{
"info": {
"db_name": "test",
"doc_count": 5,
"doc_del_count": 0,
"update_seq": 19,
"purge_seq": 0,
"compact_running": false,
"sizes": {
"active": 7979745,
"disk": 8056936,
"external": 8024930
},
"instance_start_time": "1374612186131612",
"disk_format_version": 6,
"committed_update_seq": 19
},
"id": null,
"uuid": "7b695cb34a03df0316c15ab529023a81",
"method": "GET",
"requested_path": [
"test",
"_changes?filter=test",
"random"
],
"path": [
"test",
"_changes"
],
"raw_path": "/test/_changes?filter=test/random",
"query": {
"filter": "test/random"
},
"headers": {
"Accept": "application/json",
"Accept-Encoding": "identity, gzip, deflate, compress",
"Content-Length": "0",
"Content-Type": "application/json; charset=utf-8",
"Host": "localhost:5984"
},
"body": "",
"peer": "127.0.0.1",
"form": {},
"cookie": {},
"userCtx": {
"db": "test",
"name": null,
"roles": [
"_admin"
]
},
"secObj": {}
}
]
]
查询服务器回答:
[
true,
[
true,
false
]
]
3.1.7.5. views
¶
命令: |
|
---|---|
SubCommand: |
|
争论: | 文档对象数组 |
返回: | 两个元素的数组:
|
1.2 新版功能.
执行 view function 代替过滤器。
行为方式与 filters 命令。
3.1.7.6. validate_doc_update
¶
命令: |
|
---|---|
SubCommand: |
|
争论: | |
返回: |
|
执行 validation function .
CouchDB发送:
[
"ddoc",
"_design/id",
["validate_doc_update"],
[
{
"_id": "docid",
"_rev": "2-e0165f450f6c89dc6b071c075dde3c4d",
"score": 10
},
{
"_id": "docid",
"_rev": "1-9f798c6ad72a406afdbf470b9eea8375",
"score": 4
},
{
"name": "Mike",
"roles": ["player"]
},
{
"admins": {},
"members": []
}
]
]
查询服务器回答:
1
注解
而此命令的唯一有效响应是 true
,若要防止保存文档,查询服务器需要引发错误: forbidden
或 unauthorized
;这些错误会变成正确的 HTTP 403
和 HTTP 401
分别回答。
3.1.7.7. rewrites
¶
命令: |
|
---|---|
SubCommand: |
|
争论: | |
返回: |
|
执行 rewrite function .
CouchDB发送:
[
"ddoc",
"_design/id",
["rewrites"],
[
{
"method": "POST",
"requested_path": [
"test",
"_design",
"1139",
"_update",
"nothing"
],
"path": [
"test",
"_design",
"1139",
"_update",
"nothing"
],
"raw_path": "/test/_design/1139/_update/nothing",
"query": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "identity, gzip, deflate, compress",
"Content-Length": "0",
"Host": "localhost:5984"
},
"body": "",
"peer": "127.0.0.1",
"cookie": {},
"userCtx": {
"db": "test",
"name": null,
"roles": [
"_admin"
]
},
"secObj": {}
}
]
]
查询服务器回答:
[
"ok",
{
"path": "some/path",
"query": {"key1": "value1", "key2": "value2"},
"method": "METHOD",
"headers": {"Header1": "value1", "Header2": "value2"},
"body": ""
}
]
或直接回应:
[
"ok",
{
"headers": {"Content-Type": "text/plain"},
"body": "Welcome!",
"code": 200
}
]
或立即重定向:
[
"ok",
{
"headers": {"Location": "http://example.com/path/"},
"code": 302
}
]
3.1.8. 返回错误¶
当出现错误时,查询服务器可以通过发送一条特殊消息来通知CouchDB,以响应收到的命令。
错误消息阻止进一步执行命令,并向CouchDB返回错误描述。错误逻辑上分为两组:
- Common errors . 这些错误只会中断当前的查询服务器命令,并将错误信息返回给CouchDB实例 没有 正在终止查询服务器进程。
- Fatal errors . 致命错误表示无法恢复的情况。例如,若您的a设计函数无法导入第三方模块,则最好将此类错误视为致命错误并终止整个过程。
3.1.8.1. error
¶
若要引发错误,查询服务器应响应为:
["error", "error_name", "reason why"]
这个 "error_name"
有助于按问题类型对问题进行分类,例如 "value_error"
表示数据不正确, "not_found"
指示缺少的资源和 "type_error"
指示不正确的数据类型。
这个 "reason why"
解释了人类可能会犯的错误。
例如,调用 更新函数 针对不存在的文档可能会生成错误消息:
["error", "not_found", "Update function requires existent document"]
3.1.8.2. forbidden
¶
这个 forbidden 错误被广泛应用于 验证文档更新功能 停止进一步的功能处理并阻止存储新的文档修订。由于这实际上不是一个错误,而是针对用户操作的断言,CouchDB不会将其记录在 "error" 水平,但返回 HTTP 403 Forbidden 响应错误信息对象。
若要引发此错误,查询服务器应响应为:
{"forbidden": "reason why"}
3.1.8.3. unauthorized
¶
这个 unauthorized 错误主要表现为 forbidden 一个,但意思是 请先授权 . 这个小小的差别有助于最终用户了解他们可以做些什么来解决这个问题。类似 forbidden ,CouchDB没有将其记录在 "error" level,但返回一个 HTTP 401 Unauthorized 用错误信息对象响应。
若要引发此错误,查询服务器应响应为:
{"unauthorized": "reason why"}
3.1.9. 登录中¶
在任何时候,查询服务器都可以发送一些信息,这些信息将保存在CouchDB的日志文件中。这是通过发送一个特殊的 log 对象,在单独的行上:
["log", "some message"]
CouchDB不响应,但将接收到的消息写入日志文件:
[Sun, 13 Feb 2009 23:31:30 GMT] [info] [<0.72.0>] Query Server Log Message: some message
这些消息只记录在 info level
.