diff --git a/src/lib/json/CMakeLists.txt b/src/lib/json/CMakeLists.txt index 0f0739620097dc160dc18d69bad4b3bbe130cfb5..51a1f027a8cbcd9a832ea26c24eca52f0dc36fe1 100644 --- a/src/lib/json/CMakeLists.txt +++ b/src/lib/json/CMakeLists.txt @@ -4,3 +4,4 @@ set(lib_sources set_source_files_compile_flags(${lib_sources}) add_library(json_path STATIC ${lib_sources}) +target_link_libraries(json_path misc) diff --git a/src/lib/json/json.c b/src/lib/json/json.c index 3a40bca1295b980c0ec3975a13f5afd4199e418e..430caf221d0beaecb1f1ac005a4a3a0fd644d9eb 100644 --- a/src/lib/json/json.c +++ b/src/lib/json/json.c @@ -30,11 +30,19 @@ */ #include "json.h" + +#include <assert.h> #include <ctype.h> #include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> #include <unicode/uchar.h> #include <unicode/utf8.h> + #include "trivia/util.h" +#include "third_party/PMurHash.h" /** * Read a single symbol from a string starting from an offset. @@ -243,3 +251,271 @@ json_lexer_next_token(struct json_lexer *lexer, struct json_token *token) return json_parse_identifier(lexer, token); } } + +/** + * Compare JSON token keys. + */ +static int +json_token_cmp(const struct json_token *a, const struct json_token *b) +{ + if (a->parent != b->parent) + return a->parent - b->parent; + if (a->type != b->type) + return a->type - b->type; + int ret = 0; + if (a->type == JSON_TOKEN_STR) { + if (a->len != b->len) + return a->len - b->len; + ret = memcmp(a->str, b->str, a->len); + } else if (a->type == JSON_TOKEN_NUM) { + ret = a->num - b->num; + } else { + unreachable(); + } + return ret; +} + +#define MH_SOURCE 1 +#define mh_name _json +#define mh_key_t struct json_token * +#define mh_node_t struct json_token * +#define mh_arg_t void * +#define mh_hash(a, arg) ((*(a))->hash) +#define mh_hash_key(a, arg) ((a)->hash) +#define mh_cmp(a, b, arg) (json_token_cmp(*(a), *(b))) +#define mh_cmp_key(a, b, arg) (json_token_cmp((a), *(b))) +#include "salad/mhash.h" + +static const uint32_t hash_seed = 13U; + +/** + * Compute the hash value of a JSON token. + * + * See the comment to json_tree::hash for implementation details. + */ +static uint32_t +json_token_hash(struct json_token *token) +{ + uint32_t h = token->parent->hash; + uint32_t carry = 0; + const void *data; + int data_size; + if (token->type == JSON_TOKEN_STR) { + data = token->str; + data_size = token->len; + } else if (token->type == JSON_TOKEN_NUM) { + data = &token->num; + data_size = sizeof(token->num); + } else { + unreachable(); + } + PMurHash32_Process(&h, &carry, data, data_size); + return PMurHash32_Result(h, carry, data_size); +} + +int +json_tree_create(struct json_tree *tree) +{ + memset(tree, 0, sizeof(struct json_tree)); + tree->root.hash = hash_seed; + tree->root.type = JSON_TOKEN_END; + tree->root.max_child_idx = -1; + tree->root.sibling_idx = -1; + tree->hash = mh_json_new(); + return tree->hash == NULL ? -1 : 0; +} + +static void +json_token_destroy(struct json_token *token) +{ + assert(token->max_child_idx == -1); + assert(token->sibling_idx == -1); + free(token->children); + token->children = NULL; +} + +void +json_tree_destroy(struct json_tree *tree) +{ + json_token_destroy(&tree->root); + mh_json_delete(tree->hash); +} + +struct json_token * +json_tree_lookup_slowpath(struct json_tree *tree, struct json_token *parent, + const struct json_token *token) +{ + assert(token->type == JSON_TOKEN_STR); + struct json_token key; + key.type = token->type; + key.str = token->str; + key.len = token->len; + key.parent = parent; + key.hash = json_token_hash(&key); + mh_int_t id = mh_json_find(tree->hash, &key, NULL); + if (id == mh_end(tree->hash)) + return NULL; + struct json_token **entry = mh_json_node(tree->hash, id); + assert(entry != NULL && *entry != NULL); + return *entry; +} + +int +json_tree_add(struct json_tree *tree, struct json_token *parent, + struct json_token *token) +{ + assert(json_tree_lookup(tree, parent, token) == NULL); + token->parent = parent; + token->children = NULL; + token->children_capacity = 0; + token->max_child_idx = -1; + token->hash = json_token_hash(token); + int insert_idx = token->type == JSON_TOKEN_NUM ? + (int)token->num : parent->max_child_idx + 1; + /* + * Dynamically grow the children array if necessary. + */ + if (insert_idx >= parent->children_capacity) { + int new_size = parent->children_capacity == 0 ? + 8 : 2 * parent->children_capacity; + while (insert_idx >= new_size) + new_size *= 2; + struct json_token **children = realloc(parent->children, + new_size * sizeof(void *)); + if (children == NULL) + return -1; /* out of memory */ + memset(children + parent->children_capacity, 0, + (new_size - parent->children_capacity) * sizeof(void *)); + parent->children = children; + parent->children_capacity = new_size; + } + /* + * Insert the token into the hash (only for tokens representing + * JSON map entries, see the comment to json_tree::hash). + */ + if (token->type == JSON_TOKEN_STR) { + mh_int_t id = mh_json_put(tree->hash, + (const struct json_token **)&token, NULL, NULL); + if (id == mh_end(tree->hash)) + return -1; /* out of memory */ + } + /* + * Success, now we can insert the new token into its parent's + * children array. + */ + assert(parent->children[insert_idx] == NULL); + parent->children[insert_idx] = token; + parent->max_child_idx = MAX(parent->max_child_idx, insert_idx); + token->sibling_idx = insert_idx; + assert(json_tree_lookup(tree, parent, token) == token); + return 0; +} + +void +json_tree_del(struct json_tree *tree, struct json_token *token) +{ + struct json_token *parent = token->parent; + assert(token->sibling_idx >= 0); + assert(parent->children[token->sibling_idx] == token); + assert(json_tree_lookup(tree, parent, token) == token); + /* + * Clear the entry corresponding to this token in parent's + * children array and update max_child_idx if necessary. + */ + parent->children[token->sibling_idx] = NULL; + token->sibling_idx = -1; + while (parent->max_child_idx >= 0 && + parent->children[parent->max_child_idx] == NULL) + parent->max_child_idx--; + /* + * Remove the token from the hash (only for tokens representing + * JSON map entries, see the comment to json_tree::hash). + */ + if (token->type == JSON_TOKEN_STR) { + mh_int_t id = mh_json_find(tree->hash, token, NULL); + assert(id != mh_end(tree->hash)); + mh_json_del(tree->hash, id, NULL); + } + json_token_destroy(token); + assert(json_tree_lookup(tree, parent, token) == NULL); +} + +struct json_token * +json_tree_lookup_path(struct json_tree *tree, struct json_token *root, + const char *path, int path_len, int index_base) +{ + int rc; + struct json_lexer lexer; + struct json_token token; + struct json_token *ret = root; + json_lexer_create(&lexer, path, path_len, index_base); + while ((rc = json_lexer_next_token(&lexer, &token)) == 0 && + token.type != JSON_TOKEN_END && ret != NULL) { + ret = json_tree_lookup(tree, ret, &token); + } + if (rc != 0 || token.type != JSON_TOKEN_END) + return NULL; + return ret; +} + +/** + * Return the child of @parent following @pos or NULL if @pos + * points to the last child in the children array. If @pos is + * NULL, this function returns the first child. + */ +static struct json_token * +json_tree_child_next(struct json_token *parent, struct json_token *pos) +{ + assert(pos == NULL || pos->parent == parent); + struct json_token **arr = parent->children; + if (arr == NULL) + return NULL; + int idx = pos != NULL ? pos->sibling_idx + 1 : 0; + while (idx <= parent->max_child_idx && arr[idx] == NULL) + idx++; + return idx <= parent->max_child_idx ? arr[idx] : NULL; +} + +/** + * Return the leftmost descendant of the tree rooted at @pos + * or NULL if the tree is empty. + */ +static struct json_token * +json_tree_leftmost(struct json_token *pos) +{ + struct json_token *last; + do { + last = pos; + pos = json_tree_child_next(pos, NULL); + } while (pos != NULL); + return last; +} + +struct json_token * +json_tree_preorder_next(struct json_token *root, struct json_token *pos) +{ + struct json_token *next = json_tree_child_next(pos, NULL); + if (next != NULL) + return next; + while (pos != root) { + next = json_tree_child_next(pos->parent, pos); + if (next != NULL) + return next; + pos = pos->parent; + } + return NULL; +} + +struct json_token * +json_tree_postorder_next(struct json_token *root, struct json_token *pos) +{ + struct json_token *next; + if (pos == NULL) + return json_tree_leftmost(root); + if (pos == root) + return NULL; + next = json_tree_child_next(pos->parent, pos); + if (next != NULL) + return json_tree_leftmost(next); + return pos->parent; +} diff --git a/src/lib/json/json.h b/src/lib/json/json.h index ebb2f1e1f1ab3e49d261fa966f80b8dd24e2e752..ffdd7a90522dee18ac37e86f8acf950bc5d6b7e1 100644 --- a/src/lib/json/json.h +++ b/src/lib/json/json.h @@ -30,11 +30,18 @@ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ +#include <stddef.h> +#include <stdint.h> +#include "trivia/util.h" #ifdef __cplusplus extern "C" { #endif +#ifndef typeof +#define typeof __typeof__ +#endif + /** * Lexer for JSON paths: * <field>, <.field>, <[123]>, <['field']> and their combinations. @@ -66,6 +73,10 @@ enum json_token_type { * Element of a JSON path. It can be either string or number. * String idenfiers are in ["..."] and between dots. Numbers are * indexes in [...]. + * + * May be organized in a tree-like structure reflecting a JSON + * document structure, for more details see the comment to struct + * json_tree. */ struct json_token { enum json_token_type type; @@ -79,6 +90,118 @@ struct json_token { /** Index value. */ int num; }; + /** Pointer to the parent token in a JSON tree. */ + struct json_token *parent; + /** + * Array of child tokens in a JSON tree. Indexes in this + * array match [token.num] index for JSON_TOKEN_NUM type + * and are allocated sequentially for JSON_TOKEN_STR child + * tokens. + */ + struct json_token **children; + /** Allocation size of children array. */ + int children_capacity; + /** + * Max occupied index in the children array or -1 if + * the children array is empty. + */ + int max_child_idx; + /** + * Index of this token in parent's children array or -1 + * if the token was removed from a JSON tree or represents + * a JSON tree root. + */ + int sibling_idx; + /** + * Hash value of the token. Used for lookups in a JSON tree. + * For more details, see the comment to json_tree::hash. + */ + uint32_t hash; +}; + +struct mh_json_t; + +/** + * This structure is used for organizing JSON tokens produced + * by a lexer in a tree-like structure reflecting a JSON document + * structure. + * + * Each intermediate node of the tree corresponds to either + * a JSON map or an array, depending on the key type used by + * its children (JSON_TOKEN_STR or JSON_TOKEN_NUM, respectively). + * Leaf nodes may represent both complex JSON structures and + * final values - it is not mandated by the JSON tree design. + * The root of the tree doesn't have a key and is preallocated + * when the tree is created. + * + * The json_token structure is intrusive by design, i.e. to store + * arbitrary information in a JSON tree, one has to incorporate it + * into a user defined structure. + * + * Example: + * + * struct data { + * ... + * struct json_token token; + * }; + * + * struct json_tree tree; + * json_tree_create(&tree); + * struct json_token *parent = &tree->root; + * + * // Add a path to the tree. + * struct data *data = data_new(); + * struct json_lexer lexer; + * json_lexer_create(&lexer, path, path_len); + * json_lexer_next_token(&lexer, &data->token); + * while (data->token.type != JSON_TOKEN_END) { + * json_tree_add(&tree, parent, &data->token); + * parent = &data->token; + * data = data_new(); + * json_lexer_next_token(&lexer, &data->token); + * } + * data_delete(data); + * + * // Look up a path in the tree. + * data = json_tree_lookup_path(&tree, &tree.root, + * path, path_len); + */ +struct json_tree { + /** + * Preallocated token corresponding to the JSON tree root. + * It doesn't have a key (set to JSON_TOKEN_END). + */ + struct json_token root; + /** + * Hash table that is used to quickly look up a token + * corresponding to a JSON map item given a key and + * a parent token. We store all tokens that have type + * JSON_TOKEN_STR in this hash table. Apparently, we + * don't need to store JSON_TOKEN_NUM tokens as we can + * quickly look them up in the children array anyway. + * + * The hash table uses pair <parent, key> as key, so + * even tokens that happen to have the same key will + * have different keys in the hash. To look up a tree + * node corresponding to a particular path, we split + * the path into tokens and look up the first token + * in the root node and each following token in the + * node returned at the previous step. + * + * We compute a hash value for a token by hashing its + * key using the hash value of its parent as seed. This + * is equivalent to computing hash for the path leading + * to the token. However, we don't need to recompute + * hash starting from the root at each step as we + * descend the tree looking for a specific path, and we + * can start descent from any node, not only from the root. + * + * As a consequence of this hashing technique, even + * though we don't need to store JSON_TOKEN_NUM tokens + * in the hash table, we still have to compute hash + * values for them. + */ + struct mh_json_t *hash; }; /** @@ -112,6 +235,217 @@ json_lexer_create(struct json_lexer *lexer, const char *src, int src_len, int json_lexer_next_token(struct json_lexer *lexer, struct json_token *token); +/** + * Initialize a new empty JSON tree. + * + * Returns 0 on success, -1 on memory allocation error. + */ +int +json_tree_create(struct json_tree *tree); + +/** + * Destroy a JSON tree. + * + * Note, this routine expects the tree to be empty - the caller + * is supposed to use json_tree_foreach_safe() and json_tree_del() + * to dismantle the tree before calling this function. + */ +void +json_tree_destroy(struct json_tree *tree); + +/** + * Internal function, use json_tree_lookup() instead. + */ +struct json_token * +json_tree_lookup_slowpath(struct json_tree *tree, struct json_token *parent, + const struct json_token *token); + +/** + * Look up a token in a JSON tree given a parent token and a key. + * + * Returns NULL if not found. + */ +static inline struct json_token * +json_tree_lookup(struct json_tree *tree, struct json_token *parent, + const struct json_token *token) +{ + struct json_token *ret = NULL; + if (likely(token->type == JSON_TOKEN_NUM)) { + ret = (int)token->num < parent->children_capacity ? + parent->children[token->num] : NULL; + } else { + ret = json_tree_lookup_slowpath(tree, parent, token); + } + return ret; +} + +/** + * Insert a token into a JSON tree at a given position. + * + * The token key (json_token::type and num/str,len) must be set, + * e.g. by json_lexer_next_token(). The caller must ensure that + * no token with the same key is linked to the same parent, e.g. + * with json_tree_lookup(). + * + * Returns 0 on success, -1 on memory allocation error. + */ +int +json_tree_add(struct json_tree *tree, struct json_token *parent, + struct json_token *token); + +/** + * Delete a token from a JSON tree. + * + * The token must be linked to the tree (see json_tree_add()) + * and must not have any children. + */ +void +json_tree_del(struct json_tree *tree, struct json_token *token); + +/** + * Look up a token in a JSON tree by path. + * + * The path is relative to the given root token. In order to + * look up a token by absolute path, pass json_tree::root. + * The index_base is passed to json_lexer used for tokenizing + * the path, see json_lexer_create() for more details. + * + * Returns NULL if no token is found or the path is invalid. + */ +struct json_token * +json_tree_lookup_path(struct json_tree *tree, struct json_token *root, + const char *path, int path_len, int index_base); + +/** + * Perform pre-order traversal in a JSON subtree rooted + * at a given node. + * + * To start a new traversal, pass NULL for @pos. + * Returns @root at the first iteration. + * Returns NULL when traversal is over. + */ +struct json_token * +json_tree_preorder_next(struct json_token *root, struct json_token *pos); + +/** + * Perform post-order traversal in a JSON subtree rooted + * at a given node. + * + * To start a new traversal, pass NULL for @pos. + * Returns @root at the last iteration. + * Returns NULL when traversal is over. + */ +struct json_token * +json_tree_postorder_next(struct json_token *root, struct json_token *pos); + +/** + * Perform pre-order JSON tree traversal. + * Note, this function does not visit the root node. + * See also json_tree_preorder_next(). + */ +#define json_tree_foreach_preorder(pos, root) \ + for ((pos) = json_tree_preorder_next((root), (root)); \ + (pos) != NULL; \ + (pos) = json_tree_preorder_next((root), (pos))) + +/** + * Perform post-order JSON tree traversal. + * Note, this function does not visit the root node. + * See also json_tree_postorder_next(). + */ +#define json_tree_foreach_postorder(pos, root) \ + for ((pos) = json_tree_postorder_next((root), NULL); \ + (pos) != (root); \ + (pos) = json_tree_postorder_next((root), (pos))) + +/** + * Perform post-order JSON tree traversal safe against node removal. + * Note, this function does not visit the root node. + * See also json_tree_postorder_next(). + */ +#define json_tree_foreach_safe(pos, root, tmp) \ + for ((pos) = json_tree_postorder_next((root), NULL); \ + (pos) != (root) && \ + ((tmp) = json_tree_postorder_next((root), (pos))) != NULL; \ + (pos) = (tmp)) + +/** + * Return a container of a json_tree_token. + */ +#define json_tree_entry(token, type, member) \ + container_of((token), type, member) + +/** + * Return a container of a json_tree_token or NULL if @token is NULL. + */ +#define json_tree_entry_safe(token, type, member) \ + ((token) != NULL ? json_tree_entry((token), type, member) : NULL) \ + +/** + * Container-aware wrapper around json_tree_lookup(). + */ +#define json_tree_lookup_entry(tree, parent, token, type, member) ({ \ + struct json_token *ret = json_tree_lookup((tree), (parent), (token));\ + json_tree_entry_safe(ret, type, member); \ +}) + +/** + * Container-aware wrapper around json_tree_lookup_path(). + */ +#define json_tree_lookup_path_entry(tree, root, path, path_len, index_base, \ + type, member) ({ \ + struct json_token *ret = json_tree_lookup_path((tree), (root), \ + (path), (path_len), (index_base)); \ + json_tree_entry_safe(ret, type, member); \ +}) + +/** + * Container-aware wrapper around json_tree_preorder_next(). + */ +#define json_tree_preorder_next_entry(root, pos, type, member) ({ \ + struct json_token *next = json_tree_preorder_next((root), (pos)); \ + json_tree_entry_safe(next, type, member); \ +}) + +/** + * Container-aware wrapper around json_tree_postorder_next(). + */ +#define json_tree_postorder_next_entry(root, pos, type, member) ({ \ + struct json_token *next = json_tree_postorder_next((root), (pos)); \ + json_tree_entry_safe(next, type, member); \ +}) + +/** + * Container-aware version of json_tree_foreach_preorder(). + */ +#define json_tree_foreach_entry_preorder(pos, root, type, member) \ + for ((pos) = json_tree_preorder_next_entry((root), (root), \ + type, member); \ + (pos) != NULL; \ + (pos) = json_tree_preorder_next_entry((root), &(pos)->member, \ + type, member)) + +/** + * Container-aware version of json_tree_foreach_postorder(). + */ +#define json_tree_foreach_entry_postorder(pos, root, type, member) \ + for ((pos) = json_tree_postorder_next_entry((root), NULL, \ + type, member); \ + &(pos)->member != (root); \ + (pos) = json_tree_postorder_next_entry((root), &(pos)->member, \ + type, member)) + +/** + * Container-aware version of json_tree_foreach_safe(). + */ +#define json_tree_foreach_entry_safe(pos, root, type, member, tmp) \ + for ((pos) = json_tree_postorder_next_entry((root), NULL, \ + type, member); \ + &(pos)->member != (root) && \ + ((tmp) = json_tree_postorder_next_entry((root), &(pos)->member, \ + type, member)) != NULL; \ + (pos) = (tmp)) + #ifdef __cplusplus } #endif diff --git a/test/unit/json_path.c b/test/unit/json_path.c index 954c745c862f8722fccf9fe6335b306cffe2e867..aa692e8a0a5314a210aa7ae7581539202c03c062 100644 --- a/test/unit/json_path.c +++ b/test/unit/json_path.c @@ -2,6 +2,7 @@ #include "unit.h" #include "trivia/util.h" #include <string.h> +#include <stdbool.h> #define INDEX_BASE 1 @@ -165,14 +166,253 @@ test_errors() footer(); } +struct test_struct { + int value; + struct json_token node; +}; + +struct test_struct * +test_struct_alloc(struct test_struct *records_pool, int *pool_idx) +{ + struct test_struct *ret = &records_pool[*pool_idx]; + *pool_idx = *pool_idx + 1; + memset(&ret->node, 0, sizeof(ret->node)); + return ret; +} + +struct test_struct * +test_add_path(struct json_tree *tree, const char *path, uint32_t path_len, + struct test_struct *records_pool, int *pool_idx) +{ + int rc; + struct json_lexer lexer; + struct json_token *parent = &tree->root; + json_lexer_create(&lexer, path, path_len, INDEX_BASE); + struct test_struct *field = test_struct_alloc(records_pool, pool_idx); + while ((rc = json_lexer_next_token(&lexer, &field->node)) == 0 && + field->node.type != JSON_TOKEN_END) { + struct json_token *next = + json_tree_lookup(tree, parent, &field->node); + if (next == NULL) { + rc = json_tree_add(tree, parent, &field->node); + fail_if(rc != 0); + next = &field->node; + field = test_struct_alloc(records_pool, pool_idx); + } + parent = next; + } + fail_if(rc != 0 || field->node.type != JSON_TOKEN_END); + /* Release field. */ + *pool_idx = *pool_idx - 1; + return json_tree_entry(parent, struct test_struct, node); +} + +void +test_tree() +{ + header(); + plan(54); + + struct json_tree tree; + int rc = json_tree_create(&tree); + fail_if(rc != 0); + + struct test_struct records[7]; + for (int i = 0; i < 6; i++) + records[i].value = i; + + const char *path1 = "[1][10]"; + const char *path2 = "[1][20].file"; + const char *path3 = "[1][20].file[2]"; + const char *path4 = "[1][20].file[8]"; + const char *path4_copy = "[1][20][\"file\"][8]"; + const char *path_unregistered = "[1][3]"; + + int records_idx = 0; + struct test_struct *node, *node_tmp; + node = test_add_path(&tree, path1, strlen(path1), records, + &records_idx); + is(node, &records[1], "add path '%s'", path1); + + node = test_add_path(&tree, path2, strlen(path2), records, + &records_idx); + is(node, &records[3], "add path '%s'", path2); + + node = test_add_path(&tree, path3, strlen(path3), records, + &records_idx); + is(node, &records[4], "add path '%s'", path3); + + node = test_add_path(&tree, path4, strlen(path4), records, + &records_idx); + is(node, &records[5], "add path '%s'", path4); + + node = test_add_path(&tree, path4_copy, strlen(path4_copy), records, + &records_idx); + is(node, &records[5], "add path '%s'", path4_copy); + + node = json_tree_lookup_path_entry(&tree, &tree.root, path1, + strlen(path1), INDEX_BASE, + struct test_struct, node); + is(node, &records[1], "lookup path '%s'", path1); + + node = json_tree_lookup_path_entry(&tree, &tree.root, path2, + strlen(path2), INDEX_BASE, + struct test_struct, node); + is(node, &records[3], "lookup path '%s'", path2); + + node = json_tree_lookup_path_entry(&tree, &tree.root, path_unregistered, + strlen(path_unregistered), INDEX_BASE, + struct test_struct, node); + is(node, NULL, "lookup unregistered path '%s'", path_unregistered); + + /* Test iterators. */ + struct json_token *token = NULL, *tmp; + const struct json_token *tokens_preorder[] = + {&records[0].node, &records[1].node, &records[2].node, + &records[3].node, &records[4].node, &records[5].node}; + int cnt = sizeof(tokens_preorder)/sizeof(tokens_preorder[0]); + int idx = 0; + + json_tree_foreach_preorder(token, &tree.root) { + if (idx >= cnt) + break; + struct test_struct *t1 = + json_tree_entry(token, struct test_struct, node); + struct test_struct *t2 = + json_tree_entry(tokens_preorder[idx], + struct test_struct, node); + is(token, tokens_preorder[idx], + "test foreach pre order %d: have %d expected of %d", + idx, t1->value, t2->value); + ++idx; + } + is(idx, cnt, "records iterated count %d of %d", idx, cnt); + + const struct json_token *tree_nodes_postorder[] = + {&records[1].node, &records[4].node, &records[5].node, + &records[3].node, &records[2].node, &records[0].node}; + cnt = sizeof(tree_nodes_postorder)/sizeof(tree_nodes_postorder[0]); + idx = 0; + json_tree_foreach_postorder(token, &tree.root) { + if (idx >= cnt) + break; + struct test_struct *t1 = + json_tree_entry(token, struct test_struct, node); + struct test_struct *t2 = + json_tree_entry(tree_nodes_postorder[idx], + struct test_struct, node); + is(token, tree_nodes_postorder[idx], + "test foreach post order %d: have %d expected of %d", + idx, t1->value, t2->value); + ++idx; + } + is(idx, cnt, "records iterated count %d of %d", idx, cnt); + + idx = 0; + json_tree_foreach_safe(token, &tree.root, tmp) { + if (idx >= cnt) + break; + struct test_struct *t1 = + json_tree_entry(token, struct test_struct, node); + struct test_struct *t2 = + json_tree_entry(tree_nodes_postorder[idx], + struct test_struct, node); + is(token, tree_nodes_postorder[idx], + "test foreach safe order %d: have %d expected of %d", + idx, t1->value, t2->value); + ++idx; + } + is(idx, cnt, "records iterated count %d of %d", idx, cnt); + + idx = 0; + json_tree_foreach_entry_preorder(node, &tree.root, struct test_struct, + node) { + if (idx >= cnt) + break; + struct test_struct *t = + json_tree_entry(tokens_preorder[idx], + struct test_struct, node); + is(&node->node, tokens_preorder[idx], + "test foreach entry pre order %d: have %d expected of %d", + idx, node->value, t->value); + idx++; + } + is(idx, cnt, "records iterated count %d of %d", idx, cnt); + + idx = 0; + json_tree_foreach_entry_postorder(node, &tree.root, struct test_struct, + node) { + if (idx >= cnt) + break; + struct test_struct *t = + json_tree_entry(tree_nodes_postorder[idx], + struct test_struct, node); + is(&node->node, tree_nodes_postorder[idx], + "test foreach entry post order %d: have %d expected of %d", + idx, node->value, t->value); + idx++; + } + is(idx, cnt, "records iterated count %d of %d", idx, cnt); + + /* Test record deletion. */ + is(records[3].node.max_child_idx, 7, "max_child_index %d expected of %d", + records[3].node.max_child_idx, 7); + json_tree_del(&tree, &records[5].node); + is(records[3].node.max_child_idx, 1, "max_child_index %d expected of %d", + records[3].node.max_child_idx, 1); + json_tree_del(&tree, &records[4].node); + is(records[3].node.max_child_idx, -1, "max_child_index %d expected of %d", + records[3].node.max_child_idx, -1); + node = json_tree_lookup_path_entry(&tree, &tree.root, path3, + strlen(path3), INDEX_BASE, + struct test_struct, node); + is(node, NULL, "lookup removed path '%s'", path3); + + node = json_tree_lookup_path_entry(&tree, &tree.root, path4, + strlen(path4), INDEX_BASE, + struct test_struct, node); + is(node, NULL, "lookup removed path '%s'", path4); + + node = json_tree_lookup_path_entry(&tree, &tree.root, path2, + strlen(path2), INDEX_BASE, + struct test_struct, node); + is(node, &records[3], "lookup path was not corrupted '%s'", path2); + + const struct json_token *tree_nodes_postorder_new[] = + {&records[1].node, &records[3].node, + &records[2].node, &records[0].node}; + cnt = sizeof(tree_nodes_postorder_new) / + sizeof(tree_nodes_postorder_new[0]); + idx = 0; + json_tree_foreach_entry_safe(node, &tree.root, struct test_struct, + node, node_tmp) { + if (idx >= cnt) + break; + struct test_struct *t = + json_tree_entry(tree_nodes_postorder_new[idx], + struct test_struct, node); + is(&node->node, tree_nodes_postorder_new[idx], + "test foreach entry safe order %d: have %d expected of %d", + idx, node->value, t->value); + json_tree_del(&tree, &node->node); + idx++; + } + is(idx, cnt, "records iterated count %d of %d", idx, cnt); + json_tree_destroy(&tree); + + check_plan(); + footer(); +} + int main() { header(); - plan(2); + plan(3); test_basic(); test_errors(); + test_tree(); int rc = check_plan(); footer(); diff --git a/test/unit/json_path.result b/test/unit/json_path.result index ad6f07e5a97b371633e0bcb2b70c01d72a226e1c..ee23e70c1cd27486075fed7383fd32f30ca9de9c 100644 --- a/test/unit/json_path.result +++ b/test/unit/json_path.result @@ -1,5 +1,5 @@ *** main *** -1..2 +1..3 *** test_basic *** 1..71 ok 1 - parse <[1]> @@ -100,4 +100,62 @@ ok 1 - subtests ok 21 - invalid token for index_base 1 ok 2 - subtests *** test_errors: done *** + *** test_tree *** + 1..54 + ok 1 - add path '[1][10]' + ok 2 - add path '[1][20].file' + ok 3 - add path '[1][20].file[2]' + ok 4 - add path '[1][20].file[8]' + ok 5 - add path '[1][20]["file"][8]' + ok 6 - lookup path '[1][10]' + ok 7 - lookup path '[1][20].file' + ok 8 - lookup unregistered path '[1][3]' + ok 9 - test foreach pre order 0: have 0 expected of 0 + ok 10 - test foreach pre order 1: have 1 expected of 1 + ok 11 - test foreach pre order 2: have 2 expected of 2 + ok 12 - test foreach pre order 3: have 3 expected of 3 + ok 13 - test foreach pre order 4: have 4 expected of 4 + ok 14 - test foreach pre order 5: have 5 expected of 5 + ok 15 - records iterated count 6 of 6 + ok 16 - test foreach post order 0: have 1 expected of 1 + ok 17 - test foreach post order 1: have 4 expected of 4 + ok 18 - test foreach post order 2: have 5 expected of 5 + ok 19 - test foreach post order 3: have 3 expected of 3 + ok 20 - test foreach post order 4: have 2 expected of 2 + ok 21 - test foreach post order 5: have 0 expected of 0 + ok 22 - records iterated count 6 of 6 + ok 23 - test foreach safe order 0: have 1 expected of 1 + ok 24 - test foreach safe order 1: have 4 expected of 4 + ok 25 - test foreach safe order 2: have 5 expected of 5 + ok 26 - test foreach safe order 3: have 3 expected of 3 + ok 27 - test foreach safe order 4: have 2 expected of 2 + ok 28 - test foreach safe order 5: have 0 expected of 0 + ok 29 - records iterated count 6 of 6 + ok 30 - test foreach entry pre order 0: have 0 expected of 0 + ok 31 - test foreach entry pre order 1: have 1 expected of 1 + ok 32 - test foreach entry pre order 2: have 2 expected of 2 + ok 33 - test foreach entry pre order 3: have 3 expected of 3 + ok 34 - test foreach entry pre order 4: have 4 expected of 4 + ok 35 - test foreach entry pre order 5: have 5 expected of 5 + ok 36 - records iterated count 6 of 6 + ok 37 - test foreach entry post order 0: have 1 expected of 1 + ok 38 - test foreach entry post order 1: have 4 expected of 4 + ok 39 - test foreach entry post order 2: have 5 expected of 5 + ok 40 - test foreach entry post order 3: have 3 expected of 3 + ok 41 - test foreach entry post order 4: have 2 expected of 2 + ok 42 - test foreach entry post order 5: have 0 expected of 0 + ok 43 - records iterated count 6 of 6 + ok 44 - max_child_index 7 expected of 7 + ok 45 - max_child_index 1 expected of 1 + ok 46 - max_child_index -1 expected of -1 + ok 47 - lookup removed path '[1][20].file[2]' + ok 48 - lookup removed path '[1][20].file[8]' + ok 49 - lookup path was not corrupted '[1][20].file' + ok 50 - test foreach entry safe order 0: have 1 expected of 1 + ok 51 - test foreach entry safe order 1: have 3 expected of 3 + ok 52 - test foreach entry safe order 2: have 2 expected of 2 + ok 53 - test foreach entry safe order 3: have 0 expected of 0 + ok 54 - records iterated count 4 of 4 +ok 3 - subtests + *** test_tree: done *** *** main: done ***