since-v26.diff.txt

text/plain

Filename: since-v26.diff.txt
Type: text/plain
Part: 0
Message: Re: [PoC] Federated Authn/z with OAUTHBEARER
1:  b3e925b9a9 ! 1:  202b9ecef6 common/jsonapi: support libpq as a client
    @@ Commit message
     
         Based on a patch by Michael Paquier.
     
    -    For frontend code, use PQExpBuffer instead of StringInfo. This requires
    -    us to track allocation failures so that we can return JSON_OUT_OF_MEMORY
    -    as needed rather than exit()ing.
    +    For libpq, use PQExpBuffer instead of StringInfo. This requires us to
    +    track allocation failures so that we can return JSON_OUT_OF_MEMORY as
    +    needed rather than exit()ing.
     
         Co-authored-by: Michael Paquier <michael@paquier.xyz>
         Co-authored-by: Daniel Gustafsson <daniel@yesql.se>
     
    - ## src/bin/pg_combinebackup/Makefile ##
    -@@ src/bin/pg_combinebackup/Makefile: include $(top_builddir)/src/Makefile.global
    - 
    - override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
    - LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils
    -+# TODO: fix this properly
    -+LDFLAGS_INTERNAL += -lpgcommon $(libpq_pgport)
    - 
    - OBJS = \
    - 	$(WIN32RES) \
    -@@ src/bin/pg_combinebackup/Makefile: OBJS = \
    - 
    - all: pg_combinebackup
    - 
    --pg_combinebackup: $(OBJS) | submake-libpgport submake-libpgfeutils
    -+pg_combinebackup: $(OBJS) | submake-libpq submake-libpgport submake-libpgfeutils
    - 	$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
    - 
    - install: all installdirs
    -
    - ## src/bin/pg_combinebackup/meson.build ##
    -@@ src/bin/pg_combinebackup/meson.build: endif
    - 
    - pg_combinebackup = executable('pg_combinebackup',
    -   pg_combinebackup_sources,
    --  dependencies: [frontend_code],
    -+  dependencies: [frontend_code, libpq],
    -   kwargs: default_bin_args,
    - )
    - bin_targets += pg_combinebackup
    -
    - ## src/bin/pg_verifybackup/Makefile ##
    -@@ src/bin/pg_verifybackup/Makefile: top_builddir = ../../..
    - include $(top_builddir)/src/Makefile.global
    - 
    - # We need libpq only because fe_utils does.
    --LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
    -+LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils -lpgcommon $(libpq_pgport)
    - 
    - OBJS = \
    - 	$(WIN32RES) \
    -
      ## src/common/Makefile ##
    -@@ src/common/Makefile: override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
    - override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
    - override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
    - 
    --override CPPFLAGS := -DFRONTEND -I. -I$(top_srcdir)/src/common $(CPPFLAGS)
    -+override CPPFLAGS := -DFRONTEND -I. -I$(top_srcdir)/src/common -I$(libpq_srcdir) $(CPPFLAGS)
    - LIBS += $(PTHREAD_LIBS)
    - 
    - OBJS_COMMON = \
    +@@ src/common/Makefile: endif
    + # a matter of policy, because it is not appropriate for general purpose
    + # libraries such as libpq to report errors directly.  fe_memutils.c is
    + # excluded because libpq must not exit() on allocation failure.
    ++#
    ++# The excluded files for _shlib builds are pulled into their own static
    ++# library, for the benefit of test programs that need not follow the
    ++# shlib rules.
    + OBJS_FRONTEND_SHLIB = \
    + 	$(OBJS_COMMON) \
    + 	restricted_token.o \
    + 	sprompt.o
    +-OBJS_FRONTEND = \
    +-	$(OBJS_FRONTEND_SHLIB) \
    ++OBJS_EXCLUDED_SHLIB = \
    + 	fe_memutils.o \
    + 	logging.o
    ++OBJS_FRONTEND = \
    ++	$(OBJS_FRONTEND_SHLIB) \
    ++	$(OBJS_EXCLUDED_SHLIB)
    + 
    + # foo.o, foo_shlib.o, and foo_srv.o are all built from foo.c
    + OBJS_SHLIB = $(OBJS_FRONTEND_SHLIB:%.o=%_shlib.o)
    +@@ src/common/Makefile: TOOLSDIR = $(top_srcdir)/src/tools
    + GEN_KEYWORDLIST = $(PERL) -I $(TOOLSDIR) $(TOOLSDIR)/gen_keywordlist.pl
    + GEN_KEYWORDLIST_DEPS = $(TOOLSDIR)/gen_keywordlist.pl $(TOOLSDIR)/PerfectHash.pm
    + 
    +-all: libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
    ++all: libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a libpgcommon_excluded_shlib.a
    + 
    + # libpgcommon is needed by some contrib
    + install: all installdirs
    +@@ src/common/Makefile: libpgcommon_shlib.a: $(OBJS_SHLIB)
    + 	rm -f $@
    + 	$(AR) $(AROPT) $@ $^
    + 
    ++# The JSON API normally exits on out-of-memory; disable that behavior for shared
    ++# library builds. This requires libpq's pqexpbuffer.h.
    ++jsonapi_shlib.o: override CPPFLAGS += -DJSONAPI_USE_PQEXPBUFFER
    ++jsonapi_shlib.o: override CPPFLAGS += -I$(libpq_srcdir)
    ++
    + # Because this uses its own compilation rule, it doesn't use the
    + # dependency tracking logic from Makefile.global.  To make sure that
    + # dependency tracking works anyway for the *_shlib.o files, depend on
    +@@ src/common/Makefile: libpgcommon_shlib.a: $(OBJS_SHLIB)
    + %_shlib.o: %.c %.o
    + 	$(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) -c $< -o $@
    + 
    ++libpgcommon_excluded_shlib.a: $(OBJS_EXCLUDED_SHLIB)
    ++	rm -f $@
    ++	$(AR) $(AROPT) $@ $^
    ++
    + #
    + # Server versions of object files
    + #
    +@@ src/common/Makefile: RYU_OBJS = $(RYU_FILES) $(RYU_FILES:%.o=%_shlib.o) $(RYU_FILES:%.o=%_srv.o)
    + $(RYU_OBJS): CFLAGS += $(PERMIT_DECLARATION_AFTER_STATEMENT)
    + 
    + clean distclean:
    +-	rm -f libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
    ++	rm -f libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a libpgcommon_excluded_shlib.a
    + 	rm -f $(OBJS_FRONTEND) $(OBJS_SHLIB) $(OBJS_SRV)
    + 	rm -f kwlist_d.h
     
      ## src/common/jsonapi.c ##
     @@
    @@ src/common/jsonapi.c
      #include "port/pg_lfind.h"
      
     -#ifndef FRONTEND
    -+#ifdef FRONTEND
    ++#ifdef JSONAPI_USE_PQEXPBUFFER
     +#include "pqexpbuffer.h"
     +#else
     +#include "lib/stringinfo.h"
    @@ src/common/jsonapi.c
      #endif
      
     +/*
    -+ * In backend, we will use palloc/pfree along with StringInfo.  In frontend,
    ++ * By default, we will use palloc/pfree along with StringInfo.  In libpq,
     + * use malloc and PQExpBuffer, and return JSON_OUT_OF_MEMORY on out-of-memory.
     + */
    -+#ifdef FRONTEND
    ++#ifdef JSONAPI_USE_PQEXPBUFFER
     +
     +#define STRDUP(s) strdup(s)
     +#define ALLOC(size) malloc(size)
    @@ src/common/jsonapi.c
     +#define REALLOC realloc
     +#define FREE(s) free(s)
     +
    -+#define appendStrVal			appendPQExpBuffer
    -+#define appendBinaryStrVal		appendBinaryPQExpBuffer
    -+#define appendStrValChar		appendPQExpBufferChar
    ++#define jsonapi_appendStringInfo			appendPQExpBuffer
    ++#define jsonapi_appendBinaryStringInfo		appendBinaryPQExpBuffer
    ++#define jsonapi_appendStringInfoChar		appendPQExpBufferChar
     +/* XXX should we add a macro version to PQExpBuffer? */
    -+#define appendStrValCharMacro	appendPQExpBufferChar
    -+#define createStrVal			createPQExpBuffer
    -+#define initStrVal				initPQExpBuffer
    -+#define resetStrVal				resetPQExpBuffer
    -+#define termStrVal				termPQExpBuffer
    -+#define destroyStrVal			destroyPQExpBuffer
    ++#define jsonapi_appendStringInfoCharMacro	appendPQExpBufferChar
    ++#define jsonapi_createStringInfo			createPQExpBuffer
    ++#define jsonapi_initStringInfo				initPQExpBuffer
    ++#define jsonapi_resetStringInfo				resetPQExpBuffer
    ++#define jsonapi_termStringInfo				termPQExpBuffer
    ++#define jsonapi_destroyStringInfo			destroyPQExpBuffer
     +
    -+#else							/* !FRONTEND */
    ++#else							/* !JSONAPI_USE_PQEXPBUFFER */
     +
     +#define STRDUP(s) pstrdup(s)
     +#define ALLOC(size) palloc(size)
     +#define ALLOC0(size) palloc0(size)
     +#define REALLOC repalloc
     +
    ++#ifdef FRONTEND
    ++#define FREE pfree
    ++#else
     +/*
     + * Backend pfree() doesn't handle NULL pointers like the frontend's does; smooth
     + * that over to reduce mental gymnastics. Avoid multiple evaluation of the macro
    @@ src/common/jsonapi.c
     +	if (__v)			\
     +		pfree(__v);		\
     +} while (0)
    ++#endif
     +
    -+#define appendStrVal			appendStringInfo
    -+#define appendBinaryStrVal		appendBinaryStringInfo
    -+#define appendStrValChar		appendStringInfoChar
    -+#define appendStrValCharMacro	appendStringInfoCharMacro
    -+#define createStrVal			makeStringInfo
    -+#define initStrVal				initStringInfo
    -+#define resetStrVal				resetStringInfo
    -+#define termStrVal(s)			pfree((s)->data)
    -+#define destroyStrVal			destroyStringInfo
    ++#define jsonapi_appendStringInfo			appendStringInfo
    ++#define jsonapi_appendBinaryStringInfo		appendBinaryStringInfo
    ++#define jsonapi_appendStringInfoChar		appendStringInfoChar
    ++#define jsonapi_appendStringInfoCharMacro	appendStringInfoCharMacro
    ++#define jsonapi_createStringInfo			makeStringInfo
    ++#define jsonapi_initStringInfo				initStringInfo
    ++#define jsonapi_resetStringInfo				resetStringInfo
    ++#define jsonapi_termStringInfo(s)			pfree((s)->data)
    ++#define jsonapi_destroyStringInfo			destroyStringInfo
     +
    -+#endif
    ++#endif							/* JSONAPI_USE_PQEXPBUFFER */
     +
      /*
       * The context of the parser is maintained by the recursive descent
    @@ src/common/jsonapi.c: static JsonParseErrorType parse_object(JsonLexContext *lex
      
      /* the null action object used for pure validation */
      const JsonSemAction nullSemAction =
    +@@ src/common/jsonapi.c: const JsonSemAction nullSemAction =
    + 	NULL, NULL, NULL, NULL, NULL
    + };
    + 
    ++/* sentinels used for out-of-memory conditions */
    ++static JsonLexContext failed_oom;
    ++static JsonIncrementalState failed_inc_oom;
    ++
    + /* Parser support routines */
    + 
    + /*
     @@ src/common/jsonapi.c: IsValidJsonNumber(const char *str, size_t len)
      {
      	bool		numeric_error;
    @@ src/common/jsonapi.c: IsValidJsonNumber(const char *str, size_t len)
       * freeJsonLexContext() or (in backend environment) via memory context
       * cleanup.
     + *
    -+ * In frontend code this can return NULL on OOM, so callers must inspect the
    -+ * returned pointer.
    ++ * In shlib code, any out-of-memory failures will be deferred to time
    ++ * of use; this function is guaranteed to return a valid JsonLexContext.
       */
      JsonLexContext *
      makeJsonLexContextCstringLen(JsonLexContext *lex, const char *json,
    @@ src/common/jsonapi.c: makeJsonLexContextCstringLen(JsonLexContext *lex, const ch
     -		lex = palloc0(sizeof(JsonLexContext));
     +		lex = ALLOC0(sizeof(JsonLexContext));
     +		if (!lex)
    -+			return NULL;
    ++			return &failed_oom;
      		lex->flags |= JSONLEX_FREE_STRUCT;
      	}
      	else
    @@ src/common/jsonapi.c: makeJsonLexContextCstringLen(JsonLexContext *lex, const ch
      	{
     -		lex->strval = makeStringInfo();
     +		/*
    -+		 * This call can fail in FRONTEND code. We defer error handling to
    -+		 * time of use (json_lex_string()) since we might not need to parse
    -+		 * any strings anyway.
    ++		 * This call can fail in shlib code. We defer error handling to time
    ++		 * of use (json_lex_string()) since we might not need to parse any
    ++		 * strings anyway.
     +		 */
    -+		lex->strval = createStrVal();
    ++		lex->strval = jsonapi_createStringInfo();
      		lex->flags |= JSONLEX_FREE_STRVAL;
     +		lex->parse_strval = true;
      	}
    @@ src/common/jsonapi.c: makeJsonLexContextCstringLen(JsonLexContext *lex, const ch
      
     +/*
     + * Allocates the internal bookkeeping structures for incremental parsing. This
    -+ * can only fail in-band with FRONTEND code.
    ++ * can only fail in-band with shlib code.
     + */
     +#define JS_STACK_CHUNK_SIZE 64
     +#define JS_MAX_PROD_LEN 10		/* more than we need */
    @@ src/common/jsonapi.c: makeJsonLexContextCstringLen(JsonLexContext *lex, const ch
     +	fnames = ALLOC(JS_STACK_CHUNK_SIZE * sizeof(char *));
     +	fnull = ALLOC(JS_STACK_CHUNK_SIZE * sizeof(bool));
     +
    -+#ifdef FRONTEND
    ++#ifdef JSONAPI_USE_PQEXPBUFFER
     +	if (!lex->inc_state
     +		|| !pstack
     +		|| !prediction
    @@ src/common/jsonapi.c: makeJsonLexContextCstringLen(JsonLexContext *lex, const ch
     +		FREE(fnames);
     +		FREE(fnull);
     +
    ++		lex->inc_state = &failed_inc_oom;
     +		return false;
     +	}
     +#endif
     +
    -+	initStrVal(&(lex->inc_state->partial_token));
    ++	jsonapi_initStringInfo(&(lex->inc_state->partial_token));
     +	lex->pstack = pstack;
     +	lex->pstack->stack_size = JS_STACK_CHUNK_SIZE;
     +	lex->pstack->prediction = prediction;
    @@ src/common/jsonapi.c: makeJsonLexContextCstringLen(JsonLexContext *lex, const ch
       * parse routine. We also need an accumulator for partial tokens in case
       * the boundary between chunks happens to fall in the middle of a token.
     + *
    -+ * In frontend code this can return NULL on OOM, so callers must inspect the
    -+ * returned pointer.
    ++ * In shlib code, any out-of-memory failures will be deferred to time of use;
    ++ * this function is guaranteed to return a valid JsonLexContext.
       */
     -#define JS_STACK_CHUNK_SIZE 64
     -#define JS_MAX_PROD_LEN 10		/* more than we need */
    @@ src/common/jsonapi.c: makeJsonLexContextCstringLen(JsonLexContext *lex, const ch
     -		lex = palloc0(sizeof(JsonLexContext));
     +		lex = ALLOC0(sizeof(JsonLexContext));
     +		if (!lex)
    -+			return NULL;
    ++			return &failed_oom;
     +
      		lex->flags |= JSONLEX_FREE_STRUCT;
      	}
    @@ src/common/jsonapi.c: makeJsonLexContextIncremental(JsonLexContext *lex, int enc
     +	if (!allocate_incremental_state(lex))
     +	{
     +		if (lex->flags & JSONLEX_FREE_STRUCT)
    ++		{
     +			FREE(lex);
    -+		return NULL;
    ++			return &failed_oom;
    ++		}
    ++
    ++		/* lex->inc_state tracks the OOM failure; we can return here. */
    ++		return lex;
     +	}
     +
      	if (need_escapes)
      	{
     -		lex->strval = makeStringInfo();
     +		/*
    -+		 * This call can fail in FRONTEND code. We defer error handling to
    -+		 * time of use (json_lex_string()) since we might not need to parse
    -+		 * any strings anyway.
    ++		 * This call can fail in shlib code. We defer error handling to time
    ++		 * of use (json_lex_string()) since we might not need to parse any
    ++		 * strings anyway.
     +		 */
    -+		lex->strval = createStrVal();
    ++		lex->strval = jsonapi_createStringInfo();
      		lex->flags |= JSONLEX_FREE_STRVAL;
     +		lex->parse_strval = true;
      	}
    @@ src/common/jsonapi.c: makeJsonLexContextIncremental(JsonLexContext *lex, int enc
     +							 new_stack_size * sizeof(char *));
     +		new_fnull = REALLOC(lex->pstack->fnull, new_stack_size * sizeof(bool));
     +
    -+#ifdef FRONTEND
    ++#ifdef JSONAPI_USE_PQEXPBUFFER
     +		if (!new_prediction || !new_fnames || !new_fnull)
     +			return false;
     +#endif
    @@ src/common/jsonapi.c: get_fnull(JsonLexContext *lex)
      {
     +	static const JsonLexContext empty = {0};
     +
    -+	if (!lex)
    ++	if (!lex || lex == &failed_oom)
     +		return;
     +
      	if (lex->flags & JSONLEX_FREE_STRVAL)
     -		destroyStringInfo(lex->strval);
    -+		destroyStrVal(lex->strval);
    ++		jsonapi_destroyStringInfo(lex->strval);
      
      	if (lex->errormsg)
     -		destroyStringInfo(lex->errormsg);
    -+		destroyStrVal(lex->errormsg);
    ++		jsonapi_destroyStringInfo(lex->errormsg);
      
      	if (lex->incremental)
      	{
    @@ src/common/jsonapi.c: get_fnull(JsonLexContext *lex)
     -		pfree(lex->pstack->fnames);
     -		pfree(lex->pstack->fnull);
     -		pfree(lex->pstack);
    -+		termStrVal(&lex->inc_state->partial_token);
    ++		jsonapi_termStringInfo(&lex->inc_state->partial_token);
     +		FREE(lex->inc_state);
     +		FREE(lex->pstack->prediction);
     +		FREE(lex->pstack->fnames);
    @@ src/common/jsonapi.c: JsonParseErrorType
      
      	return pg_parse_json_incremental(lex, sem, lex->input, lex->input_length, true);
      
    +@@ src/common/jsonapi.c: pg_parse_json(JsonLexContext *lex, const JsonSemAction *sem)
    + 	JsonTokenType tok;
    + 	JsonParseErrorType result;
    + 
    ++	if (lex == &failed_oom)
    ++		return JSON_OUT_OF_MEMORY;
    + 	if (lex->incremental)
    + 		return JSON_INVALID_LEXER_TYPE;
    + 
     @@ src/common/jsonapi.c: json_count_array_elements(JsonLexContext *lex, int *elements)
    + 	int			count;
    + 	JsonParseErrorType result;
    + 
    ++	if (lex == &failed_oom)
    ++		return JSON_OUT_OF_MEMORY;
    ++
    + 	/*
    + 	 * It's safe to do this with a shallow copy because the lexical routines
    + 	 * don't scribble on the input. They do scribble on the other pointers
      	 * etc, so doing this with a copy makes that safe.
      	 */
      	memcpy(&copylex, lex, sizeof(JsonLexContext));
    @@ src/common/jsonapi.c: json_count_array_elements(JsonLexContext *lex, int *elemen
      	copylex.lex_level++;
      
      	count = 0;
    +@@ src/common/jsonapi.c: pg_parse_json_incremental(JsonLexContext *lex,
    + 	JsonParseContext ctx = JSON_PARSE_VALUE;
    + 	JsonParserStack *pstack = lex->pstack;
    + 
    +-
    ++	if (lex == &failed_oom || lex->inc_state == &failed_inc_oom)
    ++		return JSON_OUT_OF_MEMORY;
    + 	if (!lex->incremental)
    + 		return JSON_INVALID_LEXER_TYPE;
    + 
     @@ src/common/jsonapi.c: pg_parse_json_incremental(JsonLexContext *lex,
      							if (result != JSON_SUCCESS)
      								return result;
    @@ src/common/jsonapi.c: json_lex(JsonLexContext *lex)
      	JsonParseErrorType result;
      
     -	if (lex->incremental && lex->inc_state->partial_completed)
    ++	if (lex == &failed_oom || lex->inc_state == &failed_inc_oom)
    ++		return JSON_OUT_OF_MEMORY;
    ++
     +	if (lex->incremental)
      	{
     -		/*
    @@ src/common/jsonapi.c: json_lex(JsonLexContext *lex)
     +			 * We just lexed a completed partial token on the last call, so
     +			 * reset everything
     +			 */
    -+			resetStrVal(&(lex->inc_state->partial_token));
    ++			jsonapi_resetStringInfo(&(lex->inc_state->partial_token));
     +			lex->token_terminator = lex->input;
     +			lex->inc_state->partial_completed = false;
     +		}
     +
    -+#ifdef FRONTEND
    ++#ifdef JSONAPI_USE_PQEXPBUFFER
     +		/* Make sure our partial token buffer is valid before using it below. */
     +		if (PQExpBufferDataBroken(lex->inc_state->partial_token))
     +			return JSON_OUT_OF_MEMORY;
    @@ src/common/jsonapi.c: json_lex(JsonLexContext *lex)
      				char		c = lex->input[i];
      
     -				appendStringInfoCharMacro(ptok, c);
    -+				appendStrValCharMacro(ptok, c);
    ++				jsonapi_appendStringInfoCharMacro(ptok, c);
      				added++;
      				if (c == '"' && escapes % 2 == 0)
      				{
    @@ src/common/jsonapi.c: json_lex(JsonLexContext *lex)
      						case '9':
      							{
     -								appendStringInfoCharMacro(ptok, cc);
    -+								appendStrValCharMacro(ptok, cc);
    ++								jsonapi_appendStringInfoCharMacro(ptok, cc);
      								added++;
      							}
      							break;
    @@ src/common/jsonapi.c: json_lex(JsonLexContext *lex)
      				if (JSON_ALPHANUMERIC_CHAR(cc))
      				{
     -					appendStringInfoCharMacro(ptok, cc);
    -+					appendStrValCharMacro(ptok, cc);
    ++					jsonapi_appendStringInfoCharMacro(ptok, cc);
      					added++;
      				}
      				else
    @@ src/common/jsonapi.c: json_lex(JsonLexContext *lex)
      					{
     -						appendBinaryStringInfo(
     -											   &(lex->inc_state->partial_token), s, end - s);
    -+						appendBinaryStrVal(
    -+										   &(lex->inc_state->partial_token), s, end - s);
    ++						jsonapi_appendBinaryStringInfo(&(lex->inc_state->partial_token), s, end - s);
      						return JSON_INCOMPLETE;
      					}
      
    @@ src/common/jsonapi.c: json_lex_string(JsonLexContext *lex)
      		{ \
     -			appendBinaryStringInfo(&lex->inc_state->partial_token, \
     -								   lex->token_start, end - lex->token_start); \
    -+			appendBinaryStrVal(&lex->inc_state->partial_token, \
    -+							   lex->token_start, end - lex->token_start); \
    ++			jsonapi_appendBinaryStringInfo(&lex->inc_state->partial_token, \
    ++										   lex->token_start, \
    ++										   end - lex->token_start); \
      			return JSON_INCOMPLETE; \
      		} \
      		lex->token_terminator = s; \
    @@ src/common/jsonapi.c: json_lex_string(JsonLexContext *lex)
     -		resetStringInfo(lex->strval);
     +	if (lex->parse_strval)
     +	{
    -+#ifdef FRONTEND
    ++#ifdef JSONAPI_USE_PQEXPBUFFER
     +		/* make sure initialization succeeded */
     +		if (lex->strval == NULL)
     +			return JSON_OUT_OF_MEMORY;
     +#endif
    -+		resetStrVal(lex->strval);
    ++		jsonapi_resetStringInfo(lex->strval);
     +	}
      
      	Assert(lex->input_length > 0);
    @@ src/common/jsonapi.c: json_lex_string(JsonLexContext *lex)
      						unicode_to_utf8(ch, (unsigned char *) utf8str);
      						utf8len = pg_utf_mblen((unsigned char *) utf8str);
     -						appendBinaryStringInfo(lex->strval, utf8str, utf8len);
    -+						appendBinaryPQExpBuffer(lex->strval, utf8str, utf8len);
    ++						jsonapi_appendBinaryStringInfo(lex->strval, utf8str, utf8len);
      					}
      					else if (ch <= 0x007f)
      					{
      						/* The ASCII range is the same in all encodings */
     -						appendStringInfoChar(lex->strval, (char) ch);
    -+						appendPQExpBufferChar(lex->strval, (char) ch);
    ++						jsonapi_appendStringInfoChar(lex->strval, (char) ch);
      					}
      					else
      						FAIL_AT_CHAR_END(JSON_UNICODE_HIGH_ESCAPE);
    @@ src/common/jsonapi.c: json_lex_string(JsonLexContext *lex)
      					case '\\':
      					case '/':
     -						appendStringInfoChar(lex->strval, *s);
    -+						appendStrValChar(lex->strval, *s);
    ++						jsonapi_appendStringInfoChar(lex->strval, *s);
      						break;
      					case 'b':
     -						appendStringInfoChar(lex->strval, '\b');
    -+						appendStrValChar(lex->strval, '\b');
    ++						jsonapi_appendStringInfoChar(lex->strval, '\b');
      						break;
      					case 'f':
     -						appendStringInfoChar(lex->strval, '\f');
    -+						appendStrValChar(lex->strval, '\f');
    ++						jsonapi_appendStringInfoChar(lex->strval, '\f');
      						break;
      					case 'n':
     -						appendStringInfoChar(lex->strval, '\n');
    -+						appendStrValChar(lex->strval, '\n');
    ++						jsonapi_appendStringInfoChar(lex->strval, '\n');
      						break;
      					case 'r':
     -						appendStringInfoChar(lex->strval, '\r');
    -+						appendStrValChar(lex->strval, '\r');
    ++						jsonapi_appendStringInfoChar(lex->strval, '\r');
      						break;
      					case 't':
     -						appendStringInfoChar(lex->strval, '\t');
    -+						appendStrValChar(lex->strval, '\t');
    ++						jsonapi_appendStringInfoChar(lex->strval, '\t');
      						break;
      					default:
      
    @@ src/common/jsonapi.c: json_lex_string(JsonLexContext *lex)
      			/*
      			 * Skip to the first byte that requires special handling, so we
     -			 * can batch calls to appendBinaryStringInfo.
    -+			 * can batch calls to appendBinaryStrVal.
    ++			 * can batch calls to jsonapi_appendBinaryStringInfo.
      			 */
      			while (p < end - sizeof(Vector8) &&
      				   !pg_lfind8('\\', (uint8 *) p, sizeof(Vector8)) &&
    @@ src/common/jsonapi.c: json_lex_string(JsonLexContext *lex)
     -			if (lex->strval != NULL)
     -				appendBinaryStringInfo(lex->strval, s, p - s);
     +			if (lex->parse_strval)
    -+				appendBinaryStrVal(lex->strval, s, p - s);
    ++				jsonapi_appendBinaryStringInfo(lex->strval, s, p - s);
      
      			/*
      			 * s will be incremented at the top of the loop, so set it to just
    @@ src/common/jsonapi.c: json_lex_string(JsonLexContext *lex)
      		return JSON_UNICODE_LOW_SURROGATE;
      	}
      
    -+#ifdef FRONTEND
    ++#ifdef JSONAPI_USE_PQEXPBUFFER
     +	if (lex->parse_strval && PQExpBufferBroken(lex->strval))
     +		return JSON_OUT_OF_MEMORY;
     +#endif
    @@ src/common/jsonapi.c: json_lex_number(JsonLexContext *lex, const char *s,
      	{
     -		appendBinaryStringInfo(&lex->inc_state->partial_token,
     -							   lex->token_start, s - lex->token_start);
    -+		appendBinaryStrVal(&lex->inc_state->partial_token,
    -+						   lex->token_start, s - lex->token_start);
    ++		jsonapi_appendBinaryStringInfo(&lex->inc_state->partial_token,
    ++									   lex->token_start, s - lex->token_start);
      		if (num_err != NULL)
      			*num_err = error;
      
    @@ src/common/jsonapi.c: report_parse_error(JsonParseContext ctx, JsonLexContext *l
      char *
      json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
      {
    -+	if (error == JSON_OUT_OF_MEMORY)
    ++	if (error == JSON_OUT_OF_MEMORY || lex == &failed_oom)
     +	{
     +		/* Short circuit. Allocating anything for this case is unhelpful. */
     +		return _("out of memory");
    @@ src/common/jsonapi.c: report_parse_error(JsonParseContext ctx, JsonLexContext *l
     +
      	if (lex->errormsg)
     -		resetStringInfo(lex->errormsg);
    -+		resetStrVal(lex->errormsg);
    ++		jsonapi_resetStringInfo(lex->errormsg);
      	else
     -		lex->errormsg = makeStringInfo();
    -+		lex->errormsg = createStrVal();
    ++		lex->errormsg = jsonapi_createStringInfo();
      
      	/*
      	 * A helper for error messages that should print the current token. The
    @@ src/common/jsonapi.c: report_parse_error(JsonParseContext ctx, JsonLexContext *l
     -	appendStringInfo((lex)->errormsg, _(format), \
     -					 (int) ((lex)->token_terminator - (lex)->token_start), \
     -					 (lex)->token_start);
    -+	appendStrVal((lex)->errormsg, _(format), \
    -+				 (int) ((lex)->token_terminator - (lex)->token_start), \
    -+				 (lex)->token_start);
    ++	jsonapi_appendStringInfo((lex)->errormsg, _(format), \
    ++							 (int) ((lex)->token_terminator - (lex)->token_start), \
    ++							 (lex)->token_start);
      
      	switch (error)
      	{
    @@ src/common/jsonapi.c: json_errdetail(JsonParseErrorType error, JsonLexContext *l
     -			appendStringInfo(lex->errormsg,
     -							 _("Character with value 0x%02x must be escaped."),
     -							 (unsigned char) *(lex->token_terminator));
    -+			appendStrVal(lex->errormsg,
    -+						 _("Character with value 0x%02x must be escaped."),
    -+						 (unsigned char) *(lex->token_terminator));
    ++			jsonapi_appendStringInfo(lex->errormsg,
    ++									 _("Character with value 0x%02x must be escaped."),
    ++									 (unsigned char) *(lex->token_terminator));
      			break;
      		case JSON_EXPECTED_END:
      			json_token_error(lex, "Expected end of input, but found \"%.*s\".");
    @@ src/common/jsonapi.c: json_errdetail(JsonParseErrorType error, JsonLexContext *l
     -		appendStringInfo(lex->errormsg,
     -						 "unexpected json parse error type: %d",
     -						 (int) error);
    -+	/* Note that lex->errormsg can be NULL in FRONTEND code. */
    ++	/* Note that lex->errormsg can be NULL in shlib code. */
     +	if (lex->errormsg && lex->errormsg->len == 0)
     +	{
     +		/*
    @@ src/common/jsonapi.c: json_errdetail(JsonParseErrorType error, JsonLexContext *l
     +		 * unhandled enum values.  But this needs to be here anyway to cover
     +		 * the possibility of an incorrect input.
     +		 */
    -+		appendStrVal(lex->errormsg,
    -+					 "unexpected json parse error type: %d",
    -+					 (int) error);
    ++		jsonapi_appendStringInfo(lex->errormsg,
    ++								 "unexpected json parse error type: %d",
    ++								 (int) error);
     +	}
     +
    -+#ifdef FRONTEND
    ++#ifdef JSONAPI_USE_PQEXPBUFFER
     +	if (PQExpBufferBroken(lex->errormsg))
     +		return _("out of memory while constructing error description");
     +#endif
    @@ src/common/jsonapi.c: json_errdetail(JsonParseErrorType error, JsonLexContext *l
      }
     
      ## src/common/meson.build ##
    -@@ src/common/meson.build: common_sources_frontend_static += files(
    - # least cryptohash_openssl.c, hmac_openssl.c depend on it.
    - # controldata_utils.c depends on wait_event_types_h. That's arguably a
    - # layering violation, but ...
    +@@ src/common/meson.build: common_sources_cflags = {
    + # a matter of policy, because it is not appropriate for general purpose
    + # libraries such as libpq to report errors directly.  fe_memutils.c is
    + # excluded because libpq must not exit() on allocation failure.
     +#
    -+# XXX Frontend builds need libpq's pqexpbuffer.h, so adjust the include paths
    -+# appropriately. This seems completely broken.
    - pgcommon = {}
    - pgcommon_variants = {
    -   '_srv': internal_lib_args + {
    -+    'include_directories': include_directories('.'),
    -     'sources': common_sources + [lwlocknames_h] + [wait_event_types_h],
    -     'dependencies': [backend_common_code],
    -   },
    -   '': default_lib_args + {
    -+    'include_directories': include_directories('../interfaces/libpq', '.'),
    -     'sources': common_sources_frontend_static,
    -     'dependencies': [frontend_common_code],
    -     # Files in libpgcommon.a should use/export the "xxx_private" versions
    ++# The excluded files for _shlib builds are pulled into their own static
    ++# library, for the benefit of test programs that need not follow the
    ++# shlib rules.
    + 
    + common_sources_frontend_shlib = common_sources
    + common_sources_frontend_shlib += files(
    +@@ src/common/meson.build: common_sources_frontend_shlib += files(
    +   'sprompt.c',
    + )
    + 
    +-common_sources_frontend_static = common_sources_frontend_shlib
    +-common_sources_frontend_static += files(
    ++common_sources_excluded_shlib = files(
    +   'fe_memutils.c',
    +   'logging.c',
    + )
    + 
    ++common_sources_frontend_static = [
    ++  common_sources_frontend_shlib,
    ++  common_sources_excluded_shlib,
    ++]
    ++
    + # Build pgcommon once for backend, once for use in frontend binaries, and
    + # once for use in shared libraries
    + #
     @@ src/common/meson.build: pgcommon_variants = {
    -   },
    -   '_shlib': default_lib_args + {
          'pic': true,
    -+    'include_directories': include_directories('../interfaces/libpq', '.'),
          'sources': common_sources_frontend_shlib,
          'dependencies': [frontend_common_code],
    ++    # The JSON API normally exits on out-of-memory; disable that behavior for
    ++    # shared library builds. This requires libpq's pqexpbuffer.h.
    ++    'c_args': ['-DJSONAPI_USE_PQEXPBUFFER'],
    ++    'include_directories': include_directories('../interfaces/libpq'),
        },
    + }
    + 
     @@ src/common/meson.build: foreach name, opts : pgcommon_variants
          c_args = opts.get('c_args', []) + common_cflags[cflagname]
          cflag_libs += static_library('libpgcommon@0@_@1@'.format(name, cflagname),
            c_pch: pch_c_h,
     -      include_directories: include_directories('.'),
            kwargs: opts + {
    ++        'include_directories': [
    ++          include_directories('.'),
    ++          opts.get('include_directories', []),
    ++        ],
              'sources': sources,
              'c_args': c_args,
    +         'build_by_default': false,
     @@ src/common/meson.build: foreach name, opts : pgcommon_variants
        lib = static_library('libpgcommon@0@'.format(name),
            link_with: cflag_libs,
            c_pch: pch_c_h,
     -      include_directories: include_directories('.'),
            kwargs: opts + {
    ++        'include_directories': [
    ++          include_directories('.'),
    ++          opts.get('include_directories', []),
    ++        ],
              'dependencies': opts['dependencies'] + [ssl],
            }
    -
    - ## src/common/parse_manifest.c ##
    -@@ src/common/parse_manifest.c: json_parse_manifest_incremental_init(JsonManifestParseContext *context)
    - 	parse->state = JM_EXPECT_TOPLEVEL_START;
    - 	parse->saw_version_field = false;
    - 
    --	makeJsonLexContextIncremental(&(incstate->lex), PG_UTF8, true);
    -+	if (!makeJsonLexContextIncremental(&(incstate->lex), PG_UTF8, true))
    -+		context->error_cb(context, "out of memory");
    - 
    - 	incstate->sem.semstate = parse;
    - 	incstate->sem.object_start = json_manifest_object_start;
    -@@ src/common/parse_manifest.c: json_parse_manifest(JsonManifestParseContext *context, const char *buffer,
    - 
    - 	/* Create a JSON lexing context. */
    - 	lex = makeJsonLexContextCstringLen(NULL, buffer, size, PG_UTF8, true);
    -+	if (!lex)
    -+		json_manifest_parse_failure(context, "out of memory");
    - 
    - 	/* Set up semantic actions. */
    - 	sem.semstate = &parse;
    +     )
    +@@ src/common/meson.build: common_srv = pgcommon['_srv']
    + common_shlib = pgcommon['_shlib']
    + common_static = pgcommon['']
    + 
    ++common_excluded_shlib = static_library('libpgcommon_excluded_shlib',
    ++  sources: common_sources_excluded_shlib,
    ++  dependencies: [frontend_common_code],
    ++  build_by_default: false,
    ++  kwargs: default_lib_args + {
    ++    'install': false,
    ++  },
    ++)
    ++
    + subdir('unicode')
     
      ## src/include/common/jsonapi.h ##
     @@
    @@ src/include/common/jsonapi.h: typedef enum JsonParseErrorType
     + * Don't depend on the internal type header for strval; if callers need access
     + * then they can include the appropriate header themselves.
     + */
    -+#ifdef FRONTEND
    ++#ifdef JSONAPI_USE_PQEXPBUFFER
     +#define StrValType PQExpBufferData
     +#else
     +#define StrValType StringInfoData
    @@ src/include/common/jsonapi.h: typedef struct JsonLexContext
      typedef JsonParseErrorType (*json_struct_action) (void *state);
     
      ## src/test/modules/test_json_parser/Makefile ##
    +@@ src/test/modules/test_json_parser/Makefile: TAP_TESTS = 1
    + 
    + OBJS = test_json_parser_incremental.o test_json_parser_perf.o $(WIN32RES)
    + 
    +-EXTRA_CLEAN = test_json_parser_incremental$(X) test_json_parser_perf$(X)
    ++EXTRA_CLEAN = test_json_parser_incremental$(X) test_json_parser_incremental_shlib$(X) test_json_parser_perf$(X)
    + 
    + ifdef USE_PGXS
    + PG_CONFIG = pg_config
     @@ src/test/modules/test_json_parser/Makefile: include $(top_builddir)/src/Makefile.global
      include $(top_srcdir)/contrib/contrib-global.mk
      endif
      
    -+# TODO: fix this properly
    -+LDFLAGS_INTERNAL += -lpgcommon $(libpq_pgport)
    -+
    - all: test_json_parser_incremental$(X) test_json_parser_perf$(X)
    +-all: test_json_parser_incremental$(X) test_json_parser_perf$(X)
    ++all: test_json_parser_incremental$(X) test_json_parser_incremental_shlib$(X) test_json_parser_perf$(X)
      
      %.o: $(top_srcdir)/$(subdir)/%.c
    + 
    + test_json_parser_incremental$(X): test_json_parser_incremental.o $(WIN32RES)
    + 	$(CC) $(CFLAGS) $^ $(PG_LIBS_INTERNAL) $(LDFLAGS) $(LDFLAGS_EX) $(PG_LIBS) $(LIBS) -o $@
    + 
    ++test_json_parser_incremental_shlib$(X): test_json_parser_incremental.o $(WIN32RES)
    ++	$(CC) $(CFLAGS) $^ $(LDFLAGS) -lpgcommon_excluded_shlib $(libpq_pgport_shlib) -o $@
    ++
    + test_json_parser_perf$(X): test_json_parser_perf.o $(WIN32RES)
    + 	$(CC) $(CFLAGS) $^ $(PG_LIBS_INTERNAL) $(LDFLAGS) $(LDFLAGS_EX) $(PG_LIBS) $(LIBS) -o $@
    + 
     
      ## src/test/modules/test_json_parser/meson.build ##
    -@@ src/test/modules/test_json_parser/meson.build: endif
    - 
    - test_json_parser_incremental = executable('test_json_parser_incremental',
    -   test_json_parser_incremental_sources,
    --  dependencies: [frontend_code],
    -+  dependencies: [frontend_code, libpq],
    -   kwargs: default_bin_args + {
    -     'install': false,
    -   },
    -@@ src/test/modules/test_json_parser/meson.build: endif
    - 
    - test_json_parser_perf = executable('test_json_parser_perf',
    -   test_json_parser_perf_sources,
    --  dependencies: [frontend_code],
    -+  dependencies: [frontend_code, libpq],
    -   kwargs: default_bin_args + {
    -     'install': false,
    +@@ src/test/modules/test_json_parser/meson.build: test_json_parser_incremental = executable('test_json_parser_incremental',
        },
    + )
    + 
    ++# A second version of test_json_parser_incremental, this time compiled against
    ++# the shared-library flavor of jsonapi.
    ++test_json_parser_incremental_shlib = executable('test_json_parser_incremental_shlib',
    ++  test_json_parser_incremental_sources,
    ++  dependencies: [frontend_shlib_code, libpq],
    ++  c_args: ['-DJSONAPI_SHLIB_ALLOC'],
    ++  link_with: [common_excluded_shlib],
    ++  kwargs: default_bin_args + {
    ++    'install': false,
    ++  },
    ++)
    ++
    + test_json_parser_perf_sources = files(
    +   'test_json_parser_perf.c',
    + )
    +
    + ## src/test/modules/test_json_parser/t/001_test_json_parser_incremental.pl ##
    +@@ src/test/modules/test_json_parser/t/001_test_json_parser_incremental.pl: use FindBin;
    + 
    + my $test_file = "$FindBin::RealBin/../tiny.json";
    + 
    +-my $exe = "test_json_parser_incremental";
    ++my @exes =
    ++  ("test_json_parser_incremental", "test_json_parser_incremental_shlib");
    + 
    +-# Test the  usage error
    +-my ($stdout, $stderr) = run_command([ $exe, "-c", 10 ]);
    +-like($stderr, qr/Usage:/, 'error message if not enough arguments');
    ++foreach my $exe (@exes)
    ++{
    ++	note "testing executable $exe";
    + 
    +-# Test that we get success for small chunk sizes from 64 down to 1.
    ++	# Test the  usage error
    ++	my ($stdout, $stderr) = run_command([ $exe, "-c", 10 ]);
    ++	like($stderr, qr/Usage:/, 'error message if not enough arguments');
    + 
    +-for (my $size = 64; $size > 0; $size--)
    +-{
    +-	($stdout, $stderr) = run_command([ $exe, "-c", $size, $test_file ]);
    ++	# Test that we get success for small chunk sizes from 64 down to 1.
    ++	for (my $size = 64; $size > 0; $size--)
    ++	{
    ++		($stdout, $stderr) = run_command([ $exe, "-c", $size, $test_file ]);
    + 
    +-	like($stdout, qr/SUCCESS/, "chunk size $size: test succeeds");
    +-	is($stderr, "", "chunk size $size: no error output");
    ++		like($stdout, qr/SUCCESS/, "chunk size $size: test succeeds");
    ++		is($stderr, "", "chunk size $size: no error output");
    ++	}
    + }
    + 
    + done_testing();
    +
    + ## src/test/modules/test_json_parser/t/002_inline.pl ##
    +@@ src/test/modules/test_json_parser/t/002_inline.pl: use Test::More;
    + use File::Temp qw(tempfile);
    + 
    + my $dir = PostgreSQL::Test::Utils::tempdir;
    ++my $exe;
    + 
    + sub test
    + {
    + 	local $Test::Builder::Level = $Test::Builder::Level + 1;
    + 
    + 	my ($name, $json, %params) = @_;
    +-	my $exe = "test_json_parser_incremental";
    + 	my $chunk = length($json);
    + 
    + 	# Test the input with chunk sizes from max(input_size, 64) down to 1
    +@@ src/test/modules/test_json_parser/t/002_inline.pl: sub test
    + 	}
    + }
    + 
    +-test("number", "12345");
    +-test("string", '"hello"');
    +-test("false", "false");
    +-test("true", "true");
    +-test("null", "null");
    +-test("empty object", "{}");
    +-test("empty array", "[]");
    +-test("array with number", "[12345]");
    +-test("array with numbers", "[12345,67890]");
    +-test("array with null", "[null]");
    +-test("array with string", '["hello"]');
    +-test("array with boolean", '[false]');
    +-test("single pair", '{"key": "value"}');
    +-test("heavily nested array", "[" x 3200 . "]" x 3200);
    +-test("serial escapes", '"\\\\\\\\\\\\\\\\"');
    +-test("interrupted escapes", '"\\\\\\"\\\\\\\\\\"\\\\"');
    +-test("whitespace", '     ""     ');
    +-
    +-test("unclosed empty object",
    +-	"{", error => qr/input string ended unexpectedly/);
    +-test("bad key", "{{", error => qr/Expected string or "}", but found "\{"/);
    +-test("bad key", "{{}", error => qr/Expected string or "}", but found "\{"/);
    +-test("numeric key", "{1234: 2}",
    +-	error => qr/Expected string or "}", but found "1234"/);
    +-test(
    +-	"second numeric key",
    +-	'{"a": "a", 1234: 2}',
    +-	error => qr/Expected string, but found "1234"/);
    +-test(
    +-	"unclosed object with pair",
    +-	'{"key": "value"',
    +-	error => qr/input string ended unexpectedly/);
    +-test("missing key value",
    +-	'{"key": }', error => qr/Expected JSON value, but found "}"/);
    +-test(
    +-	"missing colon",
    +-	'{"key" 12345}',
    +-	error => qr/Expected ":", but found "12345"/);
    +-test(
    +-	"missing comma",
    +-	'{"key": 12345 12345}',
    +-	error => qr/Expected "," or "}", but found "12345"/);
    +-test("overnested array",
    +-	"[" x 6401, error => qr/maximum permitted depth is 6400/);
    +-test("overclosed array",
    +-	"[]]", error => qr/Expected end of input, but found "]"/);
    +-test("unexpected token in array",
    +-	"[ }}} ]", error => qr/Expected array element or "]", but found "}"/);
    +-test("junk punctuation", "[ ||| ]", error => qr/Token "|" is invalid/);
    +-test("missing comma in array",
    +-	"[123 123]", error => qr/Expected "," or "]", but found "123"/);
    +-test("misspelled boolean", "tru", error => qr/Token "tru" is invalid/);
    +-test(
    +-	"misspelled boolean in array",
    +-	"[tru]",
    +-	error => qr/Token "tru" is invalid/);
    +-test("smashed top-level scalar", "12zz",
    +-	error => qr/Token "12zz" is invalid/);
    +-test(
    +-	"smashed scalar in array",
    +-	"[12zz]",
    +-	error => qr/Token "12zz" is invalid/);
    +-test(
    +-	"unknown escape sequence",
    +-	'"hello\vworld"',
    +-	error => qr/Escape sequence "\\v" is invalid/);
    +-test("unescaped control",
    +-	"\"hello\tworld\"",
    +-	error => qr/Character with value 0x09 must be escaped/);
    +-test(
    +-	"incorrect escape count",
    +-	'"\\\\\\\\\\\\\\"',
    +-	error => qr/Token ""\\\\\\\\\\\\\\"" is invalid/);
    +-
    +-# Case with three bytes: double-quote, backslash and <f5>.
    +-# Both invalid-token and invalid-escape are possible errors, because for
    +-# smaller chunk sizes the incremental parser skips the string parsing when
    +-# it cannot find an ending quote.
    +-test("incomplete UTF-8 sequence",
    +-	"\"\\\x{F5}",
    +-	error => qr/(Token|Escape sequence) ""?\\\x{F5}" is invalid/);
    ++my @exes =
    ++  ("test_json_parser_incremental", "test_json_parser_incremental_shlib");
    ++
    ++foreach (@exes)
    ++{
    ++	$exe = $_;
    ++	note "testing executable $exe";
    ++
    ++	test("number", "12345");
    ++	test("string", '"hello"');
    ++	test("false", "false");
    ++	test("true", "true");
    ++	test("null", "null");
    ++	test("empty object", "{}");
    ++	test("empty array", "[]");
    ++	test("array with number", "[12345]");
    ++	test("array with numbers", "[12345,67890]");
    ++	test("array with null", "[null]");
    ++	test("array with string", '["hello"]');
    ++	test("array with boolean", '[false]');
    ++	test("single pair", '{"key": "value"}');
    ++	test("heavily nested array", "[" x 3200 . "]" x 3200);
    ++	test("serial escapes", '"\\\\\\\\\\\\\\\\"');
    ++	test("interrupted escapes", '"\\\\\\"\\\\\\\\\\"\\\\"');
    ++	test("whitespace", '     ""     ');
    ++
    ++	test("unclosed empty object",
    ++		"{", error => qr/input string ended unexpectedly/);
    ++	test("bad key", "{{",
    ++		error => qr/Expected string or "}", but found "\{"/);
    ++	test("bad key", "{{}",
    ++		error => qr/Expected string or "}", but found "\{"/);
    ++	test("numeric key", "{1234: 2}",
    ++		error => qr/Expected string or "}", but found "1234"/);
    ++	test(
    ++		"second numeric key",
    ++		'{"a": "a", 1234: 2}',
    ++		error => qr/Expected string, but found "1234"/);
    ++	test(
    ++		"unclosed object with pair",
    ++		'{"key": "value"',
    ++		error => qr/input string ended unexpectedly/);
    ++	test("missing key value",
    ++		'{"key": }', error => qr/Expected JSON value, but found "}"/);
    ++	test(
    ++		"missing colon",
    ++		'{"key" 12345}',
    ++		error => qr/Expected ":", but found "12345"/);
    ++	test(
    ++		"missing comma",
    ++		'{"key": 12345 12345}',
    ++		error => qr/Expected "," or "}", but found "12345"/);
    ++	test("overnested array",
    ++		"[" x 6401, error => qr/maximum permitted depth is 6400/);
    ++	test("overclosed array",
    ++		"[]]", error => qr/Expected end of input, but found "]"/);
    ++	test("unexpected token in array",
    ++		"[ }}} ]", error => qr/Expected array element or "]", but found "}"/);
    ++	test("junk punctuation", "[ ||| ]", error => qr/Token "|" is invalid/);
    ++	test("missing comma in array",
    ++		"[123 123]", error => qr/Expected "," or "]", but found "123"/);
    ++	test("misspelled boolean", "tru", error => qr/Token "tru" is invalid/);
    ++	test(
    ++		"misspelled boolean in array",
    ++		"[tru]",
    ++		error => qr/Token "tru" is invalid/);
    ++	test(
    ++		"smashed top-level scalar",
    ++		"12zz",
    ++		error => qr/Token "12zz" is invalid/);
    ++	test(
    ++		"smashed scalar in array",
    ++		"[12zz]",
    ++		error => qr/Token "12zz" is invalid/);
    ++	test(
    ++		"unknown escape sequence",
    ++		'"hello\vworld"',
    ++		error => qr/Escape sequence "\\v" is invalid/);
    ++	test("unescaped control",
    ++		"\"hello\tworld\"",
    ++		error => qr/Character with value 0x09 must be escaped/);
    ++	test(
    ++		"incorrect escape count",
    ++		'"\\\\\\\\\\\\\\"',
    ++		error => qr/Token ""\\\\\\\\\\\\\\"" is invalid/);
    ++
    ++	# Case with three bytes: double-quote, backslash and <f5>.
    ++	# Both invalid-token and invalid-escape are possible errors, because for
    ++	# smaller chunk sizes the incremental parser skips the string parsing when
    ++	# it cannot find an ending quote.
    ++	test("incomplete UTF-8 sequence",
    ++		"\"\\\x{F5}",
    ++		error => qr/(Token|Escape sequence) ""?\\\x{F5}" is invalid/);
    ++}
    + 
    + done_testing();
    +
    + ## src/test/modules/test_json_parser/t/003_test_semantic.pl ##
    +@@ src/test/modules/test_json_parser/t/003_test_semantic.pl: use File::Temp qw(tempfile);
    + my $test_file = "$FindBin::RealBin/../tiny.json";
    + my $test_out = "$FindBin::RealBin/../tiny.out";
    + 
    +-my $exe = "test_json_parser_incremental";
    ++my @exes =
    ++  ("test_json_parser_incremental", "test_json_parser_incremental_shlib");
    + 
    +-my ($stdout, $stderr) = run_command([ $exe, "-s", $test_file ]);
    ++foreach my $exe (@exes)
    ++{
    ++	note "testing executable $exe";
    + 
    +-is($stderr, "", "no error output");
    ++	my ($stdout, $stderr) = run_command([ $exe, "-s", $test_file ]);
    + 
    +-my $dir = PostgreSQL::Test::Utils::tempdir;
    +-my ($fh, $fname) = tempfile(DIR => $dir);
    ++	is($stderr, "", "no error output");
    + 
    +-print $fh $stdout, "\n";
    ++	my $dir = PostgreSQL::Test::Utils::tempdir;
    ++	my ($fh, $fname) = tempfile(DIR => $dir);
    + 
    +-close($fh);
    ++	print $fh $stdout, "\n";
    + 
    +-my @diffopts = ("-u");
    +-push(@diffopts, "--strip-trailing-cr") if $windows_os;
    +-($stdout, $stderr) = run_command([ "diff", @diffopts, $fname, $test_out ]);
    ++	close($fh);
    + 
    +-is($stdout, "", "no output diff");
    +-is($stderr, "", "no diff error");
    ++	my @diffopts = ("-u");
    ++	push(@diffopts, "--strip-trailing-cr") if $windows_os;
    ++	($stdout, $stderr) =
    ++	  run_command([ "diff", @diffopts, $fname, $test_out ]);
    ++
    ++	is($stdout, "", "no output diff");
    ++	is($stderr, "", "no diff error");
    ++}
    + 
    + done_testing();
2:  da6c573a34 ! 2:  0335987632 libpq: add OAUTHBEARER SASL mechanism
    @@ src/interfaces/libpq/fe-connect.c: keep_going:						/* We will come back to here
     +					goto keep_going;
     +				}
      
    - 				/* OK, we have processed the message; mark data consumed */
    - 				conn->inStart = conn->inCursor;
    + 				/*
    + 				 * OK, we have processed the message; mark data consumed.  We
     @@ src/interfaces/libpq/fe-connect.c: keep_going:						/* We will come back to here until there is
      				goto keep_going;
      			}
    @@ src/interfaces/libpq/fe-misc.c: pqSocketCheck(PGconn *conn, int forRead, int for
     
      ## src/interfaces/libpq/libpq-fe.h ##
     @@ src/interfaces/libpq/libpq-fe.h: extern "C"
    - #define LIBPQ_HAS_TRACE_FLAGS 1
    - /* Indicates that PQsslAttribute(NULL, "library") is useful */
    - #define LIBPQ_HAS_SSL_LIBRARY_DETECTION 1
    + /* Indicates presence of PQsocketPoll, PQgetCurrentTimeUSec */
    + #define LIBPQ_HAS_SOCKET_POLL 1
    + 
    ++/* Features added in PostgreSQL v18: */
     +/* Indicates presence of the PQAUTHDATA_PROMPT_OAUTH_DEVICE authdata hook */
     +#define LIBPQ_HAS_PROMPT_OAUTH_DEVICE 1
    - 
    ++
      /*
       * Option flags for PQcopyResult
    +  */
     @@ src/interfaces/libpq/libpq-fe.h: typedef enum
      	CONNECTION_CHECK_STANDBY,	/* Checking if server is in standby mode. */
      	CONNECTION_ALLOCATED,		/* Waiting for connection attempt to be
3:  ebaaec0ecd ! 3:  a0806d7c65 backend: add OAUTHBEARER SASL mechanism
    @@ src/backend/utils/misc/guc_tables.c: struct config_string ConfigureNamesString[]
      	{
      		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
     
    - ## src/common/Makefile ##
    -@@ src/common/Makefile: override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
    - override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
    - 
    - override CPPFLAGS := -DFRONTEND -I. -I$(top_srcdir)/src/common -I$(libpq_srcdir) $(CPPFLAGS)
    --LIBS += $(PTHREAD_LIBS)
    -+LIBS += $(PTHREAD_LIBS) $(libpq_pgport)
    - 
    - OBJS_COMMON = \
    - 	archive.o \
    -
      ## src/include/libpq/auth.h ##
     @@
      
4:  78ce297b9e ! 4:  5d5694934a Review comments
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c
      /*
       * Parsed JSON Representations
       *
    -@@ src/interfaces/libpq/fe-auth-oauth-curl.c: parse_oauth_json(struct async_ctx *actx, const struct json_field *fields)
    - 		return false;
    - 	}
    - 
    --	makeJsonLexContextCstringLen(&lex, resp->data, resp->len, PG_UTF8, true);
    -+	if (!makeJsonLexContextCstringLen(&lex, resp->data, resp->len, PG_UTF8, true))
    -+	{
    -+		actx_error(actx, "out of memory");
    -+		return false;
    -+	}
    - 
    - 	ctx.errbuf = &actx->errbuf;
    - 	ctx.fields = fields;
     @@ src/interfaces/libpq/fe-auth-oauth-curl.c: setup_curl_handles(struct async_ctx *actx)
      	 * pretty strict when it comes to provider behavior, so we have to check
      	 * what comes back anyway.)
    @@ src/interfaces/libpq/fe-auth-oauth-curl.c: finish_token_request(struct async_ctx
      }
      
      /*
    -
    - ## src/interfaces/libpq/fe-auth-oauth.c ##
    -@@ src/interfaces/libpq/fe-auth-oauth.c: handle_oauth_sasl_error(PGconn *conn, char *msg, int msglen)
    - 		return false;
    - 	}
    - 
    --	makeJsonLexContextCstringLen(&lex, msg, msglen, PG_UTF8, true);
    -+	if (!makeJsonLexContextCstringLen(&lex, msg, msglen, PG_UTF8, true))
    -+	{
    -+		appendPQExpBufferStr(&conn->errorMessage,
    -+							 libpq_gettext("out of memory"));
    -+		return false;
    -+	}
    - 
    - 	initPQExpBuffer(&ctx.errbuf);
    - 	sem.semstate = &ctx;
5:  6f4709574d = 5:  b459ce7e7b DO NOT MERGE: Add pytest suite for OAuth