since-v26.diff.txt
text/plain
Filename: since-v26.diff.txt
Type: text/plain
Part: 0
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(©lex, 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