Thread: SPI/backend equivalent of extended-query Describe(statement)?

SPI/backend equivalent of extended-query Describe(statement)?

From
Chapman Flack
Date:
The longer version of $subject is: how would one go about, in the
backend using SPI (or SPI and maybe other server APIs as needed),
obtaining the same inferred parameter information that a front-end
client can get with the Describe (statement variant) extended-query
message?

I ask because I've stumbled on this 14½ year old (earliest days!) comment
in PL/Java's PreparedStatement.getParameterMetaData():

* Due to the design of the <code>SPI_prepare</code>, it is currently
* impossible to obtain the correct parameter meta data before all the
* parameters have been set, hence a ParameterMetaData obtained prior to
* setting the paramteres will have all parameters set to the default type
* {@link Types#VARCHAR}.
* Once the parameters have been set, a fair attempt is made to generate this
* object based on the supplied values.

Apparently it seemed difficult at the time to implement a
getParameterMetaData that could tell you the thing you want to know
(what parameter types are expected so you can supply them), but less
difficult to implement something that can't tell you until you've
supplied them and then tells you what you supplied. Problem solved,
in a way that's entertaining but a little short on utility. :)

I'll admit that in my own perusal of the SPI docs, I don't see an
obvious advertised way to get that information about a prepared
statement, either. But the information has to exist somewhere, and
a remote client is able to ask for it, so where should I be looking
for the means of retrieving it in the backend?

-Chap


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Tom Lane
Date:
Chapman Flack <chap@anastigmatix.net> writes:
> The longer version of $subject is: how would one go about, in the
> backend using SPI (or SPI and maybe other server APIs as needed),
> obtaining the same inferred parameter information that a front-end
> client can get with the Describe (statement variant) extended-query
> message?

If you're talking about the plan's input parameters, don't SPI_getargcount
and SPI_getargtypeid do what you need?

If you want to know about the output column types, SPI doesn't seem to
have a nice way of getting that in advance of execution, but you could
emulate what Describe does:

* Use SPI_plan_get_plan_sources to get a list of CachedPlanSources
(up to you to decide what to do if there's more or less than one CPS)

* For each/selected CPS, look at the resultDesc and/or targetlist
similarly to SendRowDescriptionMessage.

            regards, tom lane


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Pavel Stehule
Date:


2018-05-14 17:29 GMT+02:00 Tom Lane <tgl@sss.pgh.pa.us>:
Chapman Flack <chap@anastigmatix.net> writes:
> The longer version of $subject is: how would one go about, in the
> backend using SPI (or SPI and maybe other server APIs as needed),
> obtaining the same inferred parameter information that a front-end
> client can get with the Describe (statement variant) extended-query
> message?

If you're talking about the plan's input parameters, don't SPI_getargcount
and SPI_getargtypeid do what you need?

If you want to know about the output column types, SPI doesn't seem to
have a nice way of getting that in advance of execution, but you could
emulate what Describe does:

* Use SPI_plan_get_plan_sources to get a list of CachedPlanSources
(up to you to decide what to do if there's more or less than one CPS)

* For each/selected CPS, look at the resultDesc and/or targetlist
similarly to SendRowDescriptionMessage.

plpgsql_check does this work more times


Regards

Pavel

                        regards, tom lane


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Chapman Flack
Date:
On 05/14/18 11:29, Tom Lane wrote:
> Chapman Flack <chap@anastigmatix.net> writes:
>> The longer version of $subject is: how would one go about, in the
>> backend using SPI (or SPI and maybe other server APIs as needed),
>> obtaining the same inferred parameter information that a front-end
>> client can get with the Describe (statement variant) extended-query
>> message?
> 
> If you're talking about the plan's input parameters, don't SPI_getargcount
> and SPI_getargtypeid do what you need?

Huh ... let me go read those again. Sure sound promising. Right there on
the SPI contents page, too.  I wonder if I was so suggestible that I read
that comment and it blinded me to them, or I saw them and assumed they
didn't do what you'd expect, because of the comment....

The story gets better. Those two functions were added to SPI in March '04,
in 65a0db1, submitted by Thomas Hallgren himself.

Before being pushed in core, they had existed in PL/Java since January '04,
d917e46, eight days after he wrote that comment. They disappeared in
October '05 when he dropped support for PG < 8. And he had reworked
ExecutionPlan.c to make use of them (that is, after the Java code has
already bound Java values to the parameters, the native code does consult
SPI_getargtypeid while converting those to PG datums).

But he never removed that comment about getParameterMetaData, and I think
it's still correct (except for the blame-SPI part), because it looks like
he never got around to updating /that/ code to pass along the
SPI_getargtypeid info mapped to Java types so the Java caller can ask
what types to supply.

That looks like something I could fix....

Where should I look to learn more about the capabilities of the type
inference done in planning? Not everything-there-is-to-know, but basics
like, can some simple query constructs reliably cause an intended type to
be reported?  I assume if the query is "SELECT ?" then the reported argtype
will be unknown. If changed to, say, "SELECT ?::foo", will that report
a type of foo for the parameter, or continue to report the parameter
as untyped because it isn't needed to type the cast expression?
(In XQuery they made up distinct constructs "cast as" and "treat as"
to leave no such conundrum.)

-Chap


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Tom Lane
Date:
Chapman Flack <chap@anastigmatix.net> writes:
> Where should I look to learn more about the capabilities of the type
> inference done in planning?

It's not terribly well documented outside the source code, I fear.

> Not everything-there-is-to-know, but basics
> like, can some simple query constructs reliably cause an intended type to
> be reported?  I assume if the query is "SELECT ?" then the reported argtype
> will be unknown. If changed to, say, "SELECT ?::foo", will that report
> a type of foo for the parameter, or continue to report the parameter
> as untyped because it isn't needed to type the cast expression?

That will result in reporting the parameter as having type foo, cf
variable_coerce_param_hook().  Experimenting with this sort of stuff
isn't hard, eg in psql:

regression=# prepare p1 as select $1;
PREPARE
regression=# prepare p2 as select $1::bigint;
PREPARE
regression=# select name, parameter_types from pg_prepared_statements ;
 name | parameter_types 
------+-----------------
 p1   | {text}
 p2   | {bigint}
(2 rows)

(Before v10, p1 would have failed PREPARE for lack of a determinate
type for the parameter, which I think corresponds to sending back
UNKNOWN in the wire-protocol-prepare case.)

            regards, tom lane


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Chapman Flack
Date:
On 05/14/18 13:46, Chapman Flack wrote:
> On 05/14/18 11:29, Tom Lane wrote:
>> Chapman Flack <chap@anastigmatix.net> writes:
>>> The longer version of $subject is: how would one go about, in the
>>> backend using SPI (or SPI and maybe other server APIs as needed),
>>> obtaining the same inferred parameter information that a front-end
>>> client can get with the Describe (statement variant) extended-query
>>> message?
>>
>> If you're talking about the plan's input parameters, don't SPI_getargcount
>> and SPI_getargtypeid do what you need?
> 
> Huh ... let me go read those again. Sure sound promising. Right there on

I'm not sure I'm out of the woods yet. The extended-query-protocol
documentation clearly advertises that you can send param oids of zero,
or send fewer of them than the actual number of params, and get the
unspecified ones inferred. And exec_parse_message() pointedly calls
parse_analyze_varparams(), with a comment saying "Note that the originally
specified parameter set is not required to be complete, so we have to use
parse_analyze_varparams()."

The SPI_prepare docs don't contain any similar advertisement of what to do
if you want parameter types inferred ... and the code doesn't appear to
call parse_analyze_varparams, at least never before PG 9.0.

In 9.0, there's SPI_prepare_params, which seems promising; it accepts
an arbitrary ParserSetupHook "to control the parsing of external parameter
references." But its documentation doesn't suggest what to use as the
ParserSetupHook to say "please just do the same stuff you would do if
I were a client sending a Parse message with unspecified parameter types!"

Perhaps I just need something like

struct varparinfo { Oid *paramTypes, int numParams } vpi = {palloc(0), 0};

static void inferringSetupHook(struct ParseState *pstate, void *arg)
{
    struct varparinfo *vpi = (struct varparinfo *)arg;
    parse_variable_parameters(pstate, &vpi->paramTypes, &vpi->numParams);
}

SPI_prepare_params("SELECT $1", inferringSetupHook, &vpi, 0);

?

So, does this mean this really couldn't have been done in SPI earlier
than 9.0, and that old pre-9.0 comment from Thomas was completely accurate?

Were there no other PLs needing to do that before? Is SQL/JRT the only
spec for a PL that presupposes a prepare-describe-bind-execute model
where 'describe' tells you the inferred parameter types? I would have
assumed that was more common.

-Chap


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Chapman Flack
Date:
On 05/24/2018 02:30 AM, Chapman Flack wrote:
> In 9.0, there's SPI_prepare_params, which seems promising; it accepts
> an arbitrary ParserSetupHook "to control the parsing of external parameter
> references." But its documentation doesn't suggest what to use as the
> ParserSetupHook to say "please just do the same stuff you would do if
> I were a client sending a Parse message with unspecified parameter types!"
> 
> Perhaps I just need something like
> 
> struct varparinfo { Oid *paramTypes, int numParams } vpi = {palloc(0), 0};
> 
> static void inferringSetupHook(struct ParseState *pstate, void *arg)
> {
>     struct varparinfo *vpi = (struct varparinfo *)arg;
>     parse_variable_parameters(pstate, &vpi->paramTypes, &vpi->numParams);
> }
> 
> SPI_prepare_params("SELECT $1", inferringSetupHook, &vpi, 0);

Am I on the right track here? Is what I'm looking to do something
that became possible in SPI in 9.0 and wasn't before, or did I overlook
a way it could have been done pre-9.0 ?

Thanks,
-Chap


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Tom Lane
Date:
Chapman Flack <chap@anastigmatix.net> writes:
> Am I on the right track here? Is what I'm looking to do something
> that became possible in SPI in 9.0 and wasn't before, or did I overlook
> a way it could have been done pre-9.0 ?

Offhand I don't believe SPI exposed a way to do that before 9bedd128d.
Does it matter?  Pre-9.0 releases are long out of support by now,
which means they're full of known data-loss hazards and security bugs.

            regards, tom lane


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Chapman Flack
Date:
On 05/25/2018 06:01 PM, Tom Lane wrote:

> Offhand I don't believe SPI exposed a way to do that before 9bedd128d.
> Does it matter?  Pre-9.0 releases are long out of support by now,
> which means they're full of known data-loss hazards and security bugs.

Well, my exploration arose from discovering something in PL/Java's
implementation that surprised me, but it dated from pre-9.0 times,
so seems to have been doing the best that was possible then. Didn't
want to start changing it before understanding why it was that way.

As for bringing it along to the modern API, am I on the right track
with writing a ParserSetupHook that's just a thin wrapper around
parse_variable_parameters ?

-Chap


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Tom Lane
Date:
Chapman Flack <chap@anastigmatix.net> writes:
> As for bringing it along to the modern API, am I on the right track
> with writing a ParserSetupHook that's just a thin wrapper around
> parse_variable_parameters ?

Seems reasonable from here.

            regards, tom lane


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Andrew Gierth
Date:
>>>>> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:

 > Chapman Flack <chap@anastigmatix.net> writes:
 >> As for bringing it along to the modern API, am I on the right track
 >> with writing a ParserSetupHook that's just a thin wrapper around
 >> parse_variable_parameters ?

 Tom> Seems reasonable from here.

That's what I did in pllua-ng. The tricky bit was in arranging to also
call check_variable_parameters; I considered skipping that part, but
that seemed like it could be potentially problematic.

https://github.com/pllua/pllua-ng/blob/master/src/spi.c#L266

-- 
Andrew (irc:RhodiumToad)


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Chapman Flack
Date:
On 05/25/18 20:07, Andrew Gierth wrote:
>  >> with writing a ParserSetupHook that's just a thin wrapper around
>  >> parse_variable_parameters ?
> 
> That's what I did in pllua-ng. The tricky bit was in arranging to also
> call check_variable_parameters; I considered skipping that part, but
> that seemed like it could be potentially problematic.
> 
> https://github.com/pllua/pllua-ng/blob/master/src/spi.c#L266
>
>/*
> * GAH. To do parameter type checking properly, we have to install our
> * own global post-parse hook transiently.
> */
>...
>PG_TRY();
>{
>    pllua_spi_prev_parse_hook = post_parse_analyze_hook;
>    post_parse_analyze_hook = pllua_spi_prepare_checkparam_hook;
>...
>PG_CATCH();
>{
>    post_parse_analyze_hook = pllua_spi_prev_parse_hook;
>    --pllua_spi_prepare_recursion;
>    PG_RE_THROW();
>...

Gah, indeed. Thanks for the heads up. I would never have guessed it'd
be that fiddly.

-Chap


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Tom Lane
Date:
Chapman Flack <chap@anastigmatix.net> writes:
> On 05/25/18 20:07, Andrew Gierth wrote:
>> /*
>> * GAH. To do parameter type checking properly, we have to install our
>> * own global post-parse hook transiently.
>> */
>> ...
>> PG_TRY();
>> {
>> pllua_spi_prev_parse_hook = post_parse_analyze_hook;
>> post_parse_analyze_hook = pllua_spi_prepare_checkparam_hook;
>> ...
>> PG_CATCH();
>> {
>> post_parse_analyze_hook = pllua_spi_prev_parse_hook;
>> --pllua_spi_prepare_recursion;
>> PG_RE_THROW();
>> ...

> Gah, indeed. Thanks for the heads up. I would never have guessed it'd
> be that fiddly.

Yikes.  That seems pretty unsafe :-(

Obviously, I missed a bet by not folding check_variable_parameters
into the pstate hook mechanism.  It's a bit late to do anything
about that for v11, but I'd favor trying to improve the situation
in v12.

            regards, tom lane


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Andrew Gierth
Date:
>>>>> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:

 >>> /*
 >>> * GAH. To do parameter type checking properly, we have to install our
 >>> * own global post-parse hook transiently.
 >>> */

 >> Gah, indeed. Thanks for the heads up. I would never have guessed it'd
 >> be that fiddly.

 Tom> Yikes.  That seems pretty unsafe :-(

I put in a recursion check out of paranoia, but even after considerable
thought wasn't able to figure out any scenario that would actually break
it. If it's actually unsafe I'd really like to know about it :-)

 Tom> Obviously, I missed a bet by not folding check_variable_parameters
 Tom> into the pstate hook mechanism. It's a bit late to do anything
 Tom> about that for v11, but I'd favor trying to improve the situation
 Tom> in v12.

Yeah. Another issue I ran into is that if you use SPI_prepare_params,
then you have to use SPI_execute_plan_with_paramlist, it's not possible
to use SPI_execute_plan (or SPI_execute_snapshot) instead because
plan->nargs was set to 0 by the prepare and never filled in with the
actual parameter count.

-- 
Andrew (irc:RhodiumToad)


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Chapman Flack
Date:
On 05/25/18 21:16, Andrew Gierth wrote:
>>>>>> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
>  Tom> about that for v11, but I'd favor trying to improve the situation
>  Tom> in v12.
> 
> Yeah. Another issue I ran into is that if you use SPI_prepare_params,
> then you have to use SPI_execute_plan_with_paramlist, it's not possible
> to use SPI_execute_plan (or SPI_execute_snapshot) instead because
> plan->nargs was set to 0 by the prepare and never filled in with the
> actual parameter count.

Now *that* sounds arguably bug-like ...could *it*, at least, be a candidate
for back-patching?

If it's currently not possible to use SPI_execute_plan/SPI_execute_snapshot
after SPI_prepare_params, then there can't be anything currently doing so,
that a patch could conceivably disrupt, can there?

-Chap


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Tom Lane
Date:
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
>  Tom> Yikes.  That seems pretty unsafe :-(

> I put in a recursion check out of paranoia, but even after considerable
> thought wasn't able to figure out any scenario that would actually break
> it. If it's actually unsafe I'd really like to know about it :-)

The worrisome thing is the possibility that some other extension tries
to add to (or otherwise change) the post_parse_analyze_hook list while
you have it in a temporary state.  I can't think of a really likely
scenario for that, because I don't think parse analysis would ever cause
loading of a shared library that wasn't loaded already ... but just to
state that assumption is to expose how non-future-proof it is.

Really our hook mechanism only supports adding hooks, not removing them.

>  Tom> Obviously, I missed a bet by not folding check_variable_parameters
>  Tom> into the pstate hook mechanism. It's a bit late to do anything
>  Tom> about that for v11, but I'd favor trying to improve the situation
>  Tom> in v12.

> Yeah. Another issue I ran into is that if you use SPI_prepare_params,
> then you have to use SPI_execute_plan_with_paramlist, it's not possible
> to use SPI_execute_plan (or SPI_execute_snapshot) instead because
> plan->nargs was set to 0 by the prepare and never filled in with the
> actual parameter count.

I'm not following why that's such a problem?  The whole point of
SPI_prepare_params and friends is that the actual number and types
of the parameters is hidden behind the parse hooks and ParamListInfo
--- and, indeed, could change from one execution to the next.
SPI_execute_plan only makes sense with a predetermined, fixed
parameter list.

            regards, tom lane


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Chapman Flack
Date:
On 05/26/18 10:03, Tom Lane wrote:
> Really our hook mechanism only supports adding hooks, not removing them.

I suppose the pllua_spi_prepare_checkparam_hook could be linked in once
and for all, and turned on and off just where the code now hooks and
unhooks it, and just forward to the next hook when it's off.

>> Yeah. Another issue I ran into is that if you use SPI_prepare_params,
>> then you have to use SPI_execute_plan_with_paramlist, it's not possible
>> to use SPI_execute_plan (or SPI_execute_snapshot) instead because
>> plan->nargs was set to 0 by the prepare and never filled in with the
>> actual parameter count.
> 
> I'm not following why that's such a problem?  The whole point of
> SPI_prepare_params and friends is that the actual number and types
> of the parameters is hidden behind the parse hooks and ParamListInfo
> --- and, indeed, could change from one execution to the next.
> SPI_execute_plan only makes sense with a predetermined, fixed
> parameter list.

Well, when you're implementing a PL, you're faced with this task of
mapping/coercing parameters from a PL type system that invariably
(invariably? Yes, I think, unless your PL's name has "sql" in it)
differs from SQL's, and that's a pretty ill-defined task if your PL
runtime is handed a query to prepare in the form of a string, and
then handed some parameter values in the PL's type system, and can't
find out what SQL types they could appropriately be mapped/coerced to
for the query to be valid.

In the JDBC API, you pass a query string to prepareStatement(), and
then on what you get back you can call getParameterMetaData() and
learn what SQL thinks the types of the parameters will have to be.
That isn't really expected to change; the requirement isn't necessarily
to support some dizzying all-dynamic-all-the-time usage pattern, it's
just to be able to get the information, and SPI_prepare_params seems
the only way to get it.

-Chap


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Andrew Gierth
Date:
>>>>> "Chapman" == Chapman Flack <chap@anastigmatix.net> writes:

 >> Really our hook mechanism only supports adding hooks, not removing
 >> them.

 Chapman> I suppose the pllua_spi_prepare_checkparam_hook could be
 Chapman> linked in once and for all, and turned on and off just where
 Chapman> the code now hooks and unhooks it, and just forward to the
 Chapman> next hook when it's off.

Yeah, or have it detect whether the ParseState it's being called for is
ours by some other means.

 >> I'm not following why that's such a problem?  The whole point of
 >> SPI_prepare_params and friends is that the actual number and types
 >> of the parameters is hidden behind the parse hooks and ParamListInfo
 >> --- and, indeed, could change from one execution to the next.

So while looking at the hook issue, I found another can of worms.

What a protocol-level Parse does is to call parse-analysis via
parse_analyze_varparams, which calls parse_variable_parameters _without_
making it a parser setup hook (either there or in CompleteCachedPlan).

This has the effect of casting the parameter types in stone on the first
parse, as the client expects; a subsequent revalidate of the statement
will use pg_analyze_and_rewrite, which takes a fixed parameter list.

However if you call parse_variable_parameters from a hook passed to
SPI_prepare_params, then you're asking for it to be called again on
revalidations, which means that the parameters might change (even if
just changing types, I think you'd need a more complex set of hooks than
parse_variable_parameters uses to change the number of parameters too).

So what I'm thinking now is that the way to go, if one wants to imitate
the client-side protocol behavior closely, would be to have a setup hook
that calls parse_variable_parameters the first time, and then
parse_fixed_parameters on subsequent calls for revalidation.

-- 
Andrew (irc:RhodiumToad)


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Chapman Flack
Date:
On 05/26/18 15:22, Andrew Gierth wrote:
> So what I'm thinking now is that the way to go, if one wants to imitate
> the client-side protocol behavior closely, would be to have a setup hook
> that calls parse_variable_parameters the first time, and then
> parse_fixed_parameters on subsequent calls for revalidation.

What circumstances would call for revalidation, and what would it be
able to accomplish, under that design?

I'm kind of trying to think out what the semantics could be in PL/Java,
should they be changed from what they are now (which is just to tell you
all params are varchar until you have supplied values for them, and then
tell you what types you supplied, in case you'd forgotten).

It's interesting that with parse_variable_parameters, it is possible
to supply some types explicitly, while leaving others to be inferred.
In one plausible implementation, that could be what would happen in
PL/Java if you called prepareStatement(), then called setter methods
on a few of the parameters, then called getParameterMetaData(). And
I suppose there could be cases where the explicitly supplied types for
some parameters would affect the types that get inferred for others.

On one hand, that sounds like it could be a bit confusing, and on the
other, sounds like it might be useful sometimes and I should just embrace
my inner Schrödinger and say yeah, that's how it works, you 'collapse'
the type assignments to whatever fits best at whatever moment you call
getParameterMetaData().

From a glance at the (network client) pgjdbc code, that seems to be
what you'd expect to happen there, too; it gets sent to the backend
for "describe only" at the moment getParameterMetaData is called.

What I can find of the JDBC spec seems informal and breezy about this
stuff to an unsettling degree....

-Chap


Re: SPI/backend equivalent of extended-query Describe(statement)?

From
Andrew Gierth
Date:
>>>>> "Chapman" == Chapman Flack <chap@anastigmatix.net> writes:

 >> So what I'm thinking now is that the way to go, if one wants to
 >> imitate the client-side protocol behavior closely, would be to have
 >> a setup hook that calls parse_variable_parameters the first time,
 >> and then parse_fixed_parameters on subsequent calls for
 >> revalidation.

 Chapman> What circumstances would call for revalidation, and what would
 Chapman> it be able to accomplish, under that design?

Any change that could invalidate the cached plan, such as altering any
of the tables referenced by it.

But thinking about it more (and actually trying it out in pllua-ng),
it's not as bad as I thought because parse_variable_parameters on
revalidation (at least the way I do it) will still be using the same
parameter types array as before, which will have the types of all
actually used parameters already filled in, and so those won't be
changed.

I think in the absence of a columnref hook, that means that the number
and type of parameters found when using parse_variable_parameters can't
change after the first time, so there's probably no need to get too
fancy in the hook.

-- 
Andrew (irc:RhodiumToad)