summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoey Adams2010-08-10 05:10:19 +0000
committerJoey Adams2010-08-10 05:10:19 +0000
commit75b17eeca0394e27759acf2f6b039851a5a28f98 (patch)
tree9c156bf2f70a49ed781a56ebfe89caed4edbc6a0
parentf9f677a465a5746874dc2f2c86cc444ffa28a020 (diff)
Added explicit NULL and '\0' checks, and documented more functions/structures.
-rw-r--r--json.c99
-rw-r--r--json.h6
-rw-r--r--json_io.c17
-rw-r--r--json_op.c22
-rw-r--r--jsonpath.c88
-rw-r--r--jsonpath.h24
6 files changed, 170 insertions, 86 deletions
diff --git a/json.c b/json.c
index 46953fa..3aa6ddd 100644
--- a/json.c
+++ b/json.c
@@ -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.
*/
diff --git a/json.h b/json.h
index 3f53f6a..16654ed 100644
--- a/json.h
+++ b/json.h
@@ -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) */
diff --git a/json_io.c b/json_io.c
index 0380e5e..2510d49 100644
--- a/json_io.c
+++ b/json_io.c
@@ -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");
diff --git a/json_op.c b/json_op.c
index 1b6bf28..f0a3ed2 100644
--- a/json_op.c
+++ b/json_op.c
@@ -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);
diff --git a/jsonpath.c b/jsonpath.c
index 253f2cb..edfa6e8 100644
--- a/jsonpath.c
+++ b/jsonpath.c
@@ -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)
{
diff --git a/jsonpath.h b/jsonpath.h
index 3d873ed..4be498d 100644
--- a/jsonpath.h
+++ b/jsonpath.h
@@ -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;