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 13748 : while ((c = getopt(argc, argv, "c:os")) != -1)
102 : {
103 5892 : switch (c)
104 : {
105 3920 : case 'c': /* chunksize */
106 3920 : chunk_size = strtou64(optarg, NULL, 10);
107 3920 : if (chunk_size > BUFSIZE)
108 0 : pg_fatal("chunk size cannot exceed %d", BUFSIZE);
109 3920 : break;
110 1964 : case 'o': /* switch token ownership */
111 1964 : lex_owns_tokens = true;
112 1964 : break;
113 8 : case 's': /* do semantic processing */
114 8 : testsem = &sem;
115 8 : sem.semstate = palloc(sizeof(struct DoState));
116 8 : ((struct DoState *) sem.semstate)->lex = &lex;
117 8 : ((struct DoState *) sem.semstate)->buf = makeStringInfo();
118 8 : need_strings = true;
119 8 : break;
120 : }
121 9820 : }
122 :
123 3928 : if (optind < argc)
124 : {
125 3920 : testfile = argv[optind];
126 3920 : optind++;
127 : }
128 : else
129 : {
130 8 : usage(argv[0]);
131 8 : exit(1);
132 : }
133 :
134 3920 : makeJsonLexContextIncremental(&lex, PG_UTF8, need_strings);
135 3920 : setJsonLexContextOwnsTokens(&lex, lex_owns_tokens);
136 3920 : initStringInfo(&json);
137 :
138 3920 : if ((json_file = fopen(testfile, PG_BINARY_R)) == NULL)
139 0 : pg_fatal("error opening input: %m");
140 :
141 3920 : if (fstat(fileno(json_file), &statbuf) != 0)
142 0 : pg_fatal("error statting input: %m");
143 :
144 3920 : bytes_left = statbuf.st_size;
145 :
146 : for (;;)
147 : {
148 : /* We will break when there's nothing left to read */
149 :
150 745416 : if (bytes_left < chunk_size)
151 2624 : chunk_size = bytes_left;
152 :
153 745416 : n_read = fread(buff, 1, chunk_size, json_file);
154 745416 : if (n_read < chunk_size)
155 0 : pg_fatal("error reading input file: %d", ferror(json_file));
156 :
157 745416 : appendBinaryStringInfo(&json, buff, n_read);
158 :
159 : /*
160 : * Append some trailing junk to the buffer passed to the parser. This
161 : * helps us ensure that the parser does the right thing even if the
162 : * chunk isn't terminated with a '\0'.
163 : */
164 745416 : appendStringInfoString(&json, "1+23 trailing junk");
165 745416 : bytes_left -= n_read;
166 745416 : if (bytes_left > 0)
167 : {
168 741840 : result = pg_parse_json_incremental(&lex, testsem,
169 741840 : json.data, n_read,
170 : false);
171 741840 : if (result != JSON_INCOMPLETE)
172 : {
173 344 : fprintf(stderr, "%s\n", json_errdetail(result, &lex));
174 344 : ret = 1;
175 344 : goto cleanup;
176 : }
177 741496 : resetStringInfo(&json);
178 : }
179 : else
180 : {
181 3576 : result = pg_parse_json_incremental(&lex, testsem,
182 3576 : json.data, n_read,
183 : true);
184 3576 : if (result != JSON_SUCCESS)
185 : {
186 1560 : fprintf(stderr, "%s\n", json_errdetail(result, &lex));
187 1560 : ret = 1;
188 1560 : goto cleanup;
189 : }
190 2016 : if (!need_strings)
191 2008 : printf("SUCCESS!\n");
192 2016 : break;
193 : }
194 : }
195 :
196 3920 : cleanup:
197 3920 : fclose(json_file);
198 3920 : freeJsonLexContext(&lex);
199 3920 : free(json.data);
200 :
201 3920 : return ret;
202 : }
203 :
204 : /*
205 : * The semantic routines here essentially just output the same json, except
206 : * for white space. We could pretty print it but there's no need for our
207 : * purposes. The result should be able to be fed to any JSON processor
208 : * such as jq for validation.
209 : */
210 :
211 : static JsonParseErrorType
212 320 : do_object_start(void *state)
213 : {
214 320 : DoState *_state = (DoState *) state;
215 :
216 320 : printf("{\n");
217 320 : _state->elem_is_first = true;
218 :
219 320 : return JSON_SUCCESS;
220 : }
221 :
222 : static JsonParseErrorType
223 320 : do_object_end(void *state)
224 : {
225 320 : DoState *_state = (DoState *) state;
226 :
227 320 : printf("\n}\n");
228 320 : _state->elem_is_first = false;
229 :
230 320 : return JSON_SUCCESS;
231 : }
232 :
233 : static JsonParseErrorType
234 1248 : do_object_field_start(void *state, char *fname, bool isnull)
235 : {
236 1248 : DoState *_state = (DoState *) state;
237 :
238 1248 : if (!_state->elem_is_first)
239 968 : printf(",\n");
240 1248 : resetStringInfo(_state->buf);
241 1248 : escape_json(_state->buf, fname);
242 1248 : printf("%s: ", _state->buf->data);
243 1248 : _state->elem_is_first = false;
244 :
245 1248 : return JSON_SUCCESS;
246 : }
247 :
248 : static JsonParseErrorType
249 1248 : do_object_field_end(void *state, char *fname, bool isnull)
250 : {
251 1248 : if (!lex_owns_tokens)
252 624 : free(fname);
253 :
254 1248 : return JSON_SUCCESS;
255 : }
256 :
257 : static JsonParseErrorType
258 88 : do_array_start(void *state)
259 : {
260 88 : DoState *_state = (DoState *) state;
261 :
262 88 : printf("[\n");
263 88 : _state->elem_is_first = true;
264 :
265 88 : return JSON_SUCCESS;
266 : }
267 :
268 : static JsonParseErrorType
269 88 : do_array_end(void *state)
270 : {
271 88 : DoState *_state = (DoState *) state;
272 :
273 88 : printf("\n]\n");
274 88 : _state->elem_is_first = false;
275 :
276 88 : return JSON_SUCCESS;
277 : }
278 :
279 : static JsonParseErrorType
280 240 : do_array_element_start(void *state, bool isnull)
281 : {
282 240 : DoState *_state = (DoState *) state;
283 :
284 240 : if (!_state->elem_is_first)
285 152 : printf(",\n");
286 240 : _state->elem_is_first = false;
287 :
288 240 : return JSON_SUCCESS;
289 : }
290 :
291 : static JsonParseErrorType
292 240 : do_array_element_end(void *state, bool isnull)
293 : {
294 : /* nothing to do */
295 :
296 240 : return JSON_SUCCESS;
297 : }
298 :
299 : static JsonParseErrorType
300 1088 : do_scalar(void *state, char *token, JsonTokenType tokentype)
301 : {
302 1088 : DoState *_state = (DoState *) state;
303 :
304 1088 : if (tokentype == JSON_TOKEN_STRING)
305 : {
306 848 : resetStringInfo(_state->buf);
307 848 : escape_json(_state->buf, token);
308 848 : printf("%s", _state->buf->data);
309 : }
310 : else
311 240 : printf("%s", token);
312 :
313 1088 : if (!lex_owns_tokens)
314 544 : free(token);
315 :
316 1088 : return JSON_SUCCESS;
317 : }
318 :
319 :
320 : /* copied from backend code */
321 : static void
322 2096 : escape_json(StringInfo buf, const char *str)
323 : {
324 : const char *p;
325 :
326 2096 : appendStringInfoCharMacro(buf, '"');
327 32944 : for (p = str; *p; p++)
328 : {
329 30848 : switch (*p)
330 : {
331 8 : case '\b':
332 8 : appendStringInfoString(buf, "\\b");
333 8 : break;
334 8 : case '\f':
335 8 : appendStringInfoString(buf, "\\f");
336 8 : break;
337 8 : case '\n':
338 8 : appendStringInfoString(buf, "\\n");
339 8 : break;
340 8 : case '\r':
341 8 : appendStringInfoString(buf, "\\r");
342 8 : break;
343 8 : case '\t':
344 8 : appendStringInfoString(buf, "\\t");
345 8 : break;
346 0 : case '"':
347 0 : appendStringInfoString(buf, "\\\"");
348 0 : break;
349 8 : case '\\':
350 8 : appendStringInfoString(buf, "\\\\");
351 8 : break;
352 30800 : default:
353 30800 : if ((unsigned char) *p < ' ')
354 8 : appendStringInfo(buf, "\\u%04x", (int) *p);
355 : else
356 30792 : appendStringInfoCharMacro(buf, *p);
357 30800 : break;
358 : }
359 : }
360 2096 : appendStringInfoCharMacro(buf, '"');
361 2096 : }
362 :
363 : static void
364 8 : usage(const char *progname)
365 : {
366 8 : fprintf(stderr, "Usage: %s [OPTION ...] testfile\n", progname);
367 8 : fprintf(stderr, "Options:\n");
368 8 : fprintf(stderr, " -c chunksize size of piece fed to parser (default 64)\n");
369 8 : fprintf(stderr, " -o set JSONLEX_CTX_OWNS_TOKENS for leak checking\n");
370 8 : fprintf(stderr, " -s do semantic processing\n");
371 :
372 8 : }
|