Thread: BUG #5358: Throwing unexpected ERROR

BUG #5358: Throwing unexpected ERROR

From
"Gurjeet Singh"
Date:
The following bug has been logged online:

Bug reference:      5358
Logged by:          Gurjeet Singh
Email address:      singh.gurjeet@gmail.com
PostgreSQL version: 8.4.2
Operating system:   Windows Vista 64bit
Description:        Throwing unexpected ERROR
Details:

I am using Postgres Plus Standard Server
version: PostgreSQL 8.4.2, compiled by Visual C++ build 1400, 32-bit

create table public.test( a bytea, c text )

select relname, pg_relation_size( 'public."' || relname || '"' )/1024
from (select distinct relname
    from (select r.relname, c.attname, c.attnotnull, t.typname
        from pg_namespace as s, pg_class as r, pg_attribute as c, pg_type as t
        where s.oid = r.relnamespace
        and r.oid = c.attrelid
        and c.atttypid = t.oid
        and s.nspname = 'public'
        and t.typname in ('bytea', 'text') ) as s1
    ) as s2
where pg_relation_size( 'public."' || relname || '"' ) <> 0;

ERROR:  relation "public.pg_type" does not exist

********** Error **********

ERROR: relation "public.pg_type" does not exist
SQL state: 42P01

When I comment out the last WHERE clause, the query runs fine.

It seems that the WHERE predicate is being pushed into the subqueries a bit
too soon.


Here's the EXPLAIN:
Subquery Scan s2  (cost=123.50..124.23 rows=21 width=64)
  ->  HashAggregate  (cost=123.50..123.71 rows=21 width=64)
        ->  Nested Loop  (cost=10.93..123.44 rows=21 width=64)
              Join Filter: (r.relnamespace = s.oid)
              ->  Seq Scan on pg_namespace s  (cost=0.00..1.08 rows=1
width=4)
                    Filter: (nspname = 'public'::name)
              ->  Nested Loop  (cost=10.93..121.03 rows=107 width=68)
                    ->  Hash Join  (cost=10.93..82.03 rows=107 width=4)
                          Hash Cond: (c.atttypid = t.oid)
                          ->  Seq Scan on pg_attribute c  (cost=0.00..61.57
rows=2257 width=8)
                          ->  Hash  (cost=10.90..10.90 rows=2 width=4)
                                ->  Seq Scan on pg_type t  (cost=0.00..10.90
rows=2 width=4)
                                      Filter: (typname = ANY
('{bytea,text}'::name[]))
                    ->  Index Scan using pg_class_oid_index on pg_class r
(cost=0.00..0.35 rows=1 width=72)
                          Index Cond: (r.oid = c.attrelid)
                          Filter: (pg_relation_size(((('public."'::text ||
(r.relname)::text) || '"'::text))::regclass, 'main'::text) <> 0)

Best regards,

Re: BUG #5358: Throwing unexpected ERROR

From
Tom Lane
Date:
"Gurjeet Singh" <singh.gurjeet@gmail.com> writes:
> select relname, pg_relation_size( 'public."' || relname || '"' )/1024
> from (select distinct relname
>     from (select r.relname, c.attname, c.attnotnull, t.typname
>         from pg_namespace as s, pg_class as r, pg_attribute as c, pg_type as t
>         where s.oid = r.relnamespace
>         and r.oid = c.attrelid
>         and c.atttypid = t.oid
>         and s.nspname = 'public'
>         and t.typname in ('bytea', 'text') ) as s1
>     ) as s2
> where pg_relation_size( 'public."' || relname || '"' ) <> 0;

> ERROR:  relation "public.pg_type" does not exist

That approach to generating a textual name for a relation is really
pretty unworkable: it's on the hairy edge of being vulnerable to
SQL injection attacks, not to mention being inefficient and unwieldy.
Just pass r.oid to pg_relation_size, instead.

            regards, tom lane

Re: BUG #5358: Throwing unexpected ERROR

From
Gurjeet Singh
Date:
On Tue, Mar 2, 2010 at 7:51 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

> "Gurjeet Singh" <singh.gurjeet@gmail.com> writes:
> > select relname, pg_relation_size( 'public."' || relname || '"' )/1024
> > from (select distinct relname
> >       from (select r.relname, c.attname, c.attnotnull, t.typname
> >               from pg_namespace as s, pg_class as r, pg_attribute as c,
> pg_type as t
> >               where s.oid = r.relnamespace
> >               and r.oid = c.attrelid
> >               and c.atttypid = t.oid
> >               and s.nspname = 'public'
> >               and t.typname in ('bytea', 'text') ) as s1
> >       ) as s2
> > where pg_relation_size( 'public."' || relname || '"' ) <> 0;
>
> > ERROR:  relation "public.pg_type" does not exist
>
> That approach to generating a textual name for a relation is really
> pretty unworkable: it's on the hairy edge of being vulnerable to
> SQL injection attacks, not to mention being inefficient and unwieldy.
> Just pass r.oid to pg_relation_size, instead.
>

I have gotten on to that path already, thanks for the advice.

