3.1. 查询服务器协议

A Query Server 是一个外部进程,它通过stdin/stdout上的简单自定义JSON协议与CouchDB通信。它用于处理所有设计函数调用: viewsshowslistsfiltersupdatesvalidate_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

争论:
  • 减少函数源
  • 数组 map function 每个项目以格式表示的结果 [[key, id-of-doc], value]
返回:

具有对值的数组: true 以及另一个结果减少的数组

如果视图定义了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

命令:

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

争论:

对象数组。

  • 第一阶段(ddoc初始化):
    • "new"
    • 设计文件 _id
    • 设计文档对象
  • 第二阶段(设计功能执行):
    • 设计文件 _id
    • 作为对象键数组的函数路径
    • 函数参数数组
返回:
  • 第一阶段(ddoc初始化): true
  • 第二阶段(设计功能执行):根据执行的功能定制对象

此命令分为两个阶段: 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中被删除。

命令:

ddoc

SubCommand:

shows

争论:
  • 文档对象或 null if文件 id 未在请求中指定
  • 请求对象
返回:

包含两个元素的数组:

执行 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中被删除。

命令:

ddoc

SubCommand:

lists

争论:
返回:

数组。详见下文。

执行 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);
}

列表函数执行期间的整个通信会话可分为三个部分:

  1. 初始化

    从list函数返回的第一个对象是具有以下结构的数组::

    ["start", <chunks>, <headers>]
    

    哪里 <chunks> 是将发送到客户端的文本块数组,并且 <headers> 是具有响应HTTP标头的对象。

    此消息从查询服务器发送到 start() 初始化对客户端的HTTP响应的调用:

    [
        "start",
        [],
        {
            "headers": {
                "Content-Type": "application/json"
            }
        }
    ]
    

    在此之后,list函数可能开始处理视图行。

  2. 视图处理

    由于视图结果可能非常大,所以在一个命令中传递它的所有行是不明智的。相反,CouchDB可以一个接一个地将视图行发送到查询服务器,允许将视图处理和输出生成作为流处理。

    CouchDB发送一个携带视图行数据的特殊数组:

    [
        "list_row",
        {
            "id": "0cb42c267fe32d4b56b3500bc503e030",
            "key": "0cb42c267fe32d4b56b3500bc503e030",
            "value": "1-967a00dff5e02add41819138abb3284d"
        }
    ]
    

    如果查询服务器对此有要返回的内容,它将返回一个数组 "chunks" 项在头部,数组在尾部。对于本例,它没有要返回的内容,因此响应将是::

    [
      "chunks",
      []
    ]
    

    当没有更多的视图行要处理时,CouchDB发送一个 list_end 表示没有更多数据要发送的消息:

    ["list_end"]
    
  3. 定案

    沟通过程的最后一个阶段是返回 列表尾部 :最后一个数据块。在此之后,列表函数的处理将完成,客户端将收到完整的响应。

    对于我们的示例,最后一条消息是:

    [
        "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

SubCommand:

updates

争论:
  • 文档对象或 null if文件 id 请求中未指定
  • 请求对象
返回:

包含以下元素的数组:

  • "up"
  • 文档对象或 null 如果什么都不应该被储存
  • 响应对象

执行 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

命令:

ddoc

SubCommand:

filters

争论:
返回:

两个元素的数组:

  • true
  • 输入文档顺序相同的布尔数组。

执行 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

命令:

ddoc

SubCommand:

views

争论:

文档对象数组

返回:

两个元素的数组:

  • true
  • 输入文档顺序相同的布尔数组。

1.2 新版功能.

执行 view function 代替过滤器。

行为方式与 filters 命令。

3.1.7.6. validate_doc_update

命令:

ddoc

SubCommand:

validate_doc_update

争论:
返回:

1

执行 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 ,若要防止保存文档,查询服务器需要引发错误: forbiddenunauthorized ;这些错误会变成正确的 HTTP 403HTTP 401 分别回答。

3.1.7.7. rewrites

命令:

ddoc

SubCommand:

rewrites

争论:
返回:

1

执行 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 .