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: Code coverage Lines: 0.0 % 183 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 12 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-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            0 : main(int argc, char **argv)
      88              : {
      89            0 :         char            buff[BUFSIZE];
      90            0 :         FILE       *json_file;
      91            0 :         JsonParseErrorType result;
      92            0 :         JsonLexContext *lex;
      93            0 :         StringInfoData json;
      94            0 :         int                     n_read;
      95            0 :         size_t          chunk_size = DEFAULT_CHUNK_SIZE;
      96            0 :         bool            run_chunk_ranges = false;
      97            0 :         struct stat statbuf;
      98            0 :         const JsonSemAction *testsem = &nullSemAction;
      99            0 :         char       *testfile;
     100            0 :         int                     c;
     101            0 :         bool            need_strings = false;
     102            0 :         int                     ret = 0;
     103              : 
     104            0 :         pg_logging_init(argv[0]);
     105              : 
     106            0 :         lex = calloc(1, sizeof(JsonLexContext));
     107            0 :         if (!lex)
     108            0 :                 pg_fatal("out of memory");
     109              : 
     110            0 :         while ((c = getopt(argc, argv, "r:c:os")) != -1)
     111              :         {
     112            0 :                 switch (c)
     113              :                 {
     114              :                         case 'r':                       /* chunk range */
     115            0 :                                 run_chunk_ranges = true;
     116              :                                 /* fall through */
     117              :                         case 'c':                       /* chunk size */
     118            0 :                                 chunk_size = strtou64(optarg, NULL, 10);
     119            0 :                                 if (chunk_size > BUFSIZE)
     120            0 :                                         pg_fatal("chunk size cannot exceed %d", BUFSIZE);
     121            0 :                                 break;
     122              :                         case 'o':                       /* switch token ownership */
     123            0 :                                 lex_owns_tokens = true;
     124            0 :                                 break;
     125              :                         case 's':                       /* do semantic processing */
     126            0 :                                 testsem = &sem;
     127            0 :                                 sem.semstate = palloc_object(struct DoState);
     128            0 :                                 ((struct DoState *) sem.semstate)->lex = lex;
     129            0 :                                 ((struct DoState *) sem.semstate)->buf = makeStringInfo();
     130            0 :                                 need_strings = true;
     131            0 :                                 break;
     132              :                 }
     133              :         }
     134              : 
     135            0 :         if (optind < argc)
     136              :         {
     137            0 :                 testfile = argv[optind];
     138            0 :                 optind++;
     139            0 :         }
     140              :         else
     141              :         {
     142            0 :                 usage(argv[0]);
     143            0 :                 exit(1);
     144              :         }
     145              : 
     146            0 :         initStringInfo(&json);
     147              : 
     148            0 :         if ((json_file = fopen(testfile, PG_BINARY_R)) == NULL)
     149            0 :                 pg_fatal("error opening input: %m");
     150              : 
     151            0 :         if (fstat(fileno(json_file), &statbuf) != 0)
     152            0 :                 pg_fatal("error statting input: %m");
     153              : 
     154            0 :         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            0 :                 off_t           bytes_left = statbuf.st_size;
     162            0 :                 size_t          to_read = chunk_size;
     163              : 
     164            0 :                 makeJsonLexContextIncremental(lex, PG_UTF8, need_strings);
     165            0 :                 setJsonLexContextOwnsTokens(lex, lex_owns_tokens);
     166              : 
     167            0 :                 rewind(json_file);
     168            0 :                 resetStringInfo(&json);
     169              : 
     170            0 :                 for (;;)
     171              :                 {
     172              :                         /* We will break when there's nothing left to read */
     173              : 
     174            0 :                         if (bytes_left < to_read)
     175            0 :                                 to_read = bytes_left;
     176              : 
     177            0 :                         n_read = fread(buff, 1, to_read, json_file);
     178            0 :                         if (n_read < to_read)
     179            0 :                                 pg_fatal("error reading input file: %d", ferror(json_file));
     180              : 
     181            0 :                         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            0 :                         appendStringInfoString(&json, "1+23 trailing junk");
     189            0 :                         bytes_left -= n_read;
     190            0 :                         if (bytes_left > 0)
     191              :                         {
     192            0 :                                 result = pg_parse_json_incremental(lex, testsem,
     193            0 :                                                                                                    json.data, n_read,
     194              :                                                                                                    false);
     195            0 :                                 if (result != JSON_INCOMPLETE)
     196              :                                 {
     197            0 :                                         fprintf(stderr, "%s\n", json_errdetail(result, lex));
     198            0 :                                         ret = 1;
     199            0 :                                         goto cleanup;
     200              :                                 }
     201            0 :                                 resetStringInfo(&json);
     202            0 :                         }
     203              :                         else
     204              :                         {
     205            0 :                                 result = pg_parse_json_incremental(lex, testsem,
     206            0 :                                                                                                    json.data, n_read,
     207              :                                                                                                    true);
     208            0 :                                 if (result != JSON_SUCCESS)
     209              :                                 {
     210            0 :                                         fprintf(stderr, "%s\n", json_errdetail(result, lex));
     211            0 :                                         ret = 1;
     212            0 :                                         goto cleanup;
     213              :                                 }
     214            0 :                                 if (!need_strings)
     215            0 :                                         printf("SUCCESS!\n");
     216            0 :                                 break;
     217              :                         }
     218            0 :                 }
     219              : 
     220              : cleanup:
     221            0 :                 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            0 :                 if (run_chunk_ranges)
     229              :                 {
     230            0 :                         fputc('\0', stdout);
     231            0 :                         fputc('\0', stderr);
     232            0 :                 }
     233            0 :         } while (run_chunk_ranges && (--chunk_size > 0));
     234              : 
     235            0 :         fclose(json_file);
     236            0 :         free(json.data);
     237            0 :         free(lex);
     238              : 
     239            0 :         return ret;
     240            0 : }
     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            0 : do_object_start(void *state)
     251              : {
     252            0 :         DoState    *_state = (DoState *) state;
     253              : 
     254            0 :         printf("{\n");
     255            0 :         _state->elem_is_first = true;
     256              : 
     257            0 :         return JSON_SUCCESS;
     258            0 : }
     259              : 
     260              : static JsonParseErrorType
     261            0 : do_object_end(void *state)
     262              : {
     263            0 :         DoState    *_state = (DoState *) state;
     264              : 
     265            0 :         printf("\n}\n");
     266            0 :         _state->elem_is_first = false;
     267              : 
     268            0 :         return JSON_SUCCESS;
     269            0 : }
     270              : 
     271              : static JsonParseErrorType
     272            0 : do_object_field_start(void *state, char *fname, bool isnull)
     273              : {
     274            0 :         DoState    *_state = (DoState *) state;
     275              : 
     276            0 :         if (!_state->elem_is_first)
     277            0 :                 printf(",\n");
     278            0 :         resetStringInfo(_state->buf);
     279            0 :         escape_json(_state->buf, fname);
     280            0 :         printf("%s: ", _state->buf->data);
     281            0 :         _state->elem_is_first = false;
     282              : 
     283            0 :         return JSON_SUCCESS;
     284            0 : }
     285              : 
     286              : static JsonParseErrorType
     287            0 : do_object_field_end(void *state, char *fname, bool isnull)
     288              : {
     289            0 :         if (!lex_owns_tokens)
     290            0 :                 free(fname);
     291              : 
     292            0 :         return JSON_SUCCESS;
     293              : }
     294              : 
     295              : static JsonParseErrorType
     296            0 : do_array_start(void *state)
     297              : {
     298            0 :         DoState    *_state = (DoState *) state;
     299              : 
     300            0 :         printf("[\n");
     301            0 :         _state->elem_is_first = true;
     302              : 
     303            0 :         return JSON_SUCCESS;
     304            0 : }
     305              : 
     306              : static JsonParseErrorType
     307            0 : do_array_end(void *state)
     308              : {
     309            0 :         DoState    *_state = (DoState *) state;
     310              : 
     311            0 :         printf("\n]\n");
     312            0 :         _state->elem_is_first = false;
     313              : 
     314            0 :         return JSON_SUCCESS;
     315            0 : }
     316              : 
     317              : static JsonParseErrorType
     318            0 : do_array_element_start(void *state, bool isnull)
     319              : {
     320            0 :         DoState    *_state = (DoState *) state;
     321              : 
     322            0 :         if (!_state->elem_is_first)
     323            0 :                 printf(",\n");
     324            0 :         _state->elem_is_first = false;
     325              : 
     326            0 :         return JSON_SUCCESS;
     327            0 : }
     328              : 
     329              : static JsonParseErrorType
     330            0 : do_array_element_end(void *state, bool isnull)
     331              : {
     332              :         /* nothing to do */
     333              : 
     334            0 :         return JSON_SUCCESS;
     335              : }
     336              : 
     337              : static JsonParseErrorType
     338            0 : do_scalar(void *state, char *token, JsonTokenType tokentype)
     339              : {
     340            0 :         DoState    *_state = (DoState *) state;
     341              : 
     342            0 :         if (tokentype == JSON_TOKEN_STRING)
     343              :         {
     344            0 :                 resetStringInfo(_state->buf);
     345            0 :                 escape_json(_state->buf, token);
     346            0 :                 printf("%s", _state->buf->data);
     347            0 :         }
     348              :         else
     349            0 :                 printf("%s", token);
     350              : 
     351            0 :         if (!lex_owns_tokens)
     352            0 :                 free(token);
     353              : 
     354            0 :         return JSON_SUCCESS;
     355            0 : }
     356              : 
     357              : 
     358              : /*  copied from backend code */
     359              : static void
     360            0 : escape_json(StringInfo buf, const char *str)
     361              : {
     362            0 :         const char *p;
     363              : 
     364            0 :         appendStringInfoCharMacro(buf, '"');
     365            0 :         for (p = str; *p; p++)
     366              :         {
     367            0 :                 switch (*p)
     368              :                 {
     369              :                         case '\b':
     370            0 :                                 appendStringInfoString(buf, "\\b");
     371            0 :                                 break;
     372              :                         case '\f':
     373            0 :                                 appendStringInfoString(buf, "\\f");
     374            0 :                                 break;
     375              :                         case '\n':
     376            0 :                                 appendStringInfoString(buf, "\\n");
     377            0 :                                 break;
     378              :                         case '\r':
     379            0 :                                 appendStringInfoString(buf, "\\r");
     380            0 :                                 break;
     381              :                         case '\t':
     382            0 :                                 appendStringInfoString(buf, "\\t");
     383            0 :                                 break;
     384              :                         case '"':
     385            0 :                                 appendStringInfoString(buf, "\\\"");
     386            0 :                                 break;
     387              :                         case '\\':
     388            0 :                                 appendStringInfoString(buf, "\\\\");
     389            0 :                                 break;
     390              :                         default:
     391            0 :                                 if ((unsigned char) *p < ' ')
     392            0 :                                         appendStringInfo(buf, "\\u%04x", (int) *p);
     393              :                                 else
     394            0 :                                         appendStringInfoCharMacro(buf, *p);
     395            0 :                                 break;
     396              :                 }
     397            0 :         }
     398            0 :         appendStringInfoCharMacro(buf, '"');
     399            0 : }
     400              : 
     401              : static void
     402            0 : usage(const char *progname)
     403              : {
     404            0 :         fprintf(stderr, "Usage: %s [OPTION ...] testfile\n", progname);
     405            0 :         fprintf(stderr, "Options:\n");
     406            0 :         fprintf(stderr, "  -c chunksize      size of piece fed to parser (default 64)\n");
     407            0 :         fprintf(stderr, "  -o                set JSONLEX_CTX_OWNS_TOKENS for leak checking\n");
     408            0 :         fprintf(stderr, "  -s                do semantic processing\n");
     409              : 
     410            0 : }
        

Generated by: LCOV version 2.3.2-1