LCOV - code coverage report
Current view: top level - src/test/modules/test_json_parser - test_json_parser_incremental.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 135 142 95.1 %
Date: 2024-11-21 08:14:44 Functions: 12 12 100.0 %
Legend: Lines: hit not hit

          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 : }

Generated by: LCOV version 1.14