diff options
author | Joey Adams | 2010-08-10 05:10:19 +0000 |
---|---|---|
committer | Joey Adams | 2010-08-10 05:10:19 +0000 |
commit | 75b17eeca0394e27759acf2f6b039851a5a28f98 (patch) | |
tree | 9c156bf2f70a49ed781a56ebfe89caed4edbc6a0 | |
parent | f9f677a465a5746874dc2f2c86cc444ffa28a020 (diff) |
Added explicit NULL and '\0' checks, and documented more functions/structures.
-rw-r--r-- | json.c | 99 | ||||
-rw-r--r-- | json.h | 6 | ||||
-rw-r--r-- | json_io.c | 17 | ||||
-rw-r--r-- | json_op.c | 22 | ||||
-rw-r--r-- | jsonpath.c | 88 | ||||
-rw-r--r-- | jsonpath.h | 24 |
6 files changed, 170 insertions, 86 deletions
@@ -35,8 +35,8 @@ skip_whitespace(const char **sp) static char end_parenthesis(JSON * node) { - if (!node) - return 0; + Assert(node != NULL); + switch (node->type) { case JSON_ARRAY: @@ -44,7 +44,8 @@ end_parenthesis(JSON * node) case JSON_OBJECT: return '}'; default: - return 0; + Assert(false); + return '\0'; } } @@ -145,7 +146,7 @@ json_mknumber(const char *number, size_t length) * Indicate that the node's value has changed, * marking ancestors as necessary. * - * Call json_touch_value so that json_encode(, JSONOPT_ORIG) + * Call json_touch_value so that json_encode(..., JSONOPT_ORIG) * will encode the new value rather than using original text. */ void @@ -208,7 +209,7 @@ json_remove(JSON * node) { JSON *parent = node->parent; - if (!parent) + if (parent == NULL) return; Assert(parent->type == JSON_ARRAY || parent->type == JSON_OBJECT); Assert(parent->v.children.count > 0); @@ -333,7 +334,7 @@ json_delete(JSON * node) JSON *parent, *next; - if (!node) + if (node == NULL) return; /* Remove node from parent (if it has one). */ @@ -349,14 +350,14 @@ advance: free_node(node); node = next; - if (node) + if (node != NULL) { goto descend; } else { node = parent; - if (node) + if (node != NULL) goto advance; else return; @@ -368,11 +369,13 @@ advance: static JSON *decode_leaf(const char **sp); static JSON *decode_number(const char **sp); -char *json_decode_string(const char **sp, size_t *length, bool strict); -/* json_decode_string has a different signature than its friends - because it's also used to parse object member keys. - It's also useful outside of json.c, such as in jsonpath.c . */ +/* + * json_decode_string has a different signature than its friends + * because it's also used to parse object member keys. + * It's also useful outside of json.c, such as in jsonpath.c . + */ +char *json_decode_string(const char **sp, size_t *length, bool strict); /* * json_validate @@ -387,7 +390,7 @@ json_validate(const char *str) { JSON *node = json_decode(str); - if (!node) + if (node == NULL) return false; json_delete(node); return true; @@ -431,7 +434,7 @@ json_decode(const char *str) struct json_orig orig; bool expect_endp; - if (!str) + if (str == NULL) return NULL; Assert(utf8_validate(str, strlen(str))); @@ -456,14 +459,14 @@ item: /* Expect a value (set expect_endp before goto goto endp; } - if (parent && parent->type == JSON_OBJECT) + if (parent != NULL && parent->type == JSON_OBJECT) { /* Parse member key string. */ orig.key_left_space.end = s; orig.key.start = s; key = json_decode_string(&s, &key_length, true); - if (!key) + if (key == NULL) goto failed; orig.key.end = s; @@ -492,7 +495,7 @@ item: /* Expect a value (set expect_endp before goto orig.value.start = s; node = decode_leaf(&s); - if (!node) + if (node == NULL) { if (*s == '[') node = json_mknode(JSON_ARRAY); @@ -528,7 +531,7 @@ item: /* Expect a value (set expect_endp before goto node->orig = orig; - if (parent) + if (parent != NULL) json_append_notouch(parent, node); else root = node; @@ -545,7 +548,7 @@ item: /* Expect a value (set expect_endp before goto goto item; } - if (parent) + if (parent != NULL) goto comma_endp; else goto end; @@ -583,18 +586,18 @@ endp: /* Handle an end bracket/brace */ node->orig.right_space.end = s; - if (parent) + if (parent != NULL) goto comma_endp; else goto end; end: /* Expect end of text */ - if (*s) + if (*s != '\0') goto failed; return node; failed: /* Handle failure */ - if (key) + if (key != NULL) pfree(key); json_delete(root); return NULL; @@ -616,7 +619,7 @@ decode_leaf(const char **sp) size_t length; char *str = json_decode_string(sp, &length, true); - if (str) + if (str != NULL) { JSON *node = json_mknode(JSON_STRING); @@ -629,17 +632,17 @@ decode_leaf(const char **sp) } if ((c >= '0' && c <= '9') || c == '-') return decode_number(sp); - if (!strncmp(*sp, "true", 4)) + if (strncmp(*sp, "true", 4) == 0) { (*sp) += 4; return json_mkbool(true); } - if (!strncmp(*sp, "false", 5)) + if (strncmp(*sp, "false", 5) == 0) { (*sp) += 5; return json_mkbool(false); } - if (!strncmp(*sp, "null", 4)) + if (strncmp(*sp, "null", 4) == 0) { (*sp) += 4; return json_mknode(JSON_NULL); @@ -769,7 +772,7 @@ json_decode_string(const char **sp, size_t *length, bool strict) return NULL; } - while (*s && *s != quote) + while (*s != '\0' && *s != quote) { unsigned char c = *s++; unsigned int uc; @@ -849,7 +852,7 @@ json_decode_string(const char **sp, size_t *length, bool strict) appendStringInfoChar(&ret, c); } - if (!*s++) + if (*s++ != quote) goto failed; *length = ret.len; @@ -906,6 +909,10 @@ json_text_type(const char *str, size_t nbytes) /****************************** Encoding *****************************/ +/* + * encode_string + * Variant of json_encode_string that writes its output to a StringInfo. + */ static void encode_string(StringInfo out, const char *string, size_t length, char quote, bool escape_unicode) @@ -922,33 +929,33 @@ encode_string(StringInfo out, const char *string, size_t length, char quote, while (s < e) { unsigned char c = *s++; - unsigned char e; + unsigned char endchar; switch (c) { case '\\': - e = '\\'; + endchar = '\\'; break; case '\b': - e = 'b'; + endchar = 'b'; break; case '\f': - e = 'f'; + endchar = 'f'; break; case '\n': - e = 'n'; + endchar = 'n'; break; case '\r': - e = 'r'; + endchar = 'r'; break; case '\t': - e = 't'; + endchar = 't'; break; default: { if (c == quote) { - e = quote; + endchar = quote; break; } if (c < 0x1F || (c >= 0x80 && escape_unicode)) @@ -985,13 +992,13 @@ encode_string(StringInfo out, const char *string, size_t length, char quote, appendStringInfoString(out, txt); continue; /* Skip backslash-encoding code below. */ } - e = 0; + endchar = '\0'; } } - appendStringInfoChar(out, e ? '\\' : c); - if (e) - appendStringInfoChar(out, e); + appendStringInfoChar(out, endchar ? '\\' : c); + if (endchar != '\0') + appendStringInfoChar(out, endchar); } appendStringInfoChar(out, quote); @@ -1004,7 +1011,7 @@ encode_number(StringInfo out, const char *string) const char *start, *end; - if (!string) + if (string == NULL) return false; /* Validate number, trimming whitespace. */ @@ -1038,7 +1045,7 @@ static bool json_encode_recurse(JSON * node, json_encode_ctx * ctx); * Encode a JSON node. * * The JSONOPT_ESCAPE_UNICODE option may only be used - * if the strings in the JSON tree are UTF-8-encoded. + * if the strings in the JSON tree are UTF-8-encoded. */ char * json_encode(JSON * node, int options) @@ -1112,7 +1119,7 @@ json_encode_recurse(JSON * node, json_encode_ctx * ctx) json_foreach(child, node) { json_encode_recurse(child, ctx); - if (child->next) + if (child->next != NULL) appendStringInfoChar(&ctx->str, ','); } @@ -1146,7 +1153,7 @@ json_encode_recurse(JSON * node, json_encode_ctx * ctx) json_encode_recurse(node, ctx); - if (node->next) + if (node->next != NULL) appendStringInfoChar(&ctx->str, ','); } @@ -1156,7 +1163,7 @@ json_encode_recurse(JSON * node, json_encode_ctx * ctx) return false; } - if (txt) + if (txt != NULL) appendStringInfoString(&ctx->str, txt); } @@ -1178,7 +1185,7 @@ json_encode_recurse(JSON * node, json_encode_ctx * ctx) * in invalid JSON. * * If escape_unicode is true, str must be valid UTF-8. - * In any case, str may contain null characters (hence the length argument). + * In any case, str may contain null characters (hence the length argument). * * quote must not be a backslash. */ @@ -82,7 +82,11 @@ struct JSON size_t length; } string; - /* JSON_NUMBER */ + /* + * JSON_NUMBER + * + * Numbers are encoded as strings to avoid unnecessary precision loss. + */ char *number; /* JSON_ARRAY or JSON_OBJECT (children) */ @@ -179,7 +179,7 @@ from_json(PG_FUNCTION_ARGS) pfree(cstring_in); - if (cstring_out) + if (cstring_out != NULL) { vardata_out = utf8_cstring_to_text(cstring_out); PG_RETURN_TEXT_P(vardata_out); @@ -224,6 +224,13 @@ to_json(PG_FUNCTION_ARGS) datum_to_json(PG_GETARG_DATUM(0), typeInfo, target_type))); } +/* + * decide_json_type + * + * Given a type and its corresponding typcategory, determine what + * JSON type to encode it to. If it's not possible to encode the type + * (or if support for the type isn't implemented yet), report an error. + */ static json_type decide_json_type(Oid type, char category) { @@ -290,7 +297,7 @@ datum_to_json(Datum datum, TypeInfo *typeInfo, json_type target_type) node = json_mknumber(cstring_utf8, strlen(cstring_utf8)); encoded_utf8 = json_encode(node, 0); - if (!encoded_utf8) + if (encoded_utf8 == NULL) { /* * Currently, we assume that output conversion for all types @@ -378,7 +385,7 @@ array_to_json(Datum datum) for (i = 0; i < nitems; i++) { - if (bitmap && (*bitmap & bitmask) == 0) + if (bitmap != NULL && (*bitmap & bitmask) == 0) { values[i] = NULL; } @@ -391,7 +398,7 @@ array_to_json(Datum datum) values[i] = datum_to_json(elt, &element_typeinfo, target_type); } - if (bitmap) + if (bitmap != NULL) { bitmask <<= 1; if (bitmask == 0x100) @@ -444,7 +451,7 @@ build_array_string_recurse(StringInfo string, const char ***values, { const char *value = *(*values)++; - if (value) + if (value != NULL) appendStringInfoString(string, value); else appendStringInfoString(string, "null"); @@ -61,7 +61,7 @@ json_get_type(PG_FUNCTION_ARGS) report_corrupt_json(); label_oids = FN_EXTRA(); - if (!label_oids) + if (label_oids == NULL) { label_oids = FN_EXTRA_ALLOC(JSON_TYPE_COUNT * sizeof(Oid)); getEnumLabelOids("json_type_t", enum_labels, label_oids, JSON_TYPE_COUNT); @@ -89,6 +89,16 @@ json_condense(PG_FUNCTION_ARGS) PG_RETURN_JSON_P(utf8_cstring_to_text(condensed)); } +/* + * json_path_base + * Given the function call info of a PostgreSQL function + * with arguments (JSON, jsonpath TEXT [, ...]), + * match the JSONPath string given in the second argument + * against the JSON tree given in the first argument. + * + * This returns a List of JPRef* items, just like jp_match. + * jpref_encode() is used to convert them to JSON-formatted strings. + */ static List * json_path_base(FunctionCallInfo fcinfo) { @@ -98,11 +108,11 @@ json_path_base(FunctionCallInfo fcinfo) JSON *json = json_decode(json_string); List *result_list; - if (!jpath) + if (jpath == NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid JSONPath expression"))); - if (!json) + if (json == NULL) report_corrupt_json(); result_list = jp_match(jpath, json); @@ -162,7 +172,7 @@ json_set(PG_FUNCTION_ARGS) char *result; jsontype *result_text; - if (!jpath) + if (jpath == NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid JSONPath expression"))); @@ -207,7 +217,7 @@ json_path(PG_FUNCTION_ARGS) funcctx = SRF_PERCALL_SETUP(); result = funcctx->user_fctx; - if (result) + if (result != NULL) { char *json_string; jsontype *json_text; @@ -235,7 +245,7 @@ parse_json_path(PG_FUNCTION_ARGS) JSONPath *jpath = jp_parse(string); char *normalized; - if (!jpath) + if (jpath == NULL) PG_RETURN_NULL(); normalized = jp_show(jpath); @@ -158,6 +158,11 @@ mkRefChar(const char *bytes, size_t length) return ref; } +/* + * jp_show + * Unparse a JSONPath expression. This is used by parse_json_path + * to stringify successfully parsed JSONPaths. + */ char * jp_show(JSONPath * jp) { @@ -201,6 +206,12 @@ jp_show(JSONPath * jp) return string->data; } +/* + * Parse a long starting at *s . + * + * On success, return true and update *s to point to the end of the number. + * On failure, return false and leave *s untouched. + */ static bool parse_long(const char **s, long *out) { @@ -215,6 +226,13 @@ parse_long(const char **s, long *out) return true; } +/* + * jp_parse + * Parse a JSONPath expression (into a List of jp_element items). + * + * TODO: Get rid of all those gotos. The parser uses constant space, + * so there's no chance of a stack overflow anyway. + */ JSONPath * jp_parse(const char *pattern) { @@ -232,7 +250,7 @@ jp_parse(const char *pattern) skip_spaces(&s); /* pattern may not be empty */ - if (!*s) + if (*s == '\0') return NULL; jp = lappend(jp, mkRoot()); @@ -346,7 +364,7 @@ identifier: if (*s == '(') { - if (end - start == 4 && !memcmp(start, "char", 4)) + if (end - start == 4 && memcmp(start, "char", 4) == 0) { s++; skip_spaces(&s); @@ -364,7 +382,7 @@ identifier: string: key = json_decode_string(&s, &key_length, false); - if (!key) + if (key == NULL) goto failed; jp = lappend(jp, mkKeySubscript(key, key_length, recursive_descent)); @@ -420,7 +438,7 @@ static void match_recurse(void on_match(void *ctx, JPRef * ref), void *ctx, switch (elem->type) { case JP_WILDCARD: - if (json) + if (json != NULL) { json_foreach(child, json) match_recurse(on_match, ctx, lnext(path), mkRefNode(child)); @@ -428,43 +446,36 @@ static void match_recurse(void on_match(void *ctx, JPRef * ref), void *ctx, break; case JP_INDEX_SUBSCRIPT: - if (json && json->type == JSON_ARRAY) + if (json != NULL && json->type == JSON_ARRAY && + elem->data.index >= 0 && + (size_t) elem->data.index < json->v.children.count) { size_t i; - size_t index = elem->data.index; - /* - * Note: elem->data.index is signed (long), while index is - * unsigned (size_t). - */ - - if (elem->data.index >= 0 && index < json->v.children.count) + for (child = json->v.children.head, i = 0; + child != NULL && i < (size_t) elem->data.index; + child = child->next, i++) { - for (child = json->v.children.head, i = 0; - child != NULL && i < index; - child = child->next, i++) - { - } + } - /* - * If this fails, it means json->v.children.count was - * greater than the actual number of children. - */ - Assert(i == index && child != NULL); + /* + * If this fails, it means json->v.children.count was greater + * than the actual number of children. + */ + Assert(i == elem->data.index && child != NULL); - match_recurse(on_match, ctx, lnext(path), mkRefNode(child)); - } + match_recurse(on_match, ctx, lnext(path), mkRefNode(child)); } break; case JP_KEY_SUBSCRIPT: - if (json && json->type == JSON_OBJECT) + if (json != NULL && json->type == JSON_OBJECT) { json_foreach(child, json) { if (child->key != NULL && child->key_length == elem->data.key.length && - !memcmp(child->key, elem->data.key.ptr, child->key_length)) + memcmp(child->key, elem->data.key.ptr, child->key_length) == 0) { match_recurse(on_match, ctx, lnext(path), mkRefNode(child)); } @@ -473,7 +484,8 @@ static void match_recurse(void on_match(void *ctx, JPRef * ref), void *ctx, break; case JP_CALL_CHAR: - if (json && json->type == JSON_STRING && elem->data.index >= 0) + if (json != NULL && json->type == JSON_STRING && + elem->data.index >= 0) { const char *sub_start; size_t sub_bytes; @@ -497,7 +509,7 @@ static void match_recurse(void on_match(void *ctx, JPRef * ref), void *ctx, default:; } - if (elem->recursive_descent && json) + if (elem->recursive_descent && json != NULL) { json_foreach(child, json) { @@ -513,6 +525,13 @@ jp_match_callback(List **results, JPRef * ref) *results = lappend(*results, ref); } +/* + * jp_match + * Match a parsed JSONPath expression against a JSON tree, + * yielding a List of JPRef* items. + * + * To convert the JPRef* items to JSON-formatted strings, use jpref_encode. + */ List * jp_match(JSONPath * jp, JSON * json) { @@ -538,6 +557,15 @@ jp_set_callback(JSON * value, JPRef * ref) } } +/* + * jp_set + * Set all elements that match a parsed JSONPath expression + * in a JSON tree to a new value. + * + * Note that jp_set uses json_replace_value so it doesn't have to deep-copy + * on every assignment if @value is a tree. This means that parent pointers + * of the resulting tree will not be trustworthy. + */ void jp_set(JSONPath * jp, JSON * json, JSON * value) { @@ -546,6 +574,10 @@ jp_set(JSONPath * jp, JSON * json, JSON * value) match_recurse((void *) jp_set_callback, value, lc, mkRefNode(json)); } +/* + * jpref_encode + * Convert a JPRef to a JSON-formatted string. + */ char * jpref_encode(JPRef * ref) { @@ -3,6 +3,16 @@ * jsonpath.h * JSONPath implementation routines for JSON data type support. * + * The "JSONPath" implemented here is similar to, but not exactly the same as, + * the JSONPath described at http://goessner.net/articles/JsonPath/ . + * The main differences are stronger subscripting rules, special methods + * via function-call notation (currently, the only one provided is .char()), + * and that the '$' at the beginning of a JSONPath expression is optional. + * Also, array indices as a set (e.g. [0,1]) and filters/scripts are currently + * not implemented. Array indices are a planned feature. True filters + * would require an honest-to-goodness JavaScript engine, so perhaps + * someone will write a module for that in the future. + * * Copyright (c) 2010, PostgreSQL Global Development Group * Written by Joey Adams <[email protected]>. * @@ -25,6 +35,13 @@ typedef enum JP_CALL_CHAR } jp_element_type; +/* + * A jp_element is a single piece of a JSONPath expression + * (e.g. [3], .foo, .char(3), ..*). It represents subscripting + * down one level in a JSON tree, or invoking a special method + * (like .char() ). However, if recursive_descent is set to true, + * the subscript applies to a value and all of its descendants. + */ typedef struct { jp_element_type type; @@ -49,6 +66,13 @@ typedef enum JP_REF_CHAR } JPRefType; +/* + * A JPRef is a reference to some part of a JSON tree, + * typically a value (but not always). + * + * JPRef* is really a "subclass" of JSON* . In the future, this structure + * will likely be merged into the JSON structure to improve performance. + */ typedef struct { JPRefType type; |