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: 153 161 95.0 %
Date: 2025-04-24 12:15:10 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-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 : }

Generated by: LCOV version 1.14