This query will never be used by an application, so no fear of SQL injection
there. I was in the middle of a migration effort when I brewed this query.
The main inner query is what I started with to migrate only specific tables,
and the started slapping on outer queries to monitor the amount of data
already transferred. So I was rather surprised to see this error at a stage
where I did not expect it to fail.

IMHO the outer-most WHERE clause is being pushed through the subqueries when
it should not be. I tried to stop the optimizer from doing that and it seems
putting a LIMIT clause on S1 subquery make Postgres happy.

select relname, pg_relation_size( 'public."' || relname || '"' )/1024
from (select distinct relname
       from (select r.relname, c.attname, c.attnotnull, t.typname
               from pg_namespace as s, pg_class as r, pg_attribute as c,
pg_type as t
               where s.oid = r.relnamespace
               and r.oid = c.attrelid
               and c.atttypid = t.oid
               and s.nspname = 'public'
               and t.typname in ('bytea', 'text') ) as s1 limit 1000
       ) as s2
where pg_relation_size( 'public."' || relname || '"' ) <> 0 ;

From SQL perspective there should be no difference between this query and
the one in the first post since there's only one qualifying record.
Predicate push-down is definitely a good optimization, but it should not
affect the result-set. I have no idea how to tell optimizer to stop such
push-downs.

I am leaning towards marking this as a bug.

Best regards,
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.enterprisedb.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

Re: BUG #5358: Throwing unexpected ERROR

From
Gurjeet Singh
Date:
On Tue, Mar 2, 2010 at 10:24 PM, Gurjeet Singh <singh.gurjeet@gmail.com>wrote:

> On Tue, Mar 2, 2010 at 7:51 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
>> "Gurjeet Singh" <singh.gurjeet@gmail.com> writes:
>> > select relname, pg_relation_size( 'public."' || relname || '"' )/1024
>> > from (select distinct relname
>> >       from (select r.relname, c.attname, c.attnotnull, t.typname
>> >               from pg_namespace as s, pg_class as r, pg_attribute as c,
>> pg_type as t
>> >               where s.oid = r.relnamespace
>> >               and r.oid = c.attrelid
>> >               and c.atttypid = t.oid
>> >               and s.nspname = 'public'
>> >               and t.typname in ('bytea', 'text') ) as s1
>> >       ) as s2
>> > where pg_relation_size( 'public."' || relname || '"' ) <> 0;
>>
>> > ERROR:  relation "public.pg_type" does not exist
>>
>> That approach to generating a textual name for a relation is really
>> pretty unworkable: it's on the hairy edge of being vulnerable to
>> SQL injection attacks, not to mention being inefficient and unwieldy.
>> Just pass r.oid to pg_relation_size, instead.
>>
>
> I have gotten on to that path already, thanks for the advice.
>
> This query will never be used by an application, so no fear of SQL
> injection there. I was in the middle of a migration effort when I brewed
> this query. The main inner query is what I started with to migrate only
> specific tables, and the started slapping on outer queries to monitor the
> amount of data already transferred. So I was rather surprised to see this
> error at a stage where I did not expect it to fail.
>
> IMHO the outer-most WHERE clause is being pushed through the subqueries
> when it should not be. I tried to stop the optimizer from doing that and it
> seems putting a LIMIT clause on S1 subquery make Postgres happy.
>
>
> select relname, pg_relation_size( 'public."' || relname || '"' )/1024
> from (select distinct relname
>        from (select r.relname, c.attname, c.attnotnull, t.typname
>                from pg_namespace as s, pg_class as r, pg_attribute as c,
> pg_type as t
>                where s.oid = r.relnamespace
>                and r.oid = c.attrelid
>                and c.atttypid = t.oid
>                and s.nspname = 'public'
>                and t.typname in ('bytea', 'text') ) as s1 limit 1000
>
>        ) as s2
> where pg_relation_size( 'public."' || relname || '"' ) <> 0 ;
>
> From SQL perspective there should be no difference between this query and
> the one in the first post since there's only one qualifying record.
> Predicate push-down is definitely a good optimization, but it should not
> affect the result-set. I have no idea how to tell optimizer to stop such
> push-downs.
>

I just realized that it is the subquery pull-up that is leading to this
problem, not predicate push-down. Sleeping over it does really help I guess
:)

So instead of the LIMIT 1000, OFFSET 0 clause is the right choice for
preventing subquery pull-up without affecting the results.

I don't think the optimizer has the push-down capabiity; I may be wrong.


> I am leaning towards marking this as a bug.
>
>
Best regards,
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.enterprisedb.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

Re: BUG #5358: Throwing unexpected ERROR

From
Robert Haas
Date:
On Wed, Mar 3, 2010 at 7:29 AM, Gurjeet Singh <singh.gurjeet@gmail.com> wrote:
> I just realized that it is the subquery pull-up that is leading to this
> problem, not predicate push-down. Sleeping over it does really help I guess
> :)
>
> So instead of the LIMIT 1000, OFFSET 0 clause is the right choice for
> preventing subquery pull-up without affecting the results.
>
> I don't think the optimizer has the push-down capabiity; I may be wrong.

Maybe I'm just dense, but I don't understand what you're complaining
about here.  The SELECT DISTINCT already acts as an optimization
fence, so why would you need another one?  And what problem would you
expect it to solve?

...Robert

Re: BUG #5358: Throwing unexpected ERROR

From
Gurjeet Singh
Date:
On Wed, Mar 3, 2010 at 8:37 AM, Robert Haas <robertmhaas@gmail.com> wrote:

> On Wed, Mar 3, 2010 at 7:29 AM, Gurjeet Singh <singh.gurjeet@gmail.com>
> wrote:
> > I just realized that it is the subquery pull-up that is leading to this
> > problem, not predicate push-down. Sleeping over it does really help I
> guess
> > :)
> >
> > So instead of the LIMIT 1000, OFFSET 0 clause is the right choice for
> > preventing subquery pull-up without affecting the results.
> >
> > I don't think the optimizer has the push-down capabiity; I may be wrong.
>
> Maybe I'm just dense, but I don't understand what you're complaining
> about here.  The SELECT DISTINCT already acts as an optimization
> fence, so why would you need another one?  And what problem would you
> expect it to solve?
>
>
I am complaining about the ERROR when I don't specify OFFSET or LIMIT.

The query isn't relevant. It is there just to illustrate the fact that two
supposedly equivalent forms of a query are not treated equivalent after all
by Postgres.

You don't put that OFFSET clause, you get an ERROR. You put in that OFFSET
clause and you get proper results.

I hope my complain is clearer now.

Best regards,
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.enterprisedb.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

Re: BUG #5358: Throwing unexpected ERROR

From
Robert Haas
Date:
On Wed, Mar 3, 2010 at 8:48 AM, Gurjeet Singh <singh.gurjeet@gmail.com> wro=
te:
> On Wed, Mar 3, 2010 at 8:37 AM, Robert Haas <robertmhaas@gmail.com> wrote:
>>
>> On Wed, Mar 3, 2010 at 7:29 AM, Gurjeet Singh <singh.gurjeet@gmail.com>
>> wrote:
>> > I just realized that it is the subquery pull-up that is leading to this
>> > problem, not predicate push-down. Sleeping over it does really help I
>> > guess
>> > :)
>> >
>> > So instead of the LIMIT 1000, OFFSET 0 clause is the right choice for
>> > preventing subquery pull-up without affecting the results.
>> >
>> > I don't think the optimizer has the push-down capabiity; I may be wron=
g.
>>
>> Maybe I'm just dense, but I don't understand what you're complaining
>> about here. =A0The SELECT DISTINCT already acts as an optimization
>> fence, so why would you need another one? =A0And what problem would you
>> expect it to solve?
>>
>
> I am complaining about the ERROR when I don't specify OFFSET or LIMIT.
>
> The query isn't relevant. It is there just to illustrate the fact that two
> supposedly equivalent forms of a query are not treated equivalent after a=
ll
> by Postgres.
>
> You don't put that OFFSET clause, you get an ERROR. You put in that OFFSET
> clause and you get proper results.
>
> I hope my complain is clearer now.

It does seem a little weird, but I don't think we're likely to change
the behavior.  The optimizer is allowed to reorder quals, and I don't
think we want to change that.  Consider a very large table which has
an index on column b but not on column a, and the following query:

SELECT * FROM some_huge_table WHERE a =3D 1 AND b =3D 1

All other things being equal, we'll want to execute this query by
doing an index scan for rows with b =3D 1 and then checking whatever
comes back to see whether we also have a =3D 1.  If we insisted that a =3D
1 had to be evaluated first, we'd have to scan the whole table.

Normally this kind of reordering doesn't actually affect the result of
the query because normally the quals that are being evaluated don't
have any side-effects, but in your query you've chosen something that
can throw an exception, so it's user-visible.

...Robert

Re: BUG #5358: Throwing unexpected ERROR

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> Normally this kind of reordering doesn't actually affect the result of
> the query because normally the quals that are being evaluated don't
> have any side-effects, but in your query you've chosen something that
> can throw an exception, so it's user-visible.

There's a pretty explicit disclaimer here:
http://www.postgresql.org/docs/8.4/static/sql-expressions.html#SYNTAX-EXPRESS-EVAL
that evaluation order is not guaranteed for WHERE clauses.  While we
could try to tighten that up, I'll bet quite a lot that the howls of
pain from people whose queries stopped getting optimized would drown
out anyone who thought it was an improvement.

In this particular case I think you could make it better by putting the
unsafe function call in a subselect's output list, and placing the
restrictive condition in a WHERE clause above the subselect.  However,
that's only safe because we have restrictions forbidding folding of
subselects whose output lists include volatile functions.  The problem
here is *not* that pg_relation_size is volatile in the normal sense,
it's the possibility of throwing error on its input; so the fact that
the problem goes away seems a bit accidental.  I don't know quite what
we might do about that.  Marking functions as "could throw error"
doesn't seem especially helpful, because the set of functions not so
markable is durn close to empty.

            regards, tom lane