3.1. 查询服务器协议¶
一个 查询服务器 是一个外部进程,它通过一个简单的自定义 JSON 协议在 stdin/stdout 上与 CouchDB 通信。它用于处理所有设计函数调用:views、shows、lists、filters、updates 和 validate_doc_update。
CouchDB 通过 stdin/stdout 与查询服务器进程通信,使用以换行符结尾的 JSON 消息。发送到查询服务器的消息始终是 array 类型,并遵循模式 [<command>, <*arguments>]\n
。
注意
在文档示例中,我们省略了尾部的 \n
以提高可读性。此外,示例包含格式化的 JSON 值,而实际数据以紧凑模式传输,不包含格式化空格。
3.1.1. reset
¶
- 命令:
reset
- 参数:
查询服务器状态 (可选)
- 返回值:
true
这将重置查询服务器的状态,并使其忘记所有先前的输入。如果适用,这是运行垃圾回收的时机。
CouchDB 发送
["reset"]
查询服务器回答
true
要设置新的查询服务器状态,使用第二个参数和对象数据。
CouchDB 发送
["reset", {"reduce_limit": true, "timeout": 5000}]
查询服务器回答
true
3.1.2. add_lib
¶
- 命令:
add_lib
- 参数:
通过
views/lib
路径指定的 CommonJS 库对象- 返回值:
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
- 参数:
文档对象
- 返回值:
每个应用的 函数 的键值对数组
当视图函数存储在查询服务器中时,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
- 参数:
归约函数源代码
映射函数 结果的数组,其中每个项目以格式
[[key, id-of-doc], value]
表示
- 返回值:
包含一对值的数组:
true
和另一个包含归约结果的数组
如果视图定义了归约函数,则 CouchDB 将进入归约阶段。查询服务器将收到一个归约函数列表和一些映射结果,它可以在这些结果上应用这些函数。
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
¶
- 命令:
rereduce
- 参数:
归约函数源代码
值列表
在构建视图时,CouchDB 将直接将归约步骤应用于映射步骤的输出,并将重新归约步骤应用于先前归约步骤的输出。
CouchDB 将发送一个归约函数列表和一个值列表,其中没有键或文档 ID 到重新归约步骤。
CouchDB 发送
[
"rereduce",
[
"function(k, v, r) { return sum(v); }"
],
[
33,
55,
66
]
]
查询服务器回答
[
true,
[154]
]
3.1.7. ddoc
¶
- 命令:
ddoc
- 参数:
对象数组。
第一阶段(ddoc 初始化)
"new"
设计文档
_id
设计文档对象
第二阶段(设计函数执行)
设计文档
_id
作为对象键数组的函数路径
函数参数数组
- 返回值:
第一阶段(ddoc 初始化):
true
第二阶段(设计函数执行):取决于执行的函数的自定义对象
此命令分两个阶段执行:ddoc 注册和 设计函数 执行。
在第一阶段,CouchDB 将完整的 design document 内容发送到查询服务器,以便它通过 _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
此后,design document 将准备好为第二阶段中的子命令提供服务。
注意
每个 ddoc
子命令都是根 design document 键,因此它们实际上不是子命令,而是 JSON 路径的第一个元素,这些元素可以被处理和处理。
子命令执行的模式是通用的
["ddoc", <design_doc_id>, [<subcommand>, <funcname>], [<argument1>, <argument2>, ...]]
3.1.7.1. shows
¶
警告
Show 函数在 CouchDB 3.0 中已弃用,将在 CouchDB 4.0 中删除。
执行 show 函数。
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
¶
警告
List 函数在 CouchDB 3.0 中已弃用,将在 CouchDB 4.0 中删除。
执行 list 函数。
list 函数的通信协议有点复杂,因此让我们使用一个示例来说明。
假设我们有一个视图函数,它发出 id-rev 对
function(doc) {
emit(doc._id, doc._rev);
}
我们想用 list 函数模拟 _all_docs
JSON 响应。我们 list 函数的第一个版本如下所示
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 函数执行期间的整个通信会话可以分为三个部分
初始化
list 函数返回的第一个对象是一个数组,其结构如下
["start", <chunks>, <headers>]
其中
<chunks>
是一个将发送到客户端的文本块数组,而<headers>
是一个包含响应 HTTP 标头的对象。此消息从查询服务器发送到 CouchDB,在
start()
调用中,该调用初始化对客户端的 HTTP 响应。[ "start", [], { "headers": { "Content-Type": "application/json" } } ]
在此之后,列表函数可能会开始处理视图行。
视图处理
由于视图结果可能非常大,因此最好不要在单个命令中传递所有行。相反,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
¶
- 命令:
ddoc
- 子命令:
更新
- 参数:
文档对象或
null
(如果请求中未指定文档 id)。
- 返回值:
包含三个元素的数组。
"up"
文档对象或
null
(如果不需要存储任何内容)。
执行 更新函数。
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
¶
- 命令:
ddoc
- 子命令:
过滤器
- 参数:
文档对象数组。
- 返回值:
包含两个元素的数组。
true
与输入文档顺序相同的布尔值数组。
执行 过滤器函数。
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
¶
- 命令:
ddoc
- 子命令:
视图
- 参数:
文档对象数组。
- 返回值:
包含两个元素的数组。
true
与输入文档顺序相同的布尔值数组。
1.2 版中的新增功能。
执行 视图函数,以代替过滤器。
与 过滤器 命令的作用相同。
3.1.7.6. validate_doc_update
¶
执行 验证函数。
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
¶
- 命令:
ddoc
- 子命令:
重写
- 参数:
- 返回值:
1
执行 重写函数。
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。错误在逻辑上分为两组。
常见错误。这些错误只会中断当前的查询服务器命令,并将错误信息返回给 CouchDB 实例,不会终止查询服务器进程。
致命错误。致命错误表示无法恢复的状况。例如,如果您的设计函数无法导入第三方模块,最好将此类错误视为致命错误,并终止整个进程。
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.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
级别记录。