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 Co-authored-by: Daniel Gustafsson - ## 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 . +-# 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 . ++ # 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