Internet-Draft | List Pagination | October 2021 |
Watsen, et al. | Expires 28 April 2022 | [Page] |
In some circumstances, instances of YANG modeled "list" and "leaf-list" nodes may contain numerous entries. Retrieval of all the entries can lead to inefficiencies in the server, the client, and the network in between.¶
This document defines a model for list pagination that can be implemented by YANG-driven management protocols such as NETCONF and RESTCONF. The model supports paging over optionally filtered and/or sorted entries. The solution additionally enables servers to constrain query expressions on some "config false" lists or leaf-lists.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 28 April 2022.¶
Copyright (c) 2021 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Simplified BSD License.¶
YANG modeled "list" and "leaf-list" nodes may contain a large number of entries. For instance, there may be thousands of entries in the configuration for network interfaces or access control lists. And time-driven logging mechanisms, such as an audit log or a traffic log, can contain millions of entries.¶
Retrieval of all the entries can lead to inefficiencies in the server, the client, and the network in between. For instance, consider the following:¶
Optimal global resource utilization is obtained when clients are able to cherry-pick just that which is needed to support the application-level business logic.¶
This document defines a generic model for list pagination that can be implemented by YANG-driven management protocols such as NETCONF [RFC6241] and RESTCONF [RFC8040]. Details for how such protocols are updated are outside the scope of this document.¶
The model presented in this document supports paging over optionally filtered and/or sorted entries. Server-side filtering and sorting is ideal as servers can leverage indexes maintained by a backend storage layer to accelerate queries.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
The following terms are defined in [RFC7950] and are not redefined here: client, data model, data tree, feature, extension, module, leaf, leaf-list, and server.¶
Various examples used in this document use a placeholder value for binary data that has been base64 encoded (e.g., "BASE64VALUE="). This placeholder value is used as real base64 encoded structures are often many lines long and hence distracting to the example being presented.¶
This document is compliant with the Network Management Datastore Architecture (NMDA) [RFC8342]. The "ietf-list-pagination" module only defines a YANG extension and augments a couple leafs into a "config false" node defined by the "ietf-system-capabilities" module.¶
The solution presented in this document broadly entails a client sending a query to a server targeting a specific list or leaf-list including optional parameters guiding which entries should be returned.¶
A secondary aspect of this solution entails a client sending a query parameter to a server guiding how descendent lists and leaf-lists should be returned. This parameter may be used on any target node, not just "list" and "leaf-list" nodes.¶
Clients detect a server's support for list pagination via an entry for the "ietf-list-pagination" module (defined in Section 4) in the server's YANG Library [RFC8525] response.¶
Relying on client-provided query parameters ensures servers remain backward compatible with legacy clients.¶
This section is composed of the following subsections:¶
The five query parameters presented this section are listed in processing order. This processing order is logical, efficient, and matches the processing order implemented by database systems, such as SQL.¶
The order is as follows: a server first processes the "where" parameter (see Section 3.1.1), then the "sort-by" parameter (see Section 3.1.2), then the "direction" parameter (see Section 3.1.3), then the "offset" parameter (see Section 3.1.4), and lastly the "limit" parameter (see Section 3.1.5).¶
The allowed values are:¶
Whilst this document primarily regards pagination for a list or leaf-list, it begs the question for how descendant lists and leaf-lists should be handled, which is addressed by the "sublist-limit" query parameter described in this section.¶
The "sublist-limit" parameter limits the number of entries returned for descendent lists and leaf-lists.¶
Any descendent list or leaf-list limited by the "sublist-limit" parameter includes, somewhere in its encoding, a metadata value [RFC7952] called "remaining", a positive integer indicating the number of elements that were not included by the "sublist-limit" parameter, or the value "unknown" in case, e.g., the server determines that counting would be prohibitively expensive.¶
When used on a list node, it only affects the list's descendant nodes, not the list itself, which is only affected by the parameters presented in Section 3.1.¶
Some "config false" lists and leaf-lists may contain an enormous number of entries. For instance, a time-driven logging mechanism, such as an audit log or a traffic log, can contain millions of entries.¶
In such cases, "where" and "sort-by" expressions will not perform well if the server must bring each entry into memory in order to process it.¶
The server's best option is to leverage query-optimizing features (e.g., indexes) built into the backend database holding the dataset.¶
However, arbitrary "where" expressions and "sort-by" node identifiers into syntax supported by the backend database and/or query-optimizers may prove challenging, if not impossible, to implement.¶
Thusly this section introduces mechanisms whereby a server can:¶
Identification of which lists and leaf-lists are constrained occurs in the schema tree, not the data tree. However, as server abilities vary, it is not possible to define constraints in YANG modules defining generic data models.¶
In order to enable servers to identify which lists and leaf-lists are constrained, the solution presented in this document augments the data model defined by the "ietf-system-capabilities" module presented in [I-D.ietf-netconf-notification-capabilities].¶
Specifically, the "ietf-list-pagination" module (see Section 4) augments an empty leaf node called "constrained" into the "per-node-capabilities" node defined in the "ietf-system-capabilities" module.¶
The "constrained" leaf MAY be specified for any "config false" list or leaf-list.¶
When a list or leaf-list is constrained:¶
This section identifies how constraints for "where" filters and "sort-by" expressions are specified. These constraints are valid only if the "constrained" leaf described in the previous section Section 3.3.1 has been set on the immediate ancestor "list" node or, for "leaf-list" nodes, on itself.¶
For "where" filters, an unconstrained XPath expressions may use any node in comparisons. However, efficient mappings to backend databases may support only a subset of the nodes.¶
Similarly, for "sort-by" expressions, efficient sorts may only support a subset of the nodes.¶
In order to enable servers to identify which nodes may be used in comparisons (for both "where" and "sort-by" expressions), the "ietf-list-pagination" module (see Section 4) augments an empty leaf node called "indexed" into the "per-node-capabilities" node defined in the "ietf-system-capabilities" module (see [I-D.ietf-netconf-notification-capabilities]).¶
When a "list" or "leaf-list" node has the "constrained" leaf, only nodes having the "indexed" node may be used in "where" and/or "sort-by" expressions. If no nodes have the "indexed" leaf, when the "constrained" leaf is present, then "where" and "sort-by" expressions are disabled for that list or leaf-list.¶
The "ietf-list-pagination" module is used by servers to indicate that they support pagination on YANG "list" and "leaf-list" nodes, and to provide an ability to indicate which "config false" list and/or "leaf-list" nodes are constrained and, if so, which nodes may be used in "where" and "sort-by" expressions.¶
The following tree diagram [RFC8340] illustrates the "ietf-list-pagination" module:¶
module: ietf-list-pagination augment /sysc:system-capabilities/sysc:datastore-capabilities /sysc:per-node-capabilities: +--ro constrained? empty +--ro indexed? empty¶
Comments:¶
The following example illustrates the "ietf-list-pagination" module's augmentations of the "system-capabilities" data tree. This example assumes the "example-social" module defined in the Appendix A.1 is implemented.¶
=============== NOTE: '\' line wrapping per RFC 8792 ================ <system-capabilities xmlns="urn:ietf:params:xml:ns:yang:ietf-system-capabilities" xmlns:ds="urn:ietf:params:xml:ns:yang:ietf-datastores" xmlns:es="http://example.com/ns/example-social" xmlns:lpg="urn:ietf:params:xml:ns:yang:ietf-list-pagination"> <datastore-capabilities> <datastore>ds:operational</datastore> <per-node-capabilities> <node-selector>/es:audit-logs/es:audit-log</node-selector> <lpg:constrained/> </per-node-capabilities> <per-node-capabilities> <node-selector>/es:audit-logs/es:audit-log/es:timestamp</node-\ selector> <lpg:indexed/> </per-node-capabilities> <per-node-capabilities> <node-selector>/es:audit-logs/es:audit-log/es:member-id</node-\ selector> <lpg:indexed/> </per-node-capabilities> <per-node-capabilities> <node-selector>/es:audit-logs/es:audit-log/es:outcome</node-se\ lector> <lpg:indexed/> </per-node-capabilities> </datastore-capabilities> </system-capabilities>¶
FIXME: valid syntax for 'where'?¶
This YANG module has normative references to [RFC7952] and [I-D.ietf-netconf-notification-capabilities].¶
<CODE BEGINS> file "ietf-list-pagination@2021-10-25.yang"¶
module ietf-list-pagination { yang-version 1.1; namespace "urn:ietf:params:xml:ns:yang:ietf-list-pagination"; prefix lpg; import ietf-yang-types { prefix yang; reference "RFC 6991: Common YANG Data Types"; } import ietf-yang-metadata { prefix md; reference "RFC 7952: Defining and Using Metadata with YANG"; } import ietf-system-capabilities { prefix sysc; reference "draft-ietf-netconf-notification-capabilities: YANG Modules describing Capabilities for Systems and Datastore Update Notifications"; } organization "IETF NETCONF (Network Configuration) Working Group"; contact "WG Web: <http://tools.ietf.org/wg/netconf/> WG List: <mailto:netconf@ietf.org>"; description "This module is used by servers to 1) indicate they support pagination on 'list' and 'leaf-list' resources, 2) define a grouping for each list-pagination parameter, and 3) indicate which 'config false' lists have constrained 'where' and 'sort-by' parameters and how they may be used, if at all. Copyright (c) 2021 IETF Trust and the persons identified as authors of the code. All rights reserved. Redistribution and use in source and binary forms, with or without modification, is permitted pursuant to, and subject to the license terms contained in, the Simplified BSD License set forth in Section 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info). This version of this YANG module is part of RFC XXXX (https://www.rfc-editor.org/info/rfcXXXX); see the RFC itself for full legal notices. The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document are to be interpreted as described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, they appear in all capitals, as shown here."; revision 2021-10-25 { description "Initial revision."; reference "RFC XXXX: List Pagination for YANG-driven Protocols"; } // Annotations md:annotation remaining { type union { type uint32; type enumeration { enum "unknown" { description "Indicates that number of remaining entries is unknown to the server in case, e.g., the server has determined that counting would be prohibitively expensive."; } } } description "This annotation contains the number of elements not included in the result set (a positive value) due to a 'limit' or 'sublist-limit' operation. If no elements were removed, this annotation MUST NOT appear. The minimum value (0), which never occurs in normal operation, is reserved to represent 'unknown'. The maximum value (2^32-1) is reserved to represent any value greater than or equal to 2^32-1 elements."; } // Identities identity list-pagination-error { description "Base identity for list-pagination errors."; } identity offset-out-of-range { base list-pagination-error; description "The 'offset' query parameter value is greater than the number of instances in the target list or leaf-list resource."; } // Groupings grouping where-param-grouping { description "This grouping may be used by protocol-specific YANG modules to define a protocol-specific query parameter."; leaf where { type union { type yang:xpath1.0; type enumeration { enum "unfiltered" { description "Indicates that no entries are to be filtered from the working result-set."; } } } default "unfiltered"; description "The 'where' parameter specifies a boolean expression that result-set entries must match. It is an error if the XPath expression references a node identifier that does not exist in the schema, is optional or conditional in the schema or, for constrained 'config false' lists and leaf-lists, if the node identifier does not point to a node having the 'indexed' extension statement applied to it (see RFC XXXX)."; } } grouping sort-by-param-grouping { description "This grouping may be used by protocol-specific YANG modules to define a protocol-specific query parameter."; leaf sort-by { type union { type string { // An RFC 7950 'descendant-schema-nodeid'. pattern '([0-9a-fA-F]*:)?[0-9a-fA-F]*' + '(/([0-9a-fA-F]*:)?[0-9a-fA-F]*)*'; } type enumeration { enum "none" { description "Indicates that the list or leaf-list's default order is to be used, per the YANG 'ordered-by' statement."; } } } default "none"; description "The 'sort-by' parameter indicates the node in the working result-set (i.e., after the 'where' parameter has been applied) that entries should be sorted by. Sorts are in ascending order (e.g., '1' before '9', 'a' before 'z', etc.). Missing values are sorted to the end (e.g., after all nodes having values)."; } } grouping direction-param-grouping { description "This grouping may be used by protocol-specific YANG modules to define a protocol-specific query parameter."; leaf direction { type enumeration { enum forwards { description "Indicates that entries should be traversed from the first to last item in the working result set."; } enum backwards { description "Indicates that entries should be traversed from the last to first item in the working result set."; } } default "forwards"; description "The 'direction' parameter indicates how the entries in the working result-set (i.e., after the 'sort-by' parameter has been applied) should be traversed."; } } grouping offset-param-grouping { description "This grouping may be used by protocol-specific YANG modules to define a protocol-specific query parameter."; leaf offset { type uint32; default 0; description "The 'offset' parameter indicates the number of entries in the working result-set (i.e., after the 'direction' parameter has been applied) that should be skipped over when preparing the response."; } } grouping limit-param-grouping { description "This grouping may be used by protocol-specific YANG modules to define a protocol-specific query parameter."; leaf limit { type union { type uint32 { range "1..max"; } type enumeration { enum "unbounded" { description "Indicates that the number of entries that may be returned is unbounded."; } } } default "unbounded"; description "The 'limit' parameter limits the number of entries returned from the working result-set (i.e., after the 'offset' parameter has been applied). Any result-set that is limited includes, somewhere in its encoding, the metadata value 'remaining' to indicate the number entries not included in the result set."; } } grouping sublist-limit-param-grouping { description "This grouping may be used by protocol-specific YANG modules to define a protocol-specific query parameter."; leaf sublist-limit { type union { type uint32 { range "1..max"; } type enumeration { enum "unbounded" { description "Indicates that the number of entries that may be returned is unbounded."; } } } default "unbounded"; description "The 'sublist-limit' parameter limits the number of entries for descendent lists and leaf-lists. Any result-set that is limited includes, somewhere in its encoding, the metadata value 'remaining' to indicate the number entries not included in the result set."; } } // Protocol-accessible nodes augment // FIXME: ensure datastore == <operational> "/sysc:system-capabilities/sysc:datastore-capabilities" + "/sysc:per-node-capabilities" { description "Defines some leafs that MAY be used by the server to describe constraints imposed of the 'where' filters and 'sort-by' parameters used in list pagination queries."; leaf constrained { type empty; description "Indicates that 'where' filters and 'sort-by' parameters on the targeted 'config false' list node are constrained. If a list is not 'constrained', then full XPath 1.0 expressions may be used in 'where' filters and all node identifiers are usable by 'sort-by'."; } leaf indexed { type empty; description "Indicates that the targeted descendent node of a 'constrained' list (see the 'constrained' leaf) may be used in 'where' filters and/or 'sort-by' parameters. If a descendent node of a 'constrained' list is not 'indexed', then it MUST NOT be used in 'where' filters or 'sort-by' parameters."; } } }¶
<CODE ENDS>¶
This document registers one URI in the "ns" subregistry of the IETF XML Registry [RFC3688] maintained at https://www.iana.org/assignments/xml-registry/xml-registry.xhtml#ns. Following the format in [RFC3688], the following registration is requested:¶
URI: urn:ietf:params:xml:ns:yang:ietf-list-pagination Registrant Contact: The IESG. XML: N/A, the requested URI is an XML namespace.¶
This document registers one YANG module in the YANG Module Names registry [RFC6020] maintained at https://www.iana.org/assignments/yang-parameters/yang-parameters.xhtml. Following the format defined in [RFC6020], the below registration is requested:¶
name: ietf-list-pagination namespace: urn:ietf:params:xml:ns:yang:ietf-list-pagination prefix: lpg RFC: XXXX¶
Pursuant the template defined in ...FIXME¶
This normative appendix section illustrates every notable edge condition conceived during this document's production.¶
Test inputs and outputs are provided in a manner that is both generic and concise.¶
Management protocol specific documents need only reproduce as many of these tests as necessary to convey pecularities presented by the protocol.¶
Implementations are RECOMMENDED to implement the tests presented in this document, in addition to any tests that may be presented in protocol specific documents.¶
The examples assume the server's operational state as follows.¶
The data is provided in JSON only for convenience and, in particular, has no bearing on the "generic" nature of the tests themselves.¶
{ "example-social:members": { "member": [ { "member-id": "bob", "email-address": "bob@example.com", "password": "$0$1543", "avatar": "BASE64VALUE=", "tagline": "Here and now, like never before.", "posts": { "post": [ { "timestamp": "2020-08-14T03:32:25Z", "body": "Just got in." }, { "timestamp": "2020-08-14T03:33:55Z", "body": "What's new?" }, { "timestamp": "2020-08-14T03:34:30Z", "body": "I'm bored..." } ] }, "favorites": { "decimal64-numbers": ["3.14159", "2.71828"] }, "stats": { "joined": "2020-08-14T03:30:00Z", "membership-level": "standard", "last-activity": "2020-08-14T03:34:30Z" } }, { "member-id": "eric", "email-address": "eric@example.com", "password": "$0$1543", "avatar": "BASE64VALUE=", "tagline": "Go to bed with dreams; wake up with a purpose.", "following": ["alice"], "posts": { "post": [ { "timestamp": "2020-09-17T18:02:04Z", "title": "Son, brother, husband, father", "body": "What's your story?" } ] }, "favorites": { "bits": ["two", "one", "zero"] }, "stats": { "joined": "2020-09-17T19:38:32Z", "membership-level": "pro", "last-activity": "2020-09-17T18:02:04Z" } }, { "member-id": "alice", "email-address": "alice@example.com", "password": "$0$1543", "avatar": "BASE64VALUE=", "tagline": "Every day is a new day", "privacy-settings": { "hide-network": "false", "post-visibility": "public" }, "following": ["bob", "eric", "lin"], "posts": { "post": [ { "timestamp": "2020-07-08T13:12:45Z", "title": "My first post", "body": "Hiya all!" }, { "timestamp": "2020-07-09T01:32:23Z", "title": "Sleepy...", "body": "Catch y'all tomorrow." } ] }, "favorites": { "uint8-numbers": [17, 13, 11, 7, 5, 3], "int8-numbers": [-5, -3, -1, 1, 3, 5] }, "stats": { "joined": "2020-07-08T12:38:32Z", "membership-level": "admin", "last-activity": "2021-04-01T02:51:11Z" } }, { "member-id": "lin", "email-address": "lin@example.com", "password": "$0$1543", "privacy-settings": { "hide-network": "true", "post-visibility": "followers-only" }, "following": ["joe", "eric", "alice"], "stats": { "joined": "2020-07-09T12:38:32Z", "membership-level": "standard", "last-activity": "2021-04-01T02:51:11Z" } }, { "member-id": "joe", "email-address": "joe@example.com", "password": "$0$1543", "avatar": "BASE64VALUE=", "tagline": "Greatness is measured by courage and heart.", "privacy-settings": { "post-visibility": "unlisted" }, "following": ["bob"], "posts": { "post": [ { "timestamp": "2020-10-17T18:02:04Z", "body": "What's your status?" } ] }, "stats": { "joined": "2020-10-08T12:38:32Z", "membership-level": "pro", "last-activity": "2021-04-01T02:51:11Z" } } ] }, "example-social:audit-logs": { "audit-log": [ { "timestamp": "2020-10-11T06:47:59Z", "member-id": "alice", "source-ip": "192.168.0.92", "request": "POST /groups/group/2043", "outcome": true }, { "timestamp": "2020-11-01T15:22:01Z", "member-id": "bob", "source-ip": "192.168.2.16", "request": "POST /groups/group/123", "outcome": false }, { "timestamp": "2020-12-12T21:00:28Z", "member-id": "eric", "source-ip": "192.168.254.1", "request": "POST /groups/group/10", "outcome": true }, { "timestamp": "2021-01-03T06:47:59Z", "member-id": "alice", "source-ip": "192.168.0.92", "request": "POST /groups/group/333", "outcome": true }, { "timestamp": "2021-01-21T10:00:00Z", "member-id": "bob", "source-ip": "192.168.2.16", "request": "POST /groups/group/42", "outcome": true }, { "timestamp": "2020-02-07T09:06:21Z", "member-id": "alice", "source-ip": "192.168.0.92", "request": "POST /groups/group/1202", "outcome": true }, { "timestamp": "2020-02-28T02:48:11Z", "member-id": "bob", "source-ip": "192.168.2.16", "request": "POST /groups/group/345", "outcome": true } ] } }¶
The following sections are presented in reverse query-parameters processing order. Starting with the simplest (limit) and ending with the most complex (where).¶
All the vector tests are presented in a protocol-independent manner. JSON is used only for its conciseness.¶
Noting that "limit" must be a positive number, the edge condition values are '1', '2', num-elements-1, num-elements, and num-elements+1.¶
These vector tests assume the target "/example-social:members/member=alice/favorites/uint8-numbers", which has six values, thus the edge condition "limit" values are: '1', '2', '5', '6', and '7'.¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: - Direction: - Offset: - Limit: 1¶
RESPONSE¶
{ "example-social:uint8-numbers": [17], "@example-social:uint8-numbers": [ { "ietf-list-pagination:remaining": 5 } ] }¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: - Direction: - Offset: - Limit: 2¶
RESPONSE¶
{ "example-social:uint8-numbers": [17, 13], "@example-social:uint8-numbers": [ { "ietf-list-pagination:remaining": 4 } ] }¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: - Direction: - Offset: - Limit: 5¶
RESPONSE¶
{ "example-social:uint8-numbers": [17, 13, 11, 7, 5], "@example-social:uint8-numbers": [ { "ietf-list-pagination:remaining": 1 } ] }¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: - Direction: - Offset: - Limit: 6¶
RESPONSE¶
{ "example-social:uint8-numbers": [17, 13, 11, 7, 5, 3] }¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: - Direction: - Offset: - Limit: 7¶
RESPONSE¶
{ "example-social:uint8-numbers": [17, 13, 11, 7, 5, 3] }¶
Noting that "offset" must be an unsigned number less than or equal to the num-elements, the edge condition values are '0', '1', '2', num-elements-1, num-elements, and num-elements+1.¶
These vector tests again assume the target "/example-social:members/member=alice/favorites/uint8-numbers", which has six values, thus the edge condition "limit" values are: '0', '1', '2', '5', '6', and '7'.¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: - Direction: - Offset: 0 Limit: -¶
RESPONSE¶
{ "example-social:uint8-numbers": [17, 13, 11, 7, 5, 3] }¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: - Direction: - Offset: 1 Limit: -¶
RESPONSE¶
{ "example-social:uint8-numbers": [13, 11, 7, 5, 3] }¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: - Direction: - Offset: 2 Limit: -¶
RESPONSE¶
{ "example-social:uint8-numbers": [11, 7, 5, 3] }¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: - Direction: - Offset: 5 Limit: -¶
RESPONSE¶
{ "example-social:uint8-numbers": [3] }¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: - Direction: - Offset: 6 Limit: -¶
RESPONSE¶
{ "example-social:uint8-numbers": [] }¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: - Direction: - Offset: 7 Limit: -¶
RESPONSE¶
ERROR¶
Noting that "direction" is an enumeration with two values, the edge condition values are each defined enumeration.¶
These vector tests again assume the target "/example-social:members/member=alice/favorites/uint8-numbers". The number of elements is relevant to the edge condition values.¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: - Direction: forwards Offset: - Limit: -¶
RESPONSE¶
{ "example-social:uint8-numbers": [17, 13, 11, 7, 5, 3] }¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: - Direction: backwards Offset: - Limit: -¶
RESPONSE¶
{ "example-social:uint8-numbers": [3, 5, 7, 11, 13, 17] }¶
Noting that the "sort-by" parameter is a node identifier, there is not so much "edge conditions" as there are "interesting conditions". This section provides examples for some interesting conditions.¶
The section provides three examples, one for a "leaf-list" and two for a "list", with one using a direct descendent and the other using an indirect descendent.¶
This example illustrates when the target node's type is a "leaf-list". Note that a single period (i.e., '.') is used to represent the nodes to be sorted.¶
This test again uses the target "/example-social:members/member=alice/favorites/uint8-numbers", which is a leaf-list.¶
REQUEST¶
Target: /example-social:members/member=alice/favorites/uint8-numbers Pagination Parameters: Where: - Sort-by: . Direction: - Offset: - Limit: -¶
RESPONSE¶
{ "example-social:uint8-numbers": [3, 5, 7, 11, 13, 17] }¶
This example illustrates when the target node's type is a "list" and a direct descendent is the "sort-by" node.¶
This vector test uses the target "/example-social:members/member", which is a "list", and the sort-by descendent node "member-id", which is the "key" for the list.¶
REQUEST¶
Target: /example-social:members/member Pagination Parameters: Where: - Sort-by: member-id Direction: - Offset: - Limit: -¶
RESPONSE¶
{ "example-social:member": [ { "member-id": "alice", ... }, { "member-id": "bob", ... }, { "member-id": "eric", ... }, { "member-id": "joe", ... }, { "member-id": "lin", ... } ] }¶
This example illustrates when the target node's type is a "list" and an indirect descendent is the "sort-by" node.¶
This vector test uses the target "/example-social:members/member", which is a "list", and the sort-by descendent node "stats/joined", which is a "config false" descendent leaf. Due to "joined" being a "config false" node, this request would have to target the "member" node in the <operational> datastore.¶
REQUEST¶
Target: /example-social:members/member Pagination Parameters: Where: - Sort-by: stats/joined Direction: - Offset: - Limit: -¶
RESPONSE¶
{ "example-social:member": [ { "member-id": "alice", ... }, { "member-id": "lin", ... }, { "member-id": "bob", ... }, { "member-id": "eric", ... }, { "member-id": "joe", ... } ] }¶
The section provides one example for when the "sort-by" node is not present in the data set.¶
FIXME: need to finish this section...¶
The "where" is an XPath 1.0 expression, there are numerous edge conditions to consider, e.g., the types of the nodes that are targeted by the expression.¶
FIXME¶
This example selects members that have an email address containing "@example.com".¶
REQUEST¶
Target: /example-social:members/member Pagination Parameters: Where: //.[contains (@email-address,'@example.com')] Sort-by: - Direction: - Offset: - Limit: -¶
RESPONSE¶
{ "example-social:member": [ { "member-id": "bob", ... }, { "member-id": "eric", ... }, { "member-id": "alice", ... }, { "member-id": "joe", ... }, { "member-id": "lin", ... } ] }¶
This example selects members that have a posting whose timestamp begins with the string "2020".¶
REQUEST¶
Target: /example-social:members/member Pagination Parameters: Where: //posts//post[starts-with(@timestamp,'2020')] Sort-by: - Direction: - Offset: - Limit: -¶
RESPONSE¶
{ "example-social:member": [ { "member-id": "bob", ... }, { "member-id": "eric", ... }, { "member-id": "alice", ... }, { "member-id": "joe", ... } ] }¶
The "sublist-limit" parameter may be used on any target node.¶
This example uses the target node '/example-social:members/member=alice' in the <intended> datastore.¶
This example sets the sublist-limit value '1', which returns just the first entry for all descendent lists and leaf-lists.¶
Note that, in the response, the "remaining" metadata value is set on the first element of each descendent list and leaf-list having more than one value.¶
REQUEST¶
Datastore: <intended> Target: /example-social:members/member=alice Sublist-limit: 1 Pagination Parameters: Where: - Sort-by: - Direction: - Offset: - Limit: -¶
RESPONSE¶
{ "example-social:member": [ { "member-id": "alice", "email-address": "alice@example.com", "password": "$0$1543", "avatar": "BASE64VALUE=", "tagline": "Every day is a new day", "privacy-settings": { "hide-network": "false", "post-visibility": "public" }, "following": ["bob"], "@following": [ { "ietf-list-pagination:remaining": "2" } ], "posts": { "post": [ { "@": { "ietf-list-pagination:remaining": "1" }, "timestamp": "2020-07-08T13:12:45Z", "title": "My first post", "body": "Hiya all!" } ] }, "favorites": { "uint8-numbers": [17], "int8-numbers": [-5], "@uint8-numbers": [ { "ietf-list-pagination:remaining": "5" } ], "@int8-numbers": [ { "ietf-list-pagination:remaining": "5" } ] } } ] }¶
This example uses the target node <intended>.¶
This example sets the sublist-limit value '1', which returns just the first entry for all descendent lists and leaf-lists.¶
Note that, in the response, the "remaining" metadata value is set on the first element of each descendent list and leaf-list having more than one value.¶
REQUEST¶
Datastore: <intended> Target: / Sublist-limit: 1 Pagination Parameters: Where: - Sort-by: - Direction: - Offset: - Limit: -¶
RESPONSE¶
{ "example-social:members": { "member": [ { "@": { "ietf-list-pagination:remaining": "4" }, "member-id": "bob", "email-address": "bob@example.com", "password": "$0$1543", "avatar": "BASE64VALUE=", "tagline": "Here and now, like never before.", "posts": { "post": [ { "@": { "ietf-list-pagination:remaining": "2" }, "timestamp": "2020-08-14T03:32:25Z", "body": "Just got in." } ] }, "favorites": { "decimal64-numbers": ["3.14159"], "@decimal64-numbers": [ { "ietf-list-pagination:remaining": "1" } ] } } ] } }¶
REQUEST¶
Datastore: <operational> Target: /example-social:members/member Sublist-limit: 1 Pagination Parameters: Where: //stats//joined[starts-with(@timestamp,'2020')] Sort-by: member-id Direction: backwards Offset: 2 Limit: 2¶
RESPONSE¶
{ "example-social:member": [ { "@": { "ietf-list-pagination:remaining": "1" }, "member-id": "eric", "email-address": "eric@example.com", "password": "$0$1543", "avatar": "BASE64VALUE=", "tagline": "Go to bed with dreams; wake up with a purpose.", "following": ["alice"], "posts": { "post": [ { "timestamp": "2020-09-17T18:02:04Z", "title": "Son, brother, husband, father", "body": "What's your story?" } ] }, "favorites": { "bits": ["two"], "@bits": [ { "ietf-list-pagination:remaining": "2" } ] }, "stats": { "joined": "2020-09-17T19:38:32Z", "membership-level": "pro", "last-activity": "2020-09-17T18:02:04Z" } }, { "member-id": "bob", "email-address": "bob@example.com", "password": "$0$1543", "avatar": "BASE64VALUE=", "tagline": "Here and now, like never before.", "posts": { "post": [ { "@": { "ietf-list-pagination:remaining": "2" }, "timestamp": "2020-08-14T03:32:25Z", "body": "Just got in." } ] }, "favorites": { "decimal64-numbers": ["3.14159"], "@decimal64-numbers": [ { "ietf-list-pagination:remaining": "1" } ] }, "stats": { "joined": "2020-08-14T03:30:00Z", "membership-level": "standard", "last-activity": "2020-08-14T03:34:30Z" } } } }¶
The authors would like to thank the following for lively discussions on list (ordered by first name): Andy Bierman, Martin Bjoerklund, and Robert Varga.¶