LCOV - code coverage report
Current view: top level - src/test/modules/test_json_parser - test_json_parser_incremental.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 95.3 % 170 162
Test Date: 2026-03-03 14:15:12 Functions: 100.0 % 12 12
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-2026, 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              :  *
      16              :  * If the "-c SIZE" option is provided, that chunk size is used instead
      17              :  * of the default of 60.
      18              :  *
      19              :  * If the "-r SIZE" option is provided, a range of chunk sizes from SIZE down to
      20              :  * 1 are run sequentially. A null byte is printed to the streams after each
      21              :  * iteration.
      22              :  *
      23              :  * If the -s flag is given, the program does semantic processing. This should
      24              :  * just mirror back the json, albeit with white space changes.
      25              :  *
      26              :  * If the -o flag is given, the JSONLEX_CTX_OWNS_TOKENS flag is set. (This can
      27              :  * be used in combination with a leak sanitizer; without the option, the parser
      28              :  * may leak memory with invalid JSON.)
      29              :  *
      30              :  * The argument specifies the file containing the JSON input.
      31              :  *
      32              :  *-------------------------------------------------------------------------
      33              :  */
      34              : 
      35              : #include "postgres_fe.h"
      36              : 
      37              : #include <stdio.h>
      38              : #include <sys/types.h>
      39              : #include <sys/stat.h>
      40              : #include <unistd.h>
      41              : 
      42              : #include "common/jsonapi.h"
      43              : #include "common/logging.h"
      44              : #include "lib/stringinfo.h"
      45              : #include "mb/pg_wchar.h"
      46              : #include "pg_getopt.h"
      47              : 
      48              : #define BUFSIZE 6000
      49              : #define DEFAULT_CHUNK_SIZE 60
      50              : 
      51              : typedef struct DoState
      52              : {
      53              :     JsonLexContext *lex;
      54              :     bool        elem_is_first;
      55              :     StringInfo  buf;
      56              : } DoState;
      57              : 
      58              : static void usage(const char *progname);
      59              : static void escape_json(StringInfo buf, const char *str);
      60              : 
      61              : /* semantic action functions for parser */
      62              : static JsonParseErrorType do_object_start(void *state);
      63              : static JsonParseErrorType do_object_end(void *state);
      64              : static JsonParseErrorType do_object_field_start(void *state, char *fname, bool isnull);
      65              : static JsonParseErrorType do_object_field_end(void *state, char *fname, bool isnull);
      66              : static JsonParseErrorType do_array_start(void *state);
      67              : static JsonParseErrorType do_array_end(void *state);
      68              : static JsonParseErrorType do_array_element_start(void *state, bool isnull);
      69              : static JsonParseErrorType do_array_element_end(void *state, bool isnull);
      70              : static JsonParseErrorType do_scalar(void *state, char *token, JsonTokenType tokentype);
      71              : 
      72              : static JsonSemAction sem = {
      73              :     .object_start = do_object_start,
      74              :     .object_end = do_object_end,
      75              :     .object_field_start = do_object_field_start,
      76              :     .object_field_end = do_object_field_end,
      77              :     .array_start = do_array_start,
      78              :     .array_end = do_array_end,
      79              :     .array_element_start = do_array_element_start,
      80              :     .array_element_end = do_array_element_end,
      81              :     .scalar = do_scalar
      82              : };
      83              : 
      84              : static bool lex_owns_tokens = false;
      85              : 
      86              : int
      87          420 : main(int argc, char **argv)
      88              : {
      89              :     char        buff[BUFSIZE];
      90              :     FILE       *json_file;
      91              :     JsonParseErrorType result;
      92              :     JsonLexContext *lex;
      93              :     StringInfoData json;
      94              :     int         n_read;
      95          420 :     size_t      chunk_size = DEFAULT_CHUNK_SIZE;
      96          420 :     bool        run_chunk_ranges = false;
      97              :     struct stat statbuf;
      98          420 :     const JsonSemAction *testsem = &nullSemAction;
      99              :     char       *testfile;
     100              :     int         c;
     101          420 :     bool        need_strings = false;
     102          420 :     int         ret = 0;
     103              : 
     104          420 :     pg_logging_init(argv[0]);
     105              : 
     106          420 :     lex = calloc(1, sizeof(JsonLexContext));
     107          420 :     if (!lex)
     108            0 :         pg_fatal("out of memory");
     109              : 
     110         1470 :     while ((c = getopt(argc, argv, "r:c:os")) != -1)
     111              :     {
     112          630 :         switch (c)
     113              :         {
     114          156 :             case 'r':           /* chunk range */
     115          156 :                 run_chunk_ranges = true;
     116              :                 pg_fallthrough;
     117          416 :             case 'c':           /* chunk size */
     118          416 :                 chunk_size = strtou64(optarg, NULL, 10);
     119          416 :                 if (chunk_size > BUFSIZE)
     120            0 :                     pg_fatal("chunk size cannot exceed %d", BUFSIZE);
     121          416 :                 break;
     122          210 :             case 'o':           /* switch token ownership */
     123          210 :                 lex_owns_tokens = true;
     124          210 :                 break;
     125            4 :             case 's':           /* do semantic processing */
     126            4 :                 testsem = &sem;
     127            4 :                 sem.semstate = palloc_object(struct DoState);
     128            4 :                 ((struct DoState *) sem.semstate)->lex = lex;
     129            4 :                 ((struct DoState *) sem.semstate)->buf = makeStringInfo();
     130            4 :                 need_strings = true;
     131            4 :                 break;
     132              :         }
     133              :     }
     134              : 
     135          420 :     if (optind < argc)
     136              :     {
     137          416 :         testfile = argv[optind];
     138          416 :         optind++;
     139              :     }
     140              :     else
     141              :     {
     142            4 :         usage(argv[0]);
     143            4 :         exit(1);
     144              :     }
     145              : 
     146          416 :     initStringInfo(&json);
     147              : 
     148          416 :     if ((json_file = fopen(testfile, PG_BINARY_R)) == NULL)
     149            0 :         pg_fatal("error opening input: %m");
     150              : 
     151          416 :     if (fstat(fileno(json_file), &statbuf) != 0)
     152            0 :         pg_fatal("error statting input: %m");
     153              : 
     154              :     do
     155              :     {
     156              :         /*
     157              :          * This outer loop only repeats in -r mode. Reset the parse state and
     158              :          * our position in the input file for the inner loop, which performs
     159              :          * the incremental parsing.
     160              :          */
     161         1960 :         off_t       bytes_left = statbuf.st_size;
     162         1960 :         size_t      to_read = chunk_size;
     163              : 
     164         1960 :         makeJsonLexContextIncremental(lex, PG_UTF8, need_strings);
     165         1960 :         setJsonLexContextOwnsTokens(lex, lex_owns_tokens);
     166              : 
     167         1960 :         rewind(json_file);
     168         1960 :         resetStringInfo(&json);
     169              : 
     170              :         for (;;)
     171              :         {
     172              :             /* We will break when there's nothing left to read */
     173              : 
     174       372708 :             if (bytes_left < to_read)
     175         1312 :                 to_read = bytes_left;
     176              : 
     177       372708 :             n_read = fread(buff, 1, to_read, json_file);
     178       372708 :             if (n_read < to_read)
     179            0 :                 pg_fatal("error reading input file: %d", ferror(json_file));
     180              : 
     181       372708 :             appendBinaryStringInfo(&json, buff, n_read);
     182              : 
     183              :             /*
     184              :              * Append some trailing junk to the buffer passed to the parser.
     185              :              * This helps us ensure that the parser does the right thing even
     186              :              * if the chunk isn't terminated with a '\0'.
     187              :              */
     188       372708 :             appendStringInfoString(&json, "1+23 trailing junk");
     189       372708 :             bytes_left -= n_read;
     190       372708 :             if (bytes_left > 0)
     191              :             {
     192       370920 :                 result = pg_parse_json_incremental(lex, testsem,
     193       370920 :                                                    json.data, n_read,
     194              :                                                    false);
     195       370920 :                 if (result != JSON_INCOMPLETE)
     196              :                 {
     197          172 :                     fprintf(stderr, "%s\n", json_errdetail(result, lex));
     198          172 :                     ret = 1;
     199          172 :                     goto cleanup;
     200              :                 }
     201       370748 :                 resetStringInfo(&json);
     202              :             }
     203              :             else
     204              :             {
     205         1788 :                 result = pg_parse_json_incremental(lex, testsem,
     206         1788 :                                                    json.data, n_read,
     207              :                                                    true);
     208         1788 :                 if (result != JSON_SUCCESS)
     209              :                 {
     210          780 :                     fprintf(stderr, "%s\n", json_errdetail(result, lex));
     211          780 :                     ret = 1;
     212          780 :                     goto cleanup;
     213              :                 }
     214         1008 :                 if (!need_strings)
     215         1004 :                     printf("SUCCESS!\n");
     216         1008 :                 break;
     217              :             }
     218              :         }
     219              : 
     220         1960 : cleanup:
     221         1960 :         freeJsonLexContext(lex);
     222              : 
     223              :         /*
     224              :          * In -r mode, separate output with nulls so that the calling test can
     225              :          * split it up, decrement the chunk size, and loop back to the top.
     226              :          * All other modes immediately fall out of the loop and exit.
     227              :          */
     228         1960 :         if (run_chunk_ranges)
     229              :         {
     230         1700 :             fputc('\0', stdout);
     231         1700 :             fputc('\0', stderr);
     232              :         }
     233         1960 :     } while (run_chunk_ranges && (--chunk_size > 0));
     234              : 
     235          416 :     fclose(json_file);
     236          416 :     free(json.data);
     237          416 :     free(lex);
     238              : 
     239          416 :     return ret;
     240              : }
     241              : 
     242              : /*
     243              :  * The semantic routines here essentially just output the same json, except
     244              :  * for white space. We could pretty print it but there's no need for our
     245              :  * purposes. The result should be able to be fed to any JSON processor
     246              :  * such as jq for validation.
     247              :  */
     248              : 
     249              : static JsonParseErrorType
     250          160 : do_object_start(void *state)
     251              : {
     252          160 :     DoState    *_state = (DoState *) state;
     253              : 
     254          160 :     printf("{\n");
     255          160 :     _state->elem_is_first = true;
     256              : 
     257          160 :     return JSON_SUCCESS;
     258              : }
     259              : 
     260              : static JsonParseErrorType
     261          160 : do_object_end(void *state)
     262              : {
     263          160 :     DoState    *_state = (DoState *) state;
     264              : 
     265          160 :     printf("\n}\n");
     266          160 :     _state->elem_is_first = false;
     267              : 
     268          160 :     return JSON_SUCCESS;
     269              : }
     270              : 
     271              : static JsonParseErrorType
     272          624 : do_object_field_start(void *state, char *fname, bool isnull)
     273              : {
     274          624 :     DoState    *_state = (DoState *) state;
     275              : 
     276          624 :     if (!_state->elem_is_first)
     277          484 :         printf(",\n");
     278          624 :     resetStringInfo(_state->buf);
     279          624 :     escape_json(_state->buf, fname);
     280          624 :     printf("%s: ", _state->buf->data);
     281          624 :     _state->elem_is_first = false;
     282              : 
     283          624 :     return JSON_SUCCESS;
     284              : }
     285              : 
     286              : static JsonParseErrorType
     287          624 : do_object_field_end(void *state, char *fname, bool isnull)
     288              : {
     289          624 :     if (!lex_owns_tokens)
     290          312 :         free(fname);
     291              : 
     292          624 :     return JSON_SUCCESS;
     293              : }
     294              : 
     295              : static JsonParseErrorType
     296           44 : do_array_start(void *state)
     297              : {
     298           44 :     DoState    *_state = (DoState *) state;
     299              : 
     300           44 :     printf("[\n");
     301           44 :     _state->elem_is_first = true;
     302              : 
     303           44 :     return JSON_SUCCESS;
     304              : }
     305              : 
     306              : static JsonParseErrorType
     307           44 : do_array_end(void *state)
     308              : {
     309           44 :     DoState    *_state = (DoState *) state;
     310              : 
     311           44 :     printf("\n]\n");
     312           44 :     _state->elem_is_first = false;
     313              : 
     314           44 :     return JSON_SUCCESS;
     315              : }
     316              : 
     317              : static JsonParseErrorType
     318          120 : do_array_element_start(void *state, bool isnull)
     319              : {
     320          120 :     DoState    *_state = (DoState *) state;
     321              : 
     322          120 :     if (!_state->elem_is_first)
     323           76 :         printf(",\n");
     324          120 :     _state->elem_is_first = false;
     325              : 
     326          120 :     return JSON_SUCCESS;
     327              : }
     328              : 
     329              : static JsonParseErrorType
     330          120 : do_array_element_end(void *state, bool isnull)
     331              : {
     332              :     /* nothing to do */
     333              : 
     334          120 :     return JSON_SUCCESS;
     335              : }
     336              : 
     337              : static JsonParseErrorType
     338          544 : do_scalar(void *state, char *token, JsonTokenType tokentype)
     339              : {
     340          544 :     DoState    *_state = (DoState *) state;
     341              : 
     342          544 :     if (tokentype == JSON_TOKEN_STRING)
     343              :     {
     344          424 :         resetStringInfo(_state->buf);
     345          424 :         escape_json(_state->buf, token);
     346          424 :         printf("%s", _state->buf->data);
     347              :     }
     348              :     else
     349          120 :         printf("%s", token);
     350              : 
     351          544 :     if (!lex_owns_tokens)
     352          272 :         free(token);
     353              : 
     354          544 :     return JSON_SUCCESS;
     355              : }
     356              : 
     357              : 
     358              : /*  copied from backend code */
     359              : static void
     360         1048 : escape_json(StringInfo buf, const char *str)
     361              : {
     362              :     const char *p;
     363              : 
     364         1048 :     appendStringInfoCharMacro(buf, '"');
     365        16472 :     for (p = str; *p; p++)
     366              :     {
     367        15424 :         switch (*p)
     368              :         {
     369            4 :             case '\b':
     370            4 :                 appendStringInfoString(buf, "\\b");
     371            4 :                 break;
     372            4 :             case '\f':
     373            4 :                 appendStringInfoString(buf, "\\f");
     374            4 :                 break;
     375            4 :             case '\n':
     376            4 :                 appendStringInfoString(buf, "\\n");
     377            4 :                 break;
     378            4 :             case '\r':
     379            4 :                 appendStringInfoString(buf, "\\r");
     380            4 :                 break;
     381            4 :             case '\t':
     382            4 :                 appendStringInfoString(buf, "\\t");
     383            4 :                 break;
     384            0 :             case '"':
     385            0 :                 appendStringInfoString(buf, "\\\"");
     386            0 :                 break;
     387            4 :             case '\\':
     388            4 :                 appendStringInfoString(buf, "\\\\");
     389            4 :                 break;
     390        15400 :             default:
     391        15400 :                 if ((unsigned char) *p < ' ')
     392            4 :                     appendStringInfo(buf, "\\u%04x", (int) *p);
     393              :                 else
     394        15396 :                     appendStringInfoCharMacro(buf, *p);
     395        15400 :                 break;
     396              :         }
     397              :     }
     398         1048 :     appendStringInfoCharMacro(buf, '"');
     399         1048 : }
     400              : 
     401              : static void
     402            4 : usage(const char *progname)
     403              : {
     404            4 :     fprintf(stderr, "Usage: %s [OPTION ...] testfile\n", progname);
     405            4 :     fprintf(stderr, "Options:\n");
     406            4 :     fprintf(stderr, "  -c chunksize      size of piece fed to parser (default 64)\n");
     407            4 :     fprintf(stderr, "  -o                set JSONLEX_CTX_OWNS_TOKENS for leak checking\n");
     408            4 :     fprintf(stderr, "  -s                do semantic processing\n");
     409              : 
     410            4 : }
        

Generated by: LCOV version 2.0-1