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