From 2f06c7daafffae594eac879fb92cbe74205b69d0 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Wed, 8 Jul 2026 21:39:14 +0000 Subject: [PATCH 1/8] Add JSON5 comment support to the JSON lexer Introduce a json5 flag on JsonLexContext (plus a param on both makeJsonLexContext constructors, all existing callers pass false) and, when set, skip // line and /* */ block comments wherever whitespace is allowed, including across incremental chunk boundaries. json5=false is byte-for-byte unchanged. --- src/backend/utils/adt/json.c | 2 +- src/backend/utils/adt/jsonb.c | 2 +- src/backend/utils/adt/jsonfuncs.c | 7 +- src/backend/utils/adt/pg_dependencies.c | 2 +- src/backend/utils/adt/pg_ndistinct.c | 2 +- src/common/jsonapi.c | 163 +++++++++++++++++- src/common/parse_manifest.c | 4 +- src/include/common/jsonapi.h | 8 +- src/interfaces/libpq-oauth/oauth-curl.c | 2 +- src/interfaces/libpq/fe-auth-oauth.c | 2 +- src/test/modules/test_escape/test_escape.c | 2 +- src/test/modules/test_json_parser/README | 4 +- .../test_json_parser/json5_comments.json5 | 12 ++ .../test_json_parser/json5_comments.out | 10 ++ src/test/modules/test_json_parser/meson.build | 3 +- .../test_json_parser/t/005_test_json5.pl | 99 +++++++++++ .../test_json_parser_incremental.c | 16 +- .../test_json_parser/test_json_parser_perf.c | 4 +- src/tools/pgindent/typedefs.list | 1 + 19 files changed, 316 insertions(+), 29 deletions(-) create mode 100644 src/test/modules/test_json_parser/json5_comments.json5 create mode 100644 src/test/modules/test_json_parser/json5_comments.out create mode 100644 src/test/modules/test_json_parser/t/005_test_json5.pl diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 0fee1b40d63..94040c5e03e 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -159,7 +159,7 @@ json_recv(PG_FUNCTION_ARGS) /* Validate it. */ makeJsonLexContextCstringLen(&lex, str, nbytes, GetDatabaseEncoding(), - false); + false, false); pg_parse_json_or_ereport(&lex, &nullSemAction); PG_RETURN_TEXT_P(cstring_to_text_with_len(str, nbytes)); diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 864c5ac1c85..1c97ede567a 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -245,7 +245,7 @@ jsonb_from_cstring(char *json, int len, bool unique_keys, Node *escontext) memset(&state, 0, sizeof(state)); memset(&sem, 0, sizeof(sem)); - makeJsonLexContextCstringLen(&lex, json, len, GetDatabaseEncoding(), true); + makeJsonLexContextCstringLen(&lex, json, len, GetDatabaseEncoding(), true, false); state.unique_keys = unique_keys; state.escontext = escontext; diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 97cc3d60340..08aec32c0d6 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -552,7 +552,8 @@ makeJsonLexContext(JsonLexContext *lex, text *json, bool need_escapes) VARDATA_ANY(json), VARSIZE_ANY_EXHDR(json), GetDatabaseEncoding(), - need_escapes); + need_escapes, + false); } /* @@ -2792,7 +2793,7 @@ populate_array_json(PopulateArrayContext *ctx, const char *json, int len) JsonSemAction sem; state.lex = makeJsonLexContextCstringLen(NULL, json, len, - GetDatabaseEncoding(), true); + GetDatabaseEncoding(), true, false); state.ctx = ctx; memset(&sem, 0, sizeof(sem)); @@ -3830,7 +3831,7 @@ get_json_object_as_hash(const char *json, int len, const char *funcname, state->function_name = funcname; state->hash = tab; state->lex = makeJsonLexContextCstringLen(NULL, json, len, - GetDatabaseEncoding(), true); + GetDatabaseEncoding(), true, false); sem->semstate = state; sem->array_start = hash_array_start; diff --git a/src/backend/utils/adt/pg_dependencies.c b/src/backend/utils/adt/pg_dependencies.c index 9c3db103d42..b5049d6b4b6 100644 --- a/src/backend/utils/adt/pg_dependencies.c +++ b/src/backend/utils/adt/pg_dependencies.c @@ -778,7 +778,7 @@ pg_dependencies_in(PG_FUNCTION_ARGS) sem_action.object_field_end = NULL; sem_action.scalar = dependencies_scalar; - lex = makeJsonLexContextCstringLen(NULL, str, strlen(str), PG_UTF8, true); + lex = makeJsonLexContextCstringLen(NULL, str, strlen(str), PG_UTF8, true, false); result = pg_parse_json(lex, &sem_action); freeJsonLexContext(lex); diff --git a/src/backend/utils/adt/pg_ndistinct.c b/src/backend/utils/adt/pg_ndistinct.c index 8d854012d6e..5d28dd48a3a 100644 --- a/src/backend/utils/adt/pg_ndistinct.c +++ b/src/backend/utils/adt/pg_ndistinct.c @@ -756,7 +756,7 @@ pg_ndistinct_in(PG_FUNCTION_ARGS) sem_action.scalar = ndistinct_scalar; lex = makeJsonLexContextCstringLen(NULL, str, strlen(str), - PG_UTF8, true); + PG_UTF8, true, false); result = pg_parse_json(lex, &sem_action); freeJsonLexContext(lex); diff --git a/src/common/jsonapi.c b/src/common/jsonapi.c index d3860197dad..4ca70c5fea9 100644 --- a/src/common/jsonapi.c +++ b/src/common/jsonapi.c @@ -153,6 +153,16 @@ struct JsonParserStack char *scalar_val; }; +/* json5 comment lexing state saved across incremental chunks */ +typedef enum Json5CommentState +{ + JSON5_COMMENT_NONE = 0, + JSON5_COMMENT_SLASH, /* saw '/', comment kind not known yet */ + JSON5_COMMENT_LINE, /* inside a // comment */ + JSON5_COMMENT_BLOCK, /* inside a block comment */ + JSON5_COMMENT_BLOCK_STAR, /* inside a block comment, last byte was '*' */ +} Json5CommentState; + /* * struct containing state used when there is a possible partial token at the * end of a json chunk when we are doing incremental parsing. @@ -165,6 +175,8 @@ struct JsonIncrementalState bool is_last_chunk; bool partial_completed; jsonapi_StrValType partial_token; + /* json5 comment resume state */ + Json5CommentState comment_state; }; /* @@ -390,7 +402,8 @@ IsValidJsonNumber(const char *str, size_t len) */ JsonLexContext * makeJsonLexContextCstringLen(JsonLexContext *lex, const char *json, - size_t len, int encoding, bool need_escapes) + size_t len, int encoding, bool need_escapes, + bool json5) { if (lex == NULL) { @@ -408,6 +421,7 @@ makeJsonLexContextCstringLen(JsonLexContext *lex, const char *json, lex->input_length = len; lex->input_encoding = encoding; lex->need_escapes = need_escapes; + lex->json5 = json5; if (need_escapes) { /* @@ -496,7 +510,7 @@ allocate_incremental_state(JsonLexContext *lex) */ JsonLexContext * makeJsonLexContextIncremental(JsonLexContext *lex, int encoding, - bool need_escapes) + bool need_escapes, bool json5) { if (lex == NULL) { @@ -525,6 +539,7 @@ makeJsonLexContextIncremental(JsonLexContext *lex, int encoding, } lex->need_escapes = need_escapes; + lex->json5 = json5; if (need_escapes) { /* @@ -1809,6 +1824,7 @@ json_lex(JsonLexContext *lex) dummy_lex.input_encoding = lex->input_encoding; dummy_lex.incremental = false; dummy_lex.need_escapes = lex->need_escapes; + dummy_lex.json5 = lex->json5; dummy_lex.strval = lex->strval; partial_result = json_lex(&dummy_lex); @@ -1849,15 +1865,146 @@ json_lex(JsonLexContext *lex) /* end of partial token processing */ } - /* Skip leading whitespace. */ - while (s < end && (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')) + /* + * Skip whitespace and, in json5 mode, comments. Comments count as + * whitespace, so this alternates between the two until neither matches. + * At an incremental chunk boundary the comment state is saved in + * inc_state and picked up again here, since the bytes deciding comment + * kind and termination can open the next chunk. + */ + for (;;) { - if (*s++ == '\n') + Json5CommentState cstate = JSON5_COMMENT_NONE; + + if (lex->json5 && lex->incremental) { - ++lex->line_number; - lex->line_start = s; + cstate = lex->inc_state->comment_state; + lex->inc_state->comment_state = JSON5_COMMENT_NONE; + } + + if (cstate == JSON5_COMMENT_NONE) + { + /* Skip leading whitespace. */ + while (s < end && (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')) + { + if (*s++ == '\n') + { + ++lex->line_number; + lex->line_start = s; + } + } + + if (!lex->json5 || s >= end || *s != '/') + break; + + if (s + 1 >= end) + { + /* + * A lone '/' at the end of the chunk might start a comment; + * ask for more input in incremental mode. Otherwise fall + * out and report the '/' as an invalid token. + */ + if (lex->incremental && !lex->inc_state->is_last_chunk) + { + lex->inc_state->comment_state = JSON5_COMMENT_SLASH; + return JSON_INCOMPLETE; + } + break; + } + if (*(s + 1) == '/') + { + s += 2; + cstate = JSON5_COMMENT_LINE; + } + else if (*(s + 1) == '*') + { + s += 2; + cstate = JSON5_COMMENT_BLOCK; + } + else + break; /* '/' not a comment: invalid token */ + } + else if (cstate == JSON5_COMMENT_SLASH) + { + /* '/' seen at the end of the previous chunk */ + if (s < end && *s == '/') + { + s++; + cstate = JSON5_COMMENT_LINE; + } + else if (s < end && *s == '*') + { + s++; + cstate = JSON5_COMMENT_BLOCK; + } + else + { + if (s >= end && !lex->inc_state->is_last_chunk) + { + lex->inc_state->comment_state = JSON5_COMMENT_SLASH; + return JSON_INCOMPLETE; + } + lex->token_start = s; + lex->prev_token_terminator = lex->token_terminator; + lex->token_terminator = s; + return JSON_INVALID_TOKEN; + } + } + + if (cstate == JSON5_COMMENT_LINE) + { + /* json5 line terminators are LF and CR */ + while (s < end && *s != '\n' && *s != '\r') + s++; + if (s >= end && lex->incremental && !lex->inc_state->is_last_chunk) + { + lex->inc_state->comment_state = JSON5_COMMENT_LINE; + return JSON_INCOMPLETE; + } + + /* + * The terminator, if any, is consumed as whitespace on the next + * round; at end of input the comment is implicitly closed. + */ + } + else + { + /* block comment body; star tracks a possible closing '*' */ + bool star = (cstate == JSON5_COMMENT_BLOCK_STAR); + bool closed = false; + + while (s < end) + { + char c = *s++; + + if (star && c == '/') + { + closed = true; + break; + } + star = (c == '*'); + if (c == '\n') + { + ++lex->line_number; + lex->line_start = s; + } + } + if (!closed) + { + if (lex->incremental && !lex->inc_state->is_last_chunk) + { + lex->inc_state->comment_state = + star ? JSON5_COMMENT_BLOCK_STAR : JSON5_COMMENT_BLOCK; + return JSON_INCOMPLETE; + } + lex->token_start = s; + lex->prev_token_terminator = lex->token_terminator; + lex->token_terminator = s; + return JSON_UNTERMINATED_COMMENT; + } } } + lex->token_start = s; /* Determine token type. */ @@ -2551,6 +2698,8 @@ json_errdetail(JsonParseErrorType error, JsonLexContext *lex) return _("Unicode high surrogate must not follow a high surrogate."); case JSON_UNICODE_LOW_SURROGATE: return _("Unicode low surrogate must follow a high surrogate."); + case JSON_UNTERMINATED_COMMENT: + return _("Block comment is not terminated."); case JSON_SEM_ACTION_FAILED: /* fall through to the error code after switch */ break; diff --git a/src/common/parse_manifest.c b/src/common/parse_manifest.c index 5065c9bf39c..23ae27dfa98 100644 --- a/src/common/parse_manifest.c +++ b/src/common/parse_manifest.c @@ -139,7 +139,7 @@ json_parse_manifest_incremental_init(JsonManifestParseContext *context) parse->state = JM_EXPECT_TOPLEVEL_START; parse->saw_version_field = false; - makeJsonLexContextIncremental(&(incstate->lex), PG_UTF8, true); + makeJsonLexContextIncremental(&(incstate->lex), PG_UTF8, true, false); incstate->sem.semstate = parse; incstate->sem.object_start = json_manifest_object_start; @@ -238,7 +238,7 @@ json_parse_manifest(JsonManifestParseContext *context, const char *buffer, parse.saw_version_field = false; /* Create a JSON lexing context. */ - lex = makeJsonLexContextCstringLen(NULL, buffer, size, PG_UTF8, true); + lex = makeJsonLexContextCstringLen(NULL, buffer, size, PG_UTF8, true, false); /* Set up semantic actions. */ sem.semstate = &parse; diff --git a/src/include/common/jsonapi.h b/src/include/common/jsonapi.h index 85cc9a11d97..8b4d8d818c7 100644 --- a/src/include/common/jsonapi.h +++ b/src/include/common/jsonapi.h @@ -56,6 +56,7 @@ typedef enum JsonParseErrorType JSON_UNICODE_UNTRANSLATABLE, JSON_UNICODE_HIGH_SURROGATE, JSON_UNICODE_LOW_SURROGATE, + JSON_UNTERMINATED_COMMENT, JSON_SEM_ACTION_FAILED, /* error should already be reported */ } JsonParseErrorType; @@ -106,6 +107,7 @@ typedef struct JsonLexContext const char *token_terminator; const char *prev_token_terminator; bool incremental; + bool json5; JsonTokenType token_type; int lex_level; uint32 flags; @@ -219,7 +221,8 @@ extern JsonLexContext *makeJsonLexContextCstringLen(JsonLexContext *lex, const char *json, size_t len, int encoding, - bool need_escapes); + bool need_escapes, + bool json5); /* * make a JsonLexContext suitable for incremental parsing. @@ -228,7 +231,8 @@ extern JsonLexContext *makeJsonLexContextCstringLen(JsonLexContext *lex, */ extern JsonLexContext *makeJsonLexContextIncremental(JsonLexContext *lex, int encoding, - bool need_escapes); + bool need_escapes, + bool json5); /* * Sets whether tokens passed to semantic action callbacks are owned by the diff --git a/src/interfaces/libpq-oauth/oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c index d4dcc4cd7a5..941a8d89000 100644 --- a/src/interfaces/libpq-oauth/oauth-curl.c +++ b/src/interfaces/libpq-oauth/oauth-curl.c @@ -896,7 +896,7 @@ parse_oauth_json(struct async_ctx *actx, const struct json_field *fields) return false; } - makeJsonLexContextCstringLen(&lex, resp->data, resp->len, PG_UTF8, true); + makeJsonLexContextCstringLen(&lex, resp->data, resp->len, PG_UTF8, true, false); setJsonLexContextOwnsTokens(&lex, true); /* must not leak on error */ ctx.errbuf = &actx->errbuf; diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c index 826f7461cb3..c8cc0f0cf70 100644 --- a/src/interfaces/libpq/fe-auth-oauth.c +++ b/src/interfaces/libpq/fe-auth-oauth.c @@ -547,7 +547,7 @@ handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen) return false; } - lex = makeJsonLexContextCstringLen(NULL, msg, msglen, PG_UTF8, true); + lex = makeJsonLexContextCstringLen(NULL, msg, msglen, PG_UTF8, true, false); setJsonLexContextOwnsTokens(lex, true); /* must not leak on error */ initPQExpBuffer(&ctx.errbuf); diff --git a/src/test/modules/test_escape/test_escape.c b/src/test/modules/test_escape/test_escape.c index 6234a9bd129..7da1241a8ac 100644 --- a/src/test/modules/test_escape/test_escape.c +++ b/src/test/modules/test_escape/test_escape.c @@ -235,7 +235,7 @@ test_gb18030_json(pe_test_config *tc) /* test itself */ lex = makeJsonLexContextCstringLen(NULL, raw_buf->data, input_len, - PG_GB18030, false); + PG_GB18030, false, false); json_error = pg_parse_json(lex, &sem); report_result(tc, json_error == JSON_UNICODE_ESCAPE_FORMAT, testname->data, "", diff --git a/src/test/modules/test_json_parser/README b/src/test/modules/test_json_parser/README index 61e7c78d588..494ce80fcf0 100644 --- a/src/test/modules/test_json_parser/README +++ b/src/test/modules/test_json_parser/README @@ -10,8 +10,8 @@ This module contains two programs for testing the json parsers. alternative chunk size, "-r nn" runs a range of chunk sizes down to one byte on the same input (with output separated by null bytes), and "-s" specifies using semantic routines. The semantic routines re-output the json, although - not in a very pretty form. The required non-option argument is the input file - name. + not in a very pretty form. The option "--json5" makes the parser accept + JSON5 input. The required non-option argument is the input file name. - `test_json_parser_perf` is for speed testing both the standard recursive descent parser and the non-recursive incremental parser. If given the `-i` flag it uses the non-recursive parser, diff --git a/src/test/modules/test_json_parser/json5_comments.json5 b/src/test/modules/test_json_parser/json5_comments.json5 new file mode 100644 index 00000000000..9cc9e539f94 --- /dev/null +++ b/src/test/modules/test_json_parser/json5_comments.json5 @@ -0,0 +1,12 @@ +// leading line comment +{ + /* block comment before key */ "key1": "value1", // trailing line comment on a pair + "key2": /* inline block comment */ "value2", + "arr": [ + 1, + /* mid-array block comment */ + 2, + 3 + ] +} +/* trailing block comment */ diff --git a/src/test/modules/test_json_parser/json5_comments.out b/src/test/modules/test_json_parser/json5_comments.out new file mode 100644 index 00000000000..1ad7126976e --- /dev/null +++ b/src/test/modules/test_json_parser/json5_comments.out @@ -0,0 +1,10 @@ +{ +"key1": "value1", +"key2": "value2", +"arr": [ +1, +2, +3 +] + +} diff --git a/src/test/modules/test_json_parser/meson.build b/src/test/modules/test_json_parser/meson.build index 2688686e37b..b56a1b3ce22 100644 --- a/src/test/modules/test_json_parser/meson.build +++ b/src/test/modules/test_json_parser/meson.build @@ -59,7 +59,8 @@ tests += { 't/001_test_json_parser_incremental.pl', 't/002_inline.pl', 't/003_test_semantic.pl', - 't/004_test_parser_perf.pl' + 't/004_test_parser_perf.pl', + 't/005_test_json5.pl' ], 'deps': [ test_json_parser_incremental, diff --git a/src/test/modules/test_json_parser/t/005_test_json5.pl b/src/test/modules/test_json_parser/t/005_test_json5.pl new file mode 100644 index 00000000000..7aac5005ab6 --- /dev/null +++ b/src/test/modules/test_json_parser/t/005_test_json5.pl @@ -0,0 +1,99 @@ + +# Copyright (c) 2021-2026, PostgreSQL Global Development Group + +# Test JSON5 support in the incremental JSON parser. Each feature +# fixture must be accepted in --json5 mode, whole-input and +# byte-at-a-time, with identical semantic output, and rejected without +# --json5. The non-incremental parser is exercised by SQL-level +# regression tests (see src/test/regress/sql/json5.sql). + +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Utils; +use Test::More; +use FindBin; +use File::Temp qw(tempfile); + +my $dir = PostgreSQL::Test::Utils::tempdir; + +my @exes = ( + [ "test_json_parser_incremental", ], + [ "test_json_parser_incremental", "-o", ], + [ "test_json_parser_incremental_shlib", ], + [ "test_json_parser_incremental_shlib", "-o", ]); + +# One entry per feature: fixture basename plus, where the failing token +# is predictable, a regex for the error reported without --json5. +my @features = ( + { + name => 'comments', + file => 'json5_comments', + error => qr/Token "\/" is invalid/, + },); + +# Parse $file with --json5 and compare the semantic output against +# $expected, whole-input and one byte at a time. +sub check_accepted +{ + my ($exe, $file, $expected, $label) = @_; + + foreach my $chunk (undef, 1) + { + my $mode = defined $chunk ? 'byte-at-a-time' : 'whole input'; + my @cmd = (@$exe, "-s", "--json5"); + push(@cmd, "-c", $chunk) if defined $chunk; + + my ($stdout, $stderr) = run_command([ @cmd, $file ]); + + is($stderr, "", "$label ($mode): no error output"); + + my ($fh, $fname) = tempfile(DIR => $dir); + print $fh $stdout, "\n"; + close($fh); + + my @diffopts = ("-u"); + push(@diffopts, "--strip-trailing-cr") if $windows_os; + ($stdout, $stderr) = + run_command([ "diff", @diffopts, $fname, $expected ]); + + is($stdout, "", "$label ($mode): no output diff"); + is($stderr, "", "$label ($mode): no diff error"); + } +} + +# Check that parsing $file fails, matching $error on stderr if given. +sub check_rejected +{ + my ($exe, $file, $label, $error, @flags) = @_; + + my ($stdout, $stderr) = run_command([ @$exe, "-s", @flags, $file ]); + + unlike($stdout, qr/SUCCESS/, "$label: parsing fails"); + if (defined $error) + { + like($stderr, $error, "$label: correct error output"); + } + else + { + isnt($stderr, "", "$label: error output"); + } +} + +foreach my $exe (@exes) +{ + note "testing executable @$exe"; + + foreach my $f (@features) + { + my $file = "$FindBin::RealBin/../$f->{file}.json5"; + my $expected = "$FindBin::RealBin/../$f->{file}.out"; + + check_accepted($exe, $file, $expected, "json5 $f->{name}"); + check_rejected($exe, $file, "non-json5 mode: $f->{name}", + $f->{error}); + } + +} + +done_testing(); diff --git a/src/test/modules/test_json_parser/test_json_parser_incremental.c b/src/test/modules/test_json_parser/test_json_parser_incremental.c index 8fbd180c861..63c15acdb52 100644 --- a/src/test/modules/test_json_parser/test_json_parser_incremental.c +++ b/src/test/modules/test_json_parser/test_json_parser_incremental.c @@ -41,9 +41,9 @@ #include "common/jsonapi.h" #include "common/logging.h" +#include "getopt_long.h" #include "lib/stringinfo.h" #include "mb/pg_wchar.h" -#include "pg_getopt.h" #define BUFSIZE 6000 #define DEFAULT_CHUNK_SIZE 60 @@ -99,15 +99,21 @@ main(int argc, char **argv) char *testfile; int c; bool need_strings = false; + bool json5 = false; int ret = 0; + static const struct option long_options[] = { + {"json5", no_argument, NULL, '5'}, + {NULL, 0, NULL, 0}, + }; + pg_logging_init(argv[0]); lex = calloc(1, sizeof(JsonLexContext)); if (!lex) pg_fatal("out of memory"); - while ((c = getopt(argc, argv, "r:c:os")) != -1) + while ((c = getopt_long(argc, argv, "r:c:os", long_options, NULL)) != -1) { switch (c) { @@ -129,6 +135,9 @@ main(int argc, char **argv) ((struct DoState *) sem.semstate)->buf = makeStringInfo(); need_strings = true; break; + case '5': /* parse as json5 */ + json5 = true; + break; } } @@ -161,7 +170,7 @@ main(int argc, char **argv) off_t bytes_left = statbuf.st_size; size_t to_read = chunk_size; - makeJsonLexContextIncremental(lex, PG_UTF8, need_strings); + makeJsonLexContextIncremental(lex, PG_UTF8, need_strings, json5); setJsonLexContextOwnsTokens(lex, lex_owns_tokens); rewind(json_file); @@ -406,5 +415,6 @@ usage(const char *progname) fprintf(stderr, " -c chunksize size of piece fed to parser (default 64)\n"); fprintf(stderr, " -o set JSONLEX_CTX_OWNS_TOKENS for leak checking\n"); fprintf(stderr, " -s do semantic processing\n"); + fprintf(stderr, " --json5 parse input as json5\n"); } diff --git a/src/test/modules/test_json_parser/test_json_parser_perf.c b/src/test/modules/test_json_parser/test_json_parser_perf.c index 9786263a191..0ca71c9c0a4 100644 --- a/src/test/modules/test_json_parser/test_json_parser_perf.c +++ b/src/test/modules/test_json_parser/test_json_parser_perf.c @@ -67,7 +67,7 @@ main(int argc, char **argv) { if (use_inc) { - lex = makeJsonLexContextIncremental(NULL, PG_UTF8, false); + lex = makeJsonLexContextIncremental(NULL, PG_UTF8, false, false); result = pg_parse_json_incremental(lex, &nullSemAction, json.data, json.len, true); @@ -76,7 +76,7 @@ main(int argc, char **argv) else { lex = makeJsonLexContextCstringLen(NULL, json.data, json.len, - PG_UTF8, false); + PG_UTF8, false, false); result = pg_parse_json(lex, &nullSemAction); freeJsonLexContext(lex); } diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index ffb413ab612..68a93e27ab9 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1433,6 +1433,7 @@ JoinTreeItem JoinType JsObject JsValue +Json5CommentState JsonAggConstructor JsonAggState JsonArgument -- 2.43.0