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