Thread: Chicken/egg problem with range types

Chicken/egg problem with range types

From
Scott Bailey
Date:
I'm trying to create a discrete range type and I'm having trouble with
the canonical function.

--Create shell type
CREATE TYPE dt_range;

--Create subtype diff
CREATE OR REPLACE FUNCTION dt_subtype_diff(timestamptz, timestamptz)
RETURNS float8 AS
$$
   SELECT EXTRACT(EPOCH FROM $1 - $2);
$$ LANGUAGE 'sql' IMMUTABLE STRICT;

-- Create the canonical function
CREATE OR REPLACE FUNCTION dt_range_canonical(dt_range)
RETURNS dt_range AS
$$
   SELECT dt_range(
     CASE WHEN lower_inc($1)
         THEN lower($1)::timestampTz(0)
         ELSE lower($1)::timestampTz(0) - INTERVAL '1s' END,
     CASE WHEN NOT upper_inc($1)
         THEN upper($1)::timestampTz(0)
         ELSE upper($1)::timestampTz(0) + INTERVAL '1s' END
   );
$$ LANGUAGE 'sql' IMMUTABLE STRICT;

Fails with ERROR: SQL function cannot accept shell type dt_range. So I
add the type and try to alter it later.

-- Create the type any way
CREATE TYPE dt_range AS RANGE (
   SUBTYPE = timestamptz,
   SUBTYPE_DIFF = dt_subtype_diff
   -- CANONICAL = dt_range_canonical -- can't use, fn doesn't exist
);

ALTER TYPE dt_range SET CANONICAL = dt_range_canonical;

This doesn't work either. I'm stuck.

Scott Bailey

Re: Chicken/egg problem with range types

From
Tom Lane
Date:
Scott Bailey <artacus72@gmail.com> writes:
> I'm trying to create a discrete range type and I'm having trouble with
> the canonical function.

Yeah, right now you really can't write canonical functions in anything
except C, for which we don't enforce the no-shell-types restriction.

Before range types, this wasn't a big deal since it was only an issue
for I/O functions, which pretty much have to be in C anyway.  But your
example shows that at least for prototyping, a SQL or PL function could
usefully be used as a canonical function.

I wonder whether we could improve this by postponing the no-shell-types
check from creation to function runtime.  It would be annoying to have
to make an additional catalog lookup at runtime just for typisdefined,
but I think that probably we could fold it in with an existing fetch of
the pg_type row during parsing of the calling query, so that no run-time
overhead is added.

This would limit what checking could be performed on the function body
at creation time, but surely no worse than, say, a reference to a
nonexistent table, which we allow.

Another thing that would have to be thought about is security: external
PLs would very possibly not get the word about needing to check for
shell arguments/results themselves, and we'd need to make certain that
nothing too awful could happen if they didn't.  That doesn't seem
insoluble but it would take some thought.  A different security angle is
making sure that nothing too awful happens if someone tries to use a SQL
or PL function as a type I/O function, which would now not be forbidden
by the no-shell-types restriction.

            regards, tom lane

Re: Chicken/egg problem with range types

From
Alban Hertroys
Date:
On 18 Jul 2012, at 5:08, Tom Lane wrote:

> Scott Bailey <artacus72@gmail.com> writes:
>> I'm trying to create a discrete range type and I'm having trouble with
>> the canonical function.

> I wonder whether we could improve this by postponing the no-shell-types
> check from creation to function runtime.  It would be annoying to have
> to make an additional catalog lookup at runtime just for typisdefined,
> but I think that probably we could fold it in with an existing fetch of
> the pg_type row during parsing of the calling query, so that no run-time
> overhead is added.


I don't suppose it'd be possible to treat it as a deferred constraint? Then the check would be moved to the end of the
transaction.

Alban Hertroys

--
Screwing up is an excellent way to attach something to the ceiling.


Re: Chicken/egg problem with range types

From
Tom Lane
Date:
Alban Hertroys <haramrae@gmail.com> writes:
> On 18 Jul 2012, at 5:08, Tom Lane wrote:
>> I wonder whether we could improve this by postponing the no-shell-types
>> check from creation to function runtime.

> I don't suppose it'd be possible to treat it as a deferred constraint? Then the check would be moved to the end of
thetransaction. 

You mean, after we've already crashed, or allowed a security breach to
happen?  Doesn't sound very helpful.  In any case, my concern is that
there not be any added overhead, not about moving it around.

            regards, tom lane

Re: Chicken/egg problem with range types

From
Scott Bailey
Date:
On 07/17/2012 08:08 PM, Tom Lane wrote:
> I wonder whether we could improve this by postponing the no-shell-types
> check from creation to function runtime.  It would be annoying to have
> to make an additional catalog lookup at runtime just for typisdefined,
> but I think that probably we could fold it in with an existing fetch of
> the pg_type row during parsing of the calling query, so that no run-time
> overhead is added.
>
> This would limit what checking could be performed on the function body
> at creation time, but surely no worse than, say, a reference to a
> nonexistent table, which we allow.


How about using ALTER TYPE to set it after both the function and the
type have been created?

Scott

Re: Chicken/egg problem with range types

From
Alban Hertroys
Date:
On 18 Jul 2012, at 16:15, Tom Lane wrote:

> Alban Hertroys <haramrae@gmail.com> writes:
>> On 18 Jul 2012, at 5:08, Tom Lane wrote:
>>> I wonder whether we could improve this by postponing the no-shell-types
>>> check from creation to function runtime.
>
>> I don't suppose it'd be possible to treat it as a deferred constraint? Then the check would be moved to the end of
thetransaction. 
>
> You mean, after we've already crashed, or allowed a security breach to
> happen?  Doesn't sound very helpful.  In any case, my concern is that
> there not be any added overhead, not about moving it around.


I did mean the transaction in which the dependency got created, not some later transaction in which it gets used. I'm
fairlysure that'd be before any crashes or security breaches. Or is that not what you're implying? 

Basically:
BEGIN;
CREATE TYPE dt_range AS (..., CANONICAL = dt_range_canonical); -- check for CANONICAL function deferred
CREATE FUNCTION dt_range_canonical(dt_range) ...; -- now it exists
COMMIT; -- here the check for the CANONICAL function is performed

I did make an assumption in there that the dependency doesn't get used before the creating transaction committed. Using
thedependency in the same transaction that created it should probably not be allowed, or the check should be moved to
functionruntime if that happens (like in your original suggestion). 


Another alternative, which would probably require a major effort to implement, would be to make CREATE FUNCTION
inlinable(is that the right word?) by making the DDL statement return the function identifier of the function it just
created.

With that, the dependency of the OP would be created somewhat like:

CREATE TYPE dt_range AS (..., CANONICAL = (CREATE FUNCTION dt_range_canonical(dt_range) ...));

Some languages use similar constructs, for example to assign methods to object prototypes in the case of Javascript. I
know,SQL is not object oriented (but neither is Javascript, strictly speaking). 

Alban Hertroys

--
If you can't see the forest for the trees,
cut the trees and you'll find there is no forest.