Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * test_json_parser_incremental.c
4 : * Test program for incremental JSON parser
5 : *
6 : * Copyright (c) 2024-2025, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * src/test/modules/test_json_parser/test_json_parser_incremental.c
10 : *
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.
15 : * If the "-c SIZE" option is provided, that chunk size is used instead
16 : * of the default of 60.
17 : *
18 : * If the -s flag is given, the program does semantic processing. This should
19 : * just mirror back the json, albeit with white space changes.
20 : *
21 : * If the -o flag is given, the JSONLEX_CTX_OWNS_TOKENS flag is set. (This can
22 : * be used in combination with a leak sanitizer; without the option, the parser
23 : * may leak memory with invalid JSON.)
24 : *
25 : * The argument specifies the file containing the JSON input.
26 : *
27 : *-------------------------------------------------------------------------
28 : */
29 :
30 : #include "postgres_fe.h"
31 :
32 : #include <stdio.h>
33 : #include <sys/types.h>
34 : #include <sys/stat.h>
35 : #include <unistd.h>
36 :
37 : #include "common/jsonapi.h"
38 : #include "common/logging.h"
39 : #include "lib/stringinfo.h"
40 : #include "mb/pg_wchar.h"
41 : #include "pg_getopt.h"
42 :
43 : #define BUFSIZE 6000
44 : #define DEFAULT_CHUNK_SIZE 60
45 :
46 : typedef struct DoState
47 : {
48 : JsonLexContext *lex;
49 : bool elem_is_first;
50 : StringInfo buf;
51 : } DoState;
52 :
53 : static void usage(const char *progname);
54 : static void escape_json(StringInfo buf, const char *str);
55 :
56 : /* semantic action functions for parser */
57 : static JsonParseErrorType do_object_start(void *state);
58 : static JsonParseErrorType do_object_end(void *state);
59 : static JsonParseErrorType do_object_field_start(void *state, char *fname, bool isnull);
60 : static JsonParseErrorType do_object_field_end(void *state, char *fname, bool isnull);
61 : static JsonParseErrorType do_array_start(void *state);
62 : static JsonParseErrorType do_array_end(void *state);
63 : static JsonParseErrorType do_array_element_start(void *state, bool isnull);
64 : static JsonParseErrorType do_array_element_end(void *state, bool isnull);
65 : static JsonParseErrorType do_scalar(void *state, char *token, JsonTokenType tokentype);
66 :
67 : static JsonSemAction sem = {
68 : .object_start = do_object_start,
69 : .object_end = do_object_end,
70 : .object_field_start = do_object_field_start,
71 : .object_field_end = do_object_field_end,
72 : .array_start = do_array_start,
73 : .array_end = do_array_end,
74 : .array_element_start = do_array_element_start,
75 : .array_element_end = do_array_element_end,
76 : .scalar = do_scalar
77 : };
78 :
79 : static bool lex_owns_tokens = false;
80 :
81 : int
82 3928 : main(int argc, char **argv)
83 : {
84 : char buff[BUFSIZE];
85 : FILE *json_file;
86 : JsonParseErrorType result;
87 : JsonLexContext *lex;
88 : StringInfoData json;
89 : int n_read;
90 3928 : size_t chunk_size = DEFAULT_CHUNK_SIZE;
91 : struct stat statbuf;
92 : off_t bytes_left;
93 3928 : const JsonSemAction *testsem = &nullSemAction;
94 : char *testfile;
95 : int c;
96 3928 : bool need_strings = false;
97 3928 : int ret = 0;
98 :
99 3928 : pg_logging_init(argv[0]);
100 :
101 3928 : lex = calloc(1, sizeof(JsonLexContext));
102 3928 : if (!lex)
103 0 : pg_fatal("out of memory");
104 :
105 13748 : while ((c = getopt(argc, argv, "c:os")) != -1)
106 : {
107 5892 : switch (c)
108 : {
109 3920 : case 'c': /* chunksize */
110 3920 : chunk_size = strtou64(optarg, NULL, 10);
111 3920 : if (chunk_size > BUFSIZE)
112 0 : pg_fatal("chunk size cannot exceed %d", BUFSIZE);
113 3920 : break;
114 1964 : case 'o': /* switch token ownership */
115 1964 : lex_owns_tokens = true;
116 1964 : break;
117 8 : case 's': /* do semantic processing */
118 8 : testsem = &sem;
119 8 : sem.semstate = palloc(sizeof(struct DoState));
120 8 : ((struct DoState *) sem.semstate)->lex = lex;
121 8 : ((struct DoState *) sem.semstate)->buf = makeStringInfo();
122 8 : need_strings = true;
123 8 : break;
124 : }
125 9820 : }
126 :
127 3928 : if (optind < argc)
128 : {
129 3920 : testfile = argv[optind];
130 3920 : optind++;
131 : }
132 : else
133 : {
134 8 : usage(argv[0]);
135 8 : exit(1);
136 : }
137 :
138 3920 : makeJsonLexContextIncremental(lex, PG_UTF8, need_strings);
139 3920 : setJsonLexContextOwnsTokens(lex, lex_owns_tokens);
140 3920 : initStringInfo(&json);
141 :
142 3920 : if ((json_file = fopen(testfile, PG_BINARY_R)) == NULL)
143 0 : pg_fatal("error opening input: %m");
144 :
145 3920 : if (fstat(fileno(json_file), &statbuf) != 0)
146 0 : pg_fatal("error statting input: %m");
147 :
148 3920 : bytes_left = statbuf.st_size;
149 :
150 : for (;;)
151 : {
152 : /* We will break when there's nothing left to read */
153 :
154 745416 : if (bytes_left < chunk_size)
155 2624 : chunk_size = bytes_left;
156 :
157 745416 : n_read = fread(buff, 1, chunk_size, json_file);
158 745416 : if (n_read < chunk_size)
159 0 : pg_fatal("error reading input file: %d", ferror(json_file));
160 :
161 745416 : appendBinaryStringInfo(&json, buff, n_read);
162 :
163 : /*
164 : * Append some trailing junk to the buffer passed to the parser. This
165 : * helps us ensure that the parser does the right thing even if the
166 : * chunk isn't terminated with a '\0'.
167 : */
168 745416 : appendStringInfoString(&json, "1+23 trailing junk");
169 745416 : bytes_left -= n_read;
170 745416 : if (bytes_left > 0)
171 : {
172 741840 : result = pg_parse_json_incremental(lex, testsem,
173 741840 : json.data, n_read,
174 : false);
175 741840 : if (result != JSON_INCOMPLETE)
176 : {
177 344 : fprintf(stderr, "%s\n", json_errdetail(result, lex));
178 344 : ret = 1;
179 344 : goto cleanup;
180 : }
181 741496 : resetStringInfo(&json);
182 : }
183 : else
184 : {
185 3576 : result = pg_parse_json_incremental(lex, testsem,
186 3576 : json.data, n_read,
187 : true);
188 3576 : if (result != JSON_SUCCESS)
189 : {
190 1560 : fprintf(stderr, "%s\n", json_errdetail(result, lex));
191 1560 : ret = 1;
192 1560 : goto cleanup;
193 : }
194 2016 : if (!need_strings)
195 2008 : printf("SUCCESS!\n");
196 2016 : break;
197 : }
198 : }
199 :
200 3920 : cleanup:
201 3920 : fclose(json_file);
202 3920 : freeJsonLexContext(lex);
203 3920 : free(json.data);
204 3920 : free(lex);
205 :
206 3920 : return ret;
207 : }
208 :
209 : /*
210 : * The semantic routines here essentially just output the same json, except
211 : * for white space. We could pretty print it but there's no need for our
212 : * purposes. The result should be able to be fed to any JSON processor
213 : * such as jq for validation.
214 : */
215 :
216 : static JsonParseErrorType
217 320 : do_object_start(void *state)
218 : {
219 320 : DoState *_state = (DoState *) state;
220 :
221 320 : printf("{\n");
222 320 : _state->elem_is_first = true;
223 :
224 320 : return JSON_SUCCESS;
225 : }
226 :
227 : static JsonParseErrorType
228 320 : do_object_end(void *state)
229 : {
230 320 : DoState *_state = (DoState *) state;
231 :
232 320 : printf("\n}\n");
233 320 : _state->elem_is_first = false;
234 :
235 320 : return JSON_SUCCESS;
236 : }
237 :
238 : static JsonParseErrorType
239 1248 : do_object_field_start(void *state, char *fname, bool isnull)
240 : {
241 1248 : DoState *_state = (DoState *) state;
242 :
243 1248 : if (!_state->elem_is_first)
244 968 : printf(",\n");
245 1248 : resetStringInfo(_state->buf);
246 1248 : escape_json(_state->buf, fname);
247 1248 : printf("%s: ", _state->buf->data);
248 1248 : _state->elem_is_first = false;
249 :
250 1248 : return JSON_SUCCESS;
251 : }
252 :
253 : static JsonParseErrorType
254 1248 : do_object_field_end(void *state, char *fname, bool isnull)
255 : {
256 1248 : if (!lex_owns_tokens)
257 624 : free(fname);
258 :
259 1248 : return JSON_SUCCESS;
260 : }
261 :
262 : static JsonParseErrorType
263 88 : do_array_start(void *state)
264 : {
265 88 : DoState *_state = (DoState *) state;
266 :
267 88 : printf("[\n");
268 88 : _state->elem_is_first = true;
269 :
270 88 : return JSON_SUCCESS;
271 : }
272 :
273 : static JsonParseErrorType
274 88 : do_array_end(void *state)
275 : {
276 88 : DoState *_state = (DoState *) state;
277 :
278 88 : printf("\n]\n");
279 88 : _state->elem_is_first = false;
280 :
281 88 : return JSON_SUCCESS;
282 : }
283 :
284 : static JsonParseErrorType
285 240 : do_array_element_start(void *state, bool isnull)
286 : {
287 240 : DoState *_state = (DoState *) state;
288 :
289 240 : if (!_state->elem_is_first)
290 152 : printf(",\n");
291 240 : _state->elem_is_first = false;
292 :
293 240 : return JSON_SUCCESS;
294 : }
295 :
296 : static JsonParseErrorType
297 240 : do_array_element_end(void *state, bool isnull)
298 : {
299 : /* nothing to do */
300 :
301 240 : return JSON_SUCCESS;
302 : }
303 :
304 : static JsonParseErrorType
305 1088 : do_scalar(void *state, char *token, JsonTokenType tokentype)
306 : {
307 1088 : DoState *_state = (DoState *) state;
308 :
309 1088 : if (tokentype == JSON_TOKEN_STRING)
310 : {
311 848 : resetStringInfo(_state->buf);
312 848 : escape_json(_state->buf, token);
313 848 : printf("%s", _state->buf->data);
314 : }
315 : else
316 240 : printf("%s", token);
317 :
318 1088 : if (!lex_owns_tokens)
319 544 : free(token);
320 :
321 1088 : return JSON_SUCCESS;
322 : }
323 :
324 :
325 : /* copied from backend code */
326 : static void
327 2096 : escape_json(StringInfo buf, const char *str)
328 : {
329 : const char *p;
330 :
331 2096 : appendStringInfoCharMacro(buf, '"');
332 32944 : for (p = str; *p; p++)
333 : {
334 30848 : switch (*p)
335 : {
336 8 : case '\b':
337 8 : appendStringInfoString(buf, "\\b");
338 8 : break;
339 8 : case '\f':
340 8 : appendStringInfoString(buf, "\\f");
341 8 : break;
342 8 : case '\n':
343 8 : appendStringInfoString(buf, "\\n");
344 8 : break;
345 8 : case '\r':
346 8 : appendStringInfoString(buf, "\\r");
347 8 : break;
348 8 : case '\t':
349 8 : appendStringInfoString(buf, "\\t");
350 8 : break;
351 0 : case '"':
352 0 : appendStringInfoString(buf, "\\\"");
353 0 : break;
354 8 : case '\\':
355 8 : appendStringInfoString(buf, "\\\\");
356 8 : break;
357 30800 : default:
358 30800 : if ((unsigned char) *p < ' ')
359 8 : appendStringInfo(buf, "\\u%04x", (int) *p);
360 : else
361 30792 : appendStringInfoCharMacro(buf, *p);
362 30800 : break;
363 : }
364 : }
365 2096 : appendStringInfoCharMacro(buf, '"');
366 2096 : }
367 :
368 : static void
369 8 : usage(const char *progname)
370 : {
371 8 : fprintf(stderr, "Usage: %s [OPTION ...] testfile\n", progname);
372 8 : fprintf(stderr, "Options:\n");
373 8 : fprintf(stderr, " -c chunksize size of piece fed to parser (default 64)\n");
374 8 : fprintf(stderr, " -o set JSONLEX_CTX_OWNS_TOKENS for leak checking\n");
375 8 : fprintf(stderr, " -s do semantic processing\n");
376 :
377 8 : }
|