Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1w8qSF-000w5e-2c for pgsql-hackers@arkaria.postgresql.org; Sat, 04 Apr 2026 02:09:44 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1w8qSC-00F14S-03 for pgsql-hackers@arkaria.postgresql.org; Sat, 04 Apr 2026 02:09:40 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1w8qSB-00F14E-1p for pgsql-hackers@lists.postgresql.org; Sat, 04 Apr 2026 02:09:40 +0000 Received: from mail-qv1-xf2e.google.com ([2607:f8b0:4864:20::f2e]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1w8qS9-00000000SFV-3Py8 for pgsql-hackers@lists.postgresql.org; Sat, 04 Apr 2026 02:09:39 +0000 Received: by mail-qv1-xf2e.google.com with SMTP id 6a1803df08f44-8a110e06b4cso31884596d6.1 for ; Fri, 03 Apr 2026 19:09:37 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1775268577; cv=none; d=google.com; s=arc-20240605; b=C4aa5gppxoET0Qsj9mO1ZAm3DdFecNfuoQODC19452UAcM18f9J7wiDzLpIFWFSrqF c/DVdxwfBqOBzrjsNtEVQK3d3rOemIIteAD4y0keeQYgWsOneSUD5jh7GyzXfZoGlF0B G9GSsq77UI81VGfaRQE7Xgmv7nOPEoRWm/hDjQB5wxdo+BxeFMEDmzEMrH9vgi2P1t9G gmDMF74nQC931pVbInYV5B2x2C3jJmlM9+d93qg71BgXh/AK1UmgM/30zmEJwE8tZNab cCVaf3N1Z19kTWiCOIcMLXBHx1JEFhRHSpjHoEELSyNqoa+zup1n3fMK/YgnaK6/jkbx 7oZw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=07VyFMTauam9I0kGoghXxVWzOOmuHwjNQHggiMb2ueU=; fh=AjDVXdEgSmp0J6PzxBGtMx4QF3vdqz54nIa202O7Ci4=; b=gEPZB8Oj0kqFoAnyEcg/2oMz1sF9NuCWw3tvcdbzpscb/4jYeAcuXEObmqe0A4uIfW 8B0tmY0VTOlDcsT44eN/UhJWSCjXyp0aRZ74VTmb551cZb+kCdr+16Dr/4ABG5lOdBFQ 7JX5Noow3f6WBg442PL/LihbD2N3Tkg7YopoKHmHUMdrb25Dw17IEyrkhcL3T4hRNoOt WuLNsJcUMmglvyUFOAbPoGy/H/Wj7wRWpiqqAaqZhuLvM3Ee95Tld7zwJC+CVYyJCZ48 wIZt07wgMCYl0IXb3wHeZ6yeSaVU1FMn4YPYNfwZVuywY37eTWFu4PgN4rVgcKPhgMF/ Xqnw==; darn=lists.postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fittl.com; s=google; t=1775268577; x=1775873377; darn=lists.postgresql.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=07VyFMTauam9I0kGoghXxVWzOOmuHwjNQHggiMb2ueU=; b=Ob2UGLYm6i7C2vaFpEY/gjyrzsfVoP6oaYvcY+cPdib/eJTZ52pMgg0076lJ8+SpXp R1ssqxCHK9jvBOVimLbcDY/41Za4zp7HvUONl7zkx4tIdG5Ek4GwG0QKbcz820aI0IA8 C4gtV0lvPI6V9QsGprFR6dpE6OBp5Zf+GOj6Q= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775268577; x=1775873377; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=07VyFMTauam9I0kGoghXxVWzOOmuHwjNQHggiMb2ueU=; b=PmLqCYKgrgovzZHwnFYw6pROg6ALVJm+EFcS1XLlyRpNlfBb6KoOA+/eQSYusClEqN hjvkkS5yL2Nofq6/ys9y/HmOPtK9nOA8UGrgHvU+Yc/ej7KAzD7LZgjZ8TYp24gnmQeq xy0QYRX0wKB/+o/6d4ufSrZJgnAl6HVifs5mgPpWyByqkbxT+yP3TdtE96R4qfuusUYn 0vDrAx8u6sw1dRFsZpen2ZHEUWHN2jlH/5TjHrZCQl7xzjWHgn0LmK+wSVFhPEQmISvj eWRUSmaD6tFTPbDSkjq57rgdQ9eTxonTzmeqAeMTwf7/NjBfBxNBWBSqZ4ICxkVLkgxy 5FOA== X-Forwarded-Encrypted: i=1; AJvYcCX5dqliAz2RuL4iHKQ6TWpc3CTlcjwawTKlQLXKSHHx4V09h3rQ+1HAF8PHi75OMR1L/satFVs0yteFlf9M@lists.postgresql.org X-Gm-Message-State: AOJu0Yzxdlfz9UEbFS1D9t39uFri1sfXwvK5rVq7Dt2bmDphIYvkEAkH WczdhwrDFlqgnkuuaGt/Q2GHTkoiaWqQfj5ONLjR4qkYBoYxYgKee/PTajkhJJHyrIV4acFUAdc BHR92xxQSRTh/3GoVM5WZ8MPF/G6K8yVfvdgStzdJ X-Gm-Gg: AeBDieu37dz1nf5gDAQ24pFvKAHUp0WQL07tg//HGksRAotkgB/+TYNZ6IIS+cG/GEQ 674iP0PUAY09+maPrM0UgUkvZBjBLB4zMjdf8NAx7nmoUERvtFULE4uT23FCRWtmizgGdcZMUnX sELTw2NHMEZ/SC6i/mcMbt1shjfVwzpLwdxRYqYwvOkn5OTeIr/ZzPUorAGa6mF+TMAGt24q/Qy THY3gzea1+I0AcS99+R+oiFapOm1IyhUGoMjH1UoOattoMHFteIbQzjFxAbLGU8OwoqZ5zKBN7p Lld9qk0Rygw4BXWWzfDpf0bnvCkS3BmZzPH/HmmBxBUKyaFzRdnIH2OikmNESOg1gru2MitL X-Received: by 2002:a05:6214:451f:b0:89e:f35f:79f7 with SMTP id 6a1803df08f44-8a704e9fa22mr98152136d6.51.1775268576640; Fri, 03 Apr 2026 19:09:36 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: Lukas Fittl Date: Fri, 3 Apr 2026 19:09:00 -0700 X-Gm-Features: AQROBzCMN87y0i1nSumMH835oFNcEt2pMqcEmpwqUkkOIX05IMgbBGnensp-GYs Message-ID: Subject: Re: Add custom EXPLAIN options support to auto_explain To: Robert Haas Cc: Matheus Alcantara , pgsql-hackers@lists.postgresql.org, Tom Lane Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On Thu, Apr 2, 2026 at 11:17=E2=80=AFAM Robert Haas = wrote: > > + an associated value. The module that provides the > > + EXPLAIN option, such as > > + pg_plan_advice= or > > + pg_overexplain, > > + should be loaded before this parameter is set. > > > > Wondering if this is clear enough about the shared_preload_libraries > > order (auto_explain should be loaded after extensions that include > > explain options) or if we should mention this explicitly. > > I actually thought that this might not be true until I tested it and > found that it sort of is. If you don't set > auto_explain.log_extension_options in postgresql.conf, then you can > load the modules in either order and it's fine. But if you do set it, > then you need to have the EXPLAIN option provider before auto_explain, > or else you get something like this: > > 2026-04-02 14:03:19.282 EDT [4614] WARNING: unrecognized EXPLAIN option = "debug" > > ...because we read the whole postgresql.conf file before applying any > of it, and so if auto_explain's _PG_init() runs first, the GUC value > is already visible but the EXPLAIN option doesn't exist yet. That's > annoying, but I'm not sure it's worth any more of a documentation > reference than what I have already. "Before this parameter is set" > could be read to encompass "put it earlier in > shared_preload_libraries," and if someone does it wrong, it will > become obvious quickly enough. If you (or someone else) doesn't agree, > feel free to propose better wording -- I just don't want to expand the > description so much that it becomes a distraction. Hmm. I think it would be typical to set "auto_explain.log_extension_options" in postgresql.conf, and this requirement of needing to put the libraries in the right order to avoid the warning doesn't seem great. As a general principle, I think we should aim to not have any ordering requirements in shared_preload_libraries. As one more data point, I know some cloud providers only expose "shared_preload_libraries" as a dropdown with options to check, i.e. you may not even have control over the order as an end user. And its unfortunate that "auto_explain" will often sort alphabetically before others.. Now the counter argument is that a WARNING is just that, i.e. it won't block the server from coming up. So maybe this is okay. I wonder if we should try to rework the code so that the GUC message when the option is not recognized is produced in auto_explain.c, and then add a GUC_check_errdetail that explains this may be due to the order of shared_preload_libraries. --- Now, onto the code: v2/0001 > +/* > + * scan_quoted_identifier - In-place scanner for quoted identifiers. > + * > + * *nextp should point to the opening double-quote character, and will b= e > + * updated to point just past the end. *endp is set to the position of > + * the closing quote. The return value is the identifier, or NULL if the > + * matching close-quote cannot be found. > + * Maybe make it clear in the comment that the return value is unterminated (e.g. "The return value is the unterminated identifier"), i.e. caller is responsible for setting the null byte based on endp. Otherwise 0001 looks good. v2/0002: > +bool > +GUCCheckExplainExtensionOption(const char *option_name, > + const char *option_value, > + NodeTag option_type) > +{ It feels a bit odd to not use an actual node here as the input argument (replacing option_value and option_type), or even pass a DefElem. But, if I followed your reasoning correctly, you're avoiding using DefElems here, because you don't want to keep nodes in auto_explain's GUC derived struct, to ensure we use guc_malloc for derived information. And presumably you're also modeling this particular method after GUCs in general, which don't have values represented as nodes. With that in mind, 0002 looks good as well. v2/0003: > +/* > + * GUC check hook for auto_explain.log_extension_options. > + */ > +static bool > +check_log_extension_options(char **newval, void **extra, GucSource sourc= e) > +{ > + char *rawstring; > + auto_explain_extension_options *result; > + auto_explain_option *options; > + int maxoptions =3D 8; > + Size rawstring_len; > + Size allocsize; > + char *errmsg; > + > + /* NULL or empty string means no options. */ > + if (*newval =3D=3D NULL || (*newval)[0] =3D=3D '\0') > + { > + *extra =3D NULL; Per src/backend/utils/misc/README, "extra is initialized to NULL before call, so it can be ignored if not needed." - so we don't have to set extra here. > +/* > + * Apply parsed extension options to an ExplainState. > + */ > +static void > +apply_extension_options(ExplainState *es, auto_explain_extension_options= *ext) > +{ > + if (ext =3D=3D NULL) > + return; > + > + for (int i =3D 0; i < ext->noptions; i++) > + { > + auto_explain_option *opt =3D &ext->options[i]; > + DefElem *def; > + Node *arg; > + > + if (opt->value =3D=3D NULL) > + arg =3D NULL; > + else if (opt->type =3D=3D T_Integer) > + arg =3D (Node *) makeInteger(strtol(opt->value, NULL, 0)); You note in a separate comment that we're using a subset of the main parser, but maybe its worth calling out integer values in particular, since things like "100_000" are not supported (since we're not using pg_strtoint64_safe + deal with the complexity of that in the scanning logic). I think this could be a code comment for now. Obviously we don't have a use case for that today, but thinking creatively, maybe someone wants to write an EXPLAIN extension that shows details for all nodes with cost values exceeding a large number, specified by an option. > +static int > +auto_explain_split_options(char *rawstring, auto_explain_option *options= , > + int maxoptions, char **errmsg) > +{ I think this function probably has the most complexity due to its hand-rolled parsing, so it might be good if someone else takes another close look at it. FWIW, I couldn't find anything wrong from a first read. Otherwise 0003 looks good to me. Thanks, Lukas --=20 Lukas Fittl