1/*-------------------------------------------------------------------------
3 * test_json_parser_incremental.c
4 * Test program for incremental JSON parser
6 * Copyright (c) 2024-2025, PostgreSQL Global Development Group
9 * src/test/modules/test_json_parser/test_json_parser_incremental.c
11 * This program tests incremental parsing of json. The input is fed into
12 * the parser in very small chunks. In practice you would normally use
13 * much larger chunks, but doing this makes it more likely that the
14 * full range of increment handling, especially in the lexer, is exercised.
16 * If the "-c SIZE" option is provided, that chunk size is used instead
17 * of the default of 60.
19 * If the "-r SIZE" option is provided, a range of chunk sizes from SIZE down to
20 * 1 are run sequentially. A null byte is printed to the streams after each
23 * If the -s flag is given, the program does semantic processing. This should
24 * just mirror back the json, albeit with white space changes.
26 * If the -o flag is given, the JSONLEX_CTX_OWNS_TOKENS flag is set. (This can
27 * be used in combination with a leak sanitizer; without the option, the parser
28 * may leak memory with invalid JSON.)
30 * The argument specifies the file containing the JSON input.
32 *-------------------------------------------------------------------------
49 #define DEFAULT_CHUNK_SIZE 60
61/* semantic action functions for parser */
87 main(
int argc,
char **argv)
96 bool run_chunk_ranges =
false;
101 bool need_strings =
false;
110 while ((
c =
getopt(argc, argv,
"r:c:os")) != -1)
114 case 'r':
/* chunk range */
115 run_chunk_ranges =
true;
117 case 'c':
/* chunk size */
118 chunk_size = strtou64(
optarg, NULL, 10);
122 case 'o':
/* switch token ownership */
125 case 's':
/* do semantic processing */
148 if ((json_file = fopen(testfile,
PG_BINARY_R)) == NULL)
149 pg_fatal(
"error opening input: %m");
151 if (
fstat(fileno(json_file), &statbuf) != 0)
152 pg_fatal(
"error statting input: %m");
157 * This outer loop only repeats in -r mode. Reset the parse state and
158 * our position in the input file for the inner loop, which performs
159 * the incremental parsing.
161 off_t bytes_left = statbuf.
st_size;
162 size_t to_read = chunk_size;
172 /* We will break when there's nothing left to read */
174 if (bytes_left < to_read)
175 to_read = bytes_left;
177 n_read = fread(buff, 1, to_read, json_file);
178 if (n_read < to_read)
179 pg_fatal(
"error reading input file: %d", ferror(json_file));
184 * Append some trailing junk to the buffer passed to the parser.
185 * This helps us ensure that the parser does the right thing even
186 * if the chunk isn't terminated with a '0円'.
189 bytes_left -= n_read;
224 * In -r mode, separate output with nulls so that the calling test can
225 * split it up, decrement the chunk size, and loop back to the top.
226 * All other modes immediately fall out of the loop and exit.
228 if (run_chunk_ranges)
233 }
while (run_chunk_ranges && (--chunk_size > 0));
243 * The semantic routines here essentially just output the same json, except
244 * for white space. We could pretty print it but there's no need for our
245 * purposes. The result should be able to be fed to any JSON processor
246 * such as jq for validation.
358/* copied from backend code */
365 for (p =
str; *p; p++)
391 if ((
unsigned char) *p <
' ')
406 fprintf(stderr,
" -c chunksize size of piece fed to parser (default 64)\n");
407 fprintf(stderr,
" -o set JSONLEX_CTX_OWNS_TOKENS for leak checking\n");
408 fprintf(stderr,
" -s do semantic processing\n");
static void cleanup(void)
#define fprintf(file, fmt, msg)
JsonParseErrorType pg_parse_json_incremental(JsonLexContext *lex, const JsonSemAction *sem, const char *json, size_t len, bool is_last)
JsonLexContext * makeJsonLexContextIncremental(JsonLexContext *lex, int encoding, bool need_escapes)
void setJsonLexContextOwnsTokens(JsonLexContext *lex, bool owned_by_context)
const JsonSemAction nullSemAction
char * json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
void freeJsonLexContext(JsonLexContext *lex)
void pg_logging_init(const char *argv0)
int getopt(int nargc, char *const *nargv, const char *ostr)
PGDLLIMPORT char * optarg
StringInfo makeStringInfo(void)
void resetStringInfo(StringInfo str)
void appendStringInfo(StringInfo str, const char *fmt,...)
void appendBinaryStringInfo(StringInfo str, const void *data, int datalen)
void appendStringInfoString(StringInfo str, const char *s)
void initStringInfo(StringInfo str)
#define appendStringInfoCharMacro(str, ch)
json_struct_action object_start
static void usage(const char *progname)
static bool lex_owns_tokens
static JsonParseErrorType do_object_field_start(void *state, char *fname, bool isnull)
int main(int argc, char **argv)
static JsonParseErrorType do_array_element_end(void *state, bool isnull)
static JsonParseErrorType do_array_element_start(void *state, bool isnull)
static JsonParseErrorType do_object_end(void *state)
static JsonParseErrorType do_scalar(void *state, char *token, JsonTokenType tokentype)
#define DEFAULT_CHUNK_SIZE
static JsonParseErrorType do_array_start(void *state)
static JsonParseErrorType do_object_start(void *state)
static JsonParseErrorType do_array_end(void *state)
static JsonParseErrorType do_object_field_end(void *state, char *fname, bool isnull)
static void escape_json(StringInfo buf, const char *str)