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 20devel Lines: 95.3 % 170 162
Test Date: 2026-07-25 22:15:46 Functions: 100.0 % 12 12
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 84.1 % 63 53

             Branch data     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