Обсуждение: subselects

Поиск
Список
Период
Сортировка

subselects

От
Bruce Momjian
Дата:
Vadim, I know you are still thinking about subselects, but I have some
more clarification that may help.

We have to add phantom range table entries to correlated subselects so
they will pass the parser.  We might as well add those fields to the
target list of the subquery at the same time:

    select *
    from taba
    where col1 = (select col2
              from tabb
              where taba.col3 = tabb.col4)

becomes:

    select *
    from taba
    where col1 = (select col2, tabb.col4 <---
              from tabb, taba  <---
              where taba.col3 = tabb.col4)

We add a field to TargetEntry and RangeTblEntry to mark the fact that it
was entered as a correlation entry:

    bool    isCorrelated;

Second, we need to hook the subselect to the main query.  I recommend we
add two fields to Query for this:

    Query *parentQuery;
    List *subqueries;

The parentQuery pointer is used to resolve field names in the correlated
subquery.

    select *
    from taba
    where col1 = (select col2, tabb.col4 <---
              from tabb, taba  <---
              where taba.col3 = tabb.col4)

In the query above, the subquery can be easily parsed, and we add the
subquery to the parsent's parentQuery list.

In the parent query, to parse the WHERE clause, we create a new operator
type, called IN or NOT_IN, or ALL, where the left side is a Var, and the
right side is an index to a slot in the subqueries List.

We can then do the rest in the upper optimizer.

--
Bruce Momjian
maillist@candle.pha.pa.us

Re: subselects

От
"Vadim B. Mikheev"
Дата:
Bruce Momjian wrote:
>
> Vadim, I know you are still thinking about subselects, but I have some
> more clarification that may help.
>
> We have to add phantom range table entries to correlated subselects so
> they will pass the parser.  We might as well add those fields to the
> target list of the subquery at the same time:
>
>         select *
>         from taba
>         where col1 = (select col2
>                       from tabb
>                       where taba.col3 = tabb.col4)
>
> becomes:
>
>         select *
>         from taba
>         where col1 = (select col2, tabb.col4 <---
>                       from tabb, taba  <---
>                       where taba.col3 = tabb.col4)
>
> We add a field to TargetEntry and RangeTblEntry to mark the fact that it
> was entered as a correlation entry:
>
>         bool    isCorrelated;

No, I don't like to add anything in parser. Example:

        select *
        from tabA
        where col1 = (select col2
                      from tabB
                      where tabA.col3 = tabB.col4
                      and exists (select *
                                  from tabC
                                  where tabB.colX = tabC.colX and
                                        tabC.colY = tabA.col2)
                     )

: a column of tabA is referenced in sub-subselect
(is it allowable by standards ?) - in this case it's better
to don't add tabA to 1st subselect but add tabA to second one
and change tabA.col3 in 1st to reference col3 in 2nd subquery temp table -
this gives us 2-tables join in 1st subquery instead of 3-tables join.
(And I'm still not sure that using temp tables is best of what can be
done in all cases...)

Instead of using isCorrelated in TE & RTE we can add

Index varlevel;

to Var node to reflect (sub)query from where this Var is come
(where is range table to find var's relation using varno). Upmost query
will have varlevel = 0, all its (dirrect) children - varlevel = 1 and so on.
                        ^^^                          ^^^^^^^^^^^^
(I don't see problems with distinguishing Vars of different children
on the same level...)

>
> Second, we need to hook the subselect to the main query.  I recommend we
> add two fields to Query for this:
>
>         Query *parentQuery;
>         List *subqueries;

Agreed. And maybe Index queryLevel.

> In the parent query, to parse the WHERE clause, we create a new operator
> type, called IN or NOT_IN, or ALL, where the left side is a Var, and the
                                               ^^^^^^^^^^^^^^^^^^
No. We have to handle (a,b,c) OP (select x, y, z ...) and
'_a_constant_' OP (select ...) - I don't know is last in standards,
Sybase has this.

Well,

typedef enum OpType
{
    OP_EXPR, FUNC_EXPR, OR_EXPR, AND_EXPR, NOT_EXPR

+ OP_EXISTS, OP_ALL, OP_ANY

} OpType;

typedef struct Expr
{
    NodeTag     type;
    Oid         typeOid;        /* oid of the type of this expr */
    OpType      opType;         /* type of the op */
    Node       *oper;           /* could be Oper or Func */
    List       *args;           /* list of argument nodes */
} Expr;

OP_EXISTS: oper is NULL, lfirst(args) is SubSelect (index in subqueries
           List, following your suggestion)

OP_ALL, OP_ANY:

oper is List of Oper nodes. We need in list because of data types of
a, b, c (above) can be different and so Oper nodes will be different too.

lfirst(args) is List of expression nodes (Const, Var, Func ?, a + b ?) -
left side of subquery' operator.
lsecond(args) is SubSelect.

Note, that there are no OP_IN, OP_NOTIN in OpType-s for Expr. We need in
IN, NOTIN in A_Expr (parser node), but both of them have to be transferred
by parser into corresponding ANY and ALL. At the moment we can do:

IN --> = ANY, NOT IN --> <> ALL

but this will be "known bug": this breaks OO-nature of Postgres, because of
operators can be overrided and '=' can mean  s o m e t h i n g (not equality).
Example: box data type. For boxes, = means equality of _areas_ and =~
means that boxes are the same ==> =~ ANY should be used for IN.

> right side is an index to a slot in the subqueries List.

Vadim

Re: subselects

От
Bruce Momjian
Дата:
>
> Bruce Momjian wrote:
> >
> > Vadim, I know you are still thinking about subselects, but I have some
> > more clarification that may help.
> >
> > We have to add phantom range table entries to correlated subselects so
> > they will pass the parser.  We might as well add those fields to the
> > target list of the subquery at the same time:
> >
> >         select *
> >         from taba
> >         where col1 = (select col2
> >                       from tabb
> >                       where taba.col3 = tabb.col4)
> >
> > becomes:
> >
> >         select *
> >         from taba
> >         where col1 = (select col2, tabb.col4 <---
> >                       from tabb, taba  <---
> >                       where taba.col3 = tabb.col4)
> >
> > We add a field to TargetEntry and RangeTblEntry to mark the fact that it
> > was entered as a correlation entry:
> >
> >         bool    isCorrelated;
>
> No, I don't like to add anything in parser. Example:
>
>         select *
>         from tabA
>         where col1 = (select col2
>                       from tabB
>                       where tabA.col3 = tabB.col4
>                       and exists (select *
>                                   from tabC
>                                   where tabB.colX = tabC.colX and
>                                         tabC.colY = tabA.col2)
>                      )
>
> : a column of tabA is referenced in sub-subselect

This is a strange case that I don't think we need to handle in our first
implementation.

> (is it allowable by standards ?) - in this case it's better
> to don't add tabA to 1st subselect but add tabA to second one
> and change tabA.col3 in 1st to reference col3 in 2nd subquery temp table -
> this gives us 2-tables join in 1st subquery instead of 3-tables join.
> (And I'm still not sure that using temp tables is best of what can be
> done in all cases...)

I don't see any use for temp tables in subselects anymore.  After having
implemented UNIONS, I now see how much can be done in the upper
optimizer.  I see you just putting the subquery PLAN into the proper
place in the plan tree, with some proper JOIN nodes for IN, NOT IN.

>
> Instead of using isCorrelated in TE & RTE we can add
>
> Index varlevel;

OK.  Sounds good.

>
> to Var node to reflect (sub)query from where this Var is come
> (where is range table to find var's relation using varno). Upmost query
> will have varlevel = 0, all its (dirrect) children - varlevel = 1 and so on.
>                         ^^^                          ^^^^^^^^^^^^
> (I don't see problems with distinguishing Vars of different children
> on the same level...)
>
> >
> > Second, we need to hook the subselect to the main query.  I recommend we
> > add two fields to Query for this:
> >
> >         Query *parentQuery;
> >         List *subqueries;
>
> Agreed. And maybe Index queryLevel.

Sure.  If it helps.

>
> > In the parent query, to parse the WHERE clause, we create a new operator
> > type, called IN or NOT_IN, or ALL, where the left side is a Var, and the
>                                                ^^^^^^^^^^^^^^^^^^
> No. We have to handle (a,b,c) OP (select x, y, z ...) and
> '_a_constant_' OP (select ...) - I don't know is last in standards,
> Sybase has this.

I have never seen this in my eight years of SQL.  Perhaps we can leave
this for later, maybe much later.

>
> Well,
>
> typedef enum OpType
> {
>     OP_EXPR, FUNC_EXPR, OR_EXPR, AND_EXPR, NOT_EXPR
>
> + OP_EXISTS, OP_ALL, OP_ANY
>
> } OpType;
>
> typedef struct Expr
> {
>     NodeTag     type;
>     Oid         typeOid;        /* oid of the type of this expr */
>     OpType      opType;         /* type of the op */
>     Node       *oper;           /* could be Oper or Func */
>     List       *args;           /* list of argument nodes */
> } Expr;
>
> OP_EXISTS: oper is NULL, lfirst(args) is SubSelect (index in subqueries
>            List, following your suggestion)
>
> OP_ALL, OP_ANY:
>
> oper is List of Oper nodes. We need in list because of data types of
> a, b, c (above) can be different and so Oper nodes will be different too.
>
> lfirst(args) is List of expression nodes (Const, Var, Func ?, a + b ?) -
> left side of subquery' operator.
> lsecond(args) is SubSelect.
>
> Note, that there are no OP_IN, OP_NOTIN in OpType-s for Expr. We need in
> IN, NOTIN in A_Expr (parser node), but both of them have to be transferred
> by parser into corresponding ANY and ALL. At the moment we can do:
>
> IN --> = ANY, NOT IN --> <> ALL
>
> but this will be "known bug": this breaks OO-nature of Postgres, because of
> operators can be overrided and '=' can mean  s o m e t h i n g (not equality).
> Example: box data type. For boxes, = means equality of _areas_ and =~
> means that boxes are the same ==> =~ ANY should be used for IN.

That is interesting, to use =~ for ANY.

Yes, but how many operators take a SUBQUERY as an operand.  This is a
special case to me.

I think I see where you are trying to go.  You want subselects to behave
like any other operator, with a subselect type, and you do all the
subselect handling in the optimizer, with special Nodes and actions.

I think this may be just too much of a leap.  We have such clean query
logic for single queries, I can't imagine having an operator that has a
Query operand, and trying to get everything to properly handle it.
UNIONS were very easy to implement as a List off of Query, with some
foreach()'s in rewrite and the high optimizer.

Subselects are SQL standard, and are never going to be over-ridden by a
user.  Same with UNION.  They want UNION, they get UNION.  They want
Subselect, we are going to spin through the Query structure and give
them what they want.

The complexities of subselects and correlated queries and range tables
and stuff is so bizarre that trying to get it to work inside the type
system could be a huge project.

>
> > right side is an index to a slot in the subqueries List.

I guess the question is what can we have by February 1?

I have been reading some postings, and it seems to me that subselects
are the litmus test for many evaluators when deciding if a database
engine is full-featured.

Sorry to be so straightforward, but I want to keep hashing this around
until we get a conclusion, so coding can start.

My suggestions have been, I believe, trying to get subselects working
with the fullest functionality by adding the least amount of code, and
keeping the logic clean.

Have you checked out the UNION code?  It is very small, but it works.  I
think it could make a good sample for subselects.

--
Bruce Momjian
maillist@candle.pha.pa.us

Re: subselects

От
"Vadim B. Mikheev"
Дата:
Bruce Momjian wrote:
>
> > No, I don't like to add anything in parser. Example:
> >
> >         select *
> >         from tabA
> >         where col1 = (select col2
> >                       from tabB
> >                       where tabA.col3 = tabB.col4
> >                       and exists (select *
> >                                   from tabC
> >                                   where tabB.colX = tabC.colX and
> >                                         tabC.colY = tabA.col2)
> >                      )
> >
> > : a column of tabA is referenced in sub-subselect
>
> This is a strange case that I don't think we need to handle in our first
> implementation.

I don't know is this strange case or not :)
But I would like to know is this allowed by standards - can someone
comment on this ?
And I don't see problems with handling this...

>
> > (is it allowable by standards ?) - in this case it's better
> > to don't add tabA to 1st subselect but add tabA to second one
> > and change tabA.col3 in 1st to reference col3 in 2nd subquery temp table -
> > this gives us 2-tables join in 1st subquery instead of 3-tables join.
> > (And I'm still not sure that using temp tables is best of what can be
> > done in all cases...)
>
> I don't see any use for temp tables in subselects anymore.  After having
> implemented UNIONS, I now see how much can be done in the upper
> optimizer.  I see you just putting the subquery PLAN into the proper
> place in the plan tree, with some proper JOIN nodes for IN, NOT IN.

When saying about temp tables, I meant tables created by node Material
for subquery plan. This is one of two ways - run subquery once for all
possible upper plan tuples and then just join result table with upper
query. Another way is re-run subquery for each upper query tuple,
without temp table but may be with caching results by some ways.
Actually, there is special case - when subquery can be alternatively
formulated as joins, - but this is just special case.

> > > In the parent query, to parse the WHERE clause, we create a new operator
> > > type, called IN or NOT_IN, or ALL, where the left side is a Var, and the
> >                                                ^^^^^^^^^^^^^^^^^^
> > No. We have to handle (a,b,c) OP (select x, y, z ...) and
> > '_a_constant_' OP (select ...) - I don't know is last in standards,
> > Sybase has this.
>
> I have never seen this in my eight years of SQL.  Perhaps we can leave
> this for later, maybe much later.

Are you saying about (a, b, c) or about 'a_constant' ?
Again, can someone comment on are they in standards or not ?
Tom ?
If yes then please add parser' support for them now...

> > Note, that there are no OP_IN, OP_NOTIN in OpType-s for Expr. We need in
> > IN, NOTIN in A_Expr (parser node), but both of them have to be transferred
> > by parser into corresponding ANY and ALL. At the moment we can do:
> >
> > IN --> = ANY, NOT IN --> <> ALL
> >
> > but this will be "known bug": this breaks OO-nature of Postgres, because of
> > operators can be overrided and '=' can mean  s o m e t h i n g (not equality).
> > Example: box data type. For boxes, = means equality of _areas_ and =~
> > means that boxes are the same ==> =~ ANY should be used for IN.
>
> That is interesting, to use =~ for ANY.
>
> Yes, but how many operators take a SUBQUERY as an operand.  This is a
> special case to me.
>
> I think I see where you are trying to go.  You want subselects to behave
> like any other operator, with a subselect type, and you do all the
> subselect handling in the optimizer, with special Nodes and actions.
>
> I think this may be just too much of a leap.  We have such clean query
> logic for single queries, I can't imagine having an operator that has a
> Query operand, and trying to get everything to properly handle it.
> UNIONS were very easy to implement as a List off of Query, with some
> foreach()'s in rewrite and the high optimizer.
>
> Subselects are SQL standard, and are never going to be over-ridden by a
> user.  Same with UNION.  They want UNION, they get UNION.  They want
> Subselect, we are going to spin through the Query structure and give
> them what they want.
>
> The complexities of subselects and correlated queries and range tables
> and stuff is so bizarre that trying to get it to work inside the type
> system could be a huge project.

PostgreSQL is a robust, next-generation, Object-Relational DBMS (ORDBMS),
derived from the Berkeley Postgres database management system. While
PostgreSQL retains the powerful object-relational data model, rich data types and
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
easy extensibility of Postgres, it replaces the PostQuel query language with an
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
extended subset of SQL.
^^^^^^^^^^^^^^^^^^^^^^

Should we say users that subselect will work for standard data types only ?
I don't see why subquery can't be used with ~, ~*, @@, ... operators, do you ?
Is there difference between handling = ANY and ~ ANY ? I don't see any.
Currently we can't get IN working properly for boxes (and may be for others too)
and I don't like to try to resolve these problems now, but hope that someday
we'll be able to do this. At the moment - just convert IN into = ANY and
NOT IN into <> ALL in parser.

(BTW, do you know how DISTINCT is implemented ? It doesn't use = but
use type_out funcs and uses strcmp()... DISTINCT is standard SQL thing...)

> >
> > > right side is an index to a slot in the subqueries List.
>
> I guess the question is what can we have by February 1?
>
> I have been reading some postings, and it seems to me that subselects
> are the litmus test for many evaluators when deciding if a database
> engine is full-featured.
>
> Sorry to be so straightforward, but I want to keep hashing this around
> until we get a conclusion, so coding can start.
>
> My suggestions have been, I believe, trying to get subselects working
> with the fullest functionality by adding the least amount of code, and
> keeping the logic clean.
>
> Have you checked out the UNION code?  It is very small, but it works.  I
> think it could make a good sample for subselects.

There is big difference between subqueries and queries in UNION -
there are not dependences between UNION queries.

Ok, opened issues:

1. Is using upper query' vars in all subquery levels in standard ?
2. Is (a, b, c) OP (subselect) in standard ?
3. What types of expressions (Var, Const, ...) are allowed on the left
   side of operator with subquery on the right ?
4. What types of operators should we support (=, >, ..., like, ~, ...) ?
   (My vote for all boolean operators).

And - did we get consensus on presentation subqueries stuff in Query,
Expr and Var ?
I would like to have something done in parser near Jan 17 to get
subqueries working by Feb 1. I vote for support of all standard
things (1. - 3.) in parser right now - if there will be no time
to implement something like (a, b, c) then optimizer will call
elog(WARN) (oh, sorry, - elog(ERROR)).

Vadim

Re: subselects

От
"Thomas G. Lockhart"
Дата:
> > > Note, that there are no OP_IN, OP_NOTIN in OpType-s for Expr. We need in
> > > IN, NOTIN in A_Expr (parser node), but both of them have to be transferred
> > > by parser into corresponding ANY and ALL. At the moment we can do:
> > >
> > > IN --> = ANY, NOT IN --> <> ALL
> > >
> > > but this will be "known bug": this breaks OO-nature of Postgres, because of
> > > operators can be overrided and '=' can mean  s o m e t h i n g (not equality).
> > > Example: box data type. For boxes, = means equality of _areas_ and =~
> > > means that boxes are the same ==> =~ ANY should be used for IN.
> >
> > That is interesting, to use =~ for ANY.

If I understand the discussion, I would think is is fine to make an assumption about
which operator is used to implement a subselect expression. If someone remaps an
operator to mean something different, then they will get a different result (or a
nonsensical one) from a subselect.

I'd be happy to remap existing operators to fit into a convention which would work
with subselects (especially if I got to help choose :).

> > Subselects are SQL standard, and are never going to be over-ridden by a
> > user.  Same with UNION.  They want UNION, they get UNION.  They want
> > Subselect, we are going to spin through the Query structure and give
> > them what they want.
>
> PostgreSQL is a robust, next-generation, Object-Relational DBMS (ORDBMS),
> derived from the Berkeley Postgres database management system. While
> PostgreSQL retains the powerful object-relational data model, rich data types and
>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> easy extensibility of Postgres, it replaces the PostQuel query language with an
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> extended subset of SQL.
> ^^^^^^^^^^^^^^^^^^^^^^
>
> Should we say users that subselect will work for standard data types only ?
> I don't see why subquery can't be used with ~, ~*, @@, ... operators, do you ?
> Is there difference between handling = ANY and ~ ANY ? I don't see any.
> Currently we can't get IN working properly for boxes (and may be for others too)
> and I don't like to try to resolve these problems now, but hope that someday
> we'll be able to do this. At the moment - just convert IN into = ANY and
> NOT IN into <> ALL in parser.
>
> (BTW, do you know how DISTINCT is implemented ? It doesn't use = but
> use type_out funcs and uses strcmp()... DISTINCT is standard SQL thing...)

?? I didn't know that. Wouldn't we want it to eventually use "=" through a sorted
list? That would give more consistant behavior...

> > I have been reading some postings, and it seems to me that subselects
> > are the litmus test for many evaluators when deciding if a database
> > engine is full-featured.
> >
> > Sorry to be so straightforward, but I want to keep hashing this around
> > until we get a conclusion, so coding can start.
> >
> > My suggestions have been, I believe, trying to get subselects working
> > with the fullest functionality by adding the least amount of code, and
> > keeping the logic clean.
> >
> > Have you checked out the UNION code?  It is very small, but it works.  I
> > think it could make a good sample for subselects.
>
> There is big difference between subqueries and queries in UNION -
> there are not dependences between UNION queries.
>
> Ok, opened issues:
>
> 1. Is using upper query' vars in all subquery levels in standard ?

I'm not certain. Let me know if you do not get an answer from someone else and I will
research it.

> 2. Is (a, b, c) OP (subselect) in standard ?

Yes. In fact, it _is_ the standard, and "a OP (subselect)" is a special case where
the parens are allowed to be omitted from a one element list.

> 3. What types of expressions (Var, Const, ...) are allowed on the left
>    side of operator with subquery on the right ?

I think most expressions are allowed. The "constant OP (subselect)" case you were
asking about is just a simplified case since "(a, b, constant) OP (subselect)" where
a and b are column references should be allowed. Of course, our optimizer could
perhaps change this to "(a, b) OP (subselect where x = constant)", or for the first
example "EXISTS (subselect where x = constant)".

> 4. What types of operators should we support (=, >, ..., like, ~, ...) ?
>    (My vote for all boolean operators).

Sounds good. But I'll vote with Bruce (and I'll bet you already agree) that it is
important to get an initial implementation for v6.3 which covers a little, some, or
all of the usual SQL subselect constructs. If we have to revisit this for v6.4 then
we will have the benefit of feedback from others in practical applications which
always uncovers new things to consider.

> And - did we get consensus on presentation subqueries stuff in Query,
> Expr and Var ?
> I would like to have something done in parser near Jan 17 to get
> subqueries working by Feb 1. I vote for support of all standard
> things (1. - 3.) in parser right now - if there will be no time
> to implement something like (a, b, c) then optimizer will callelog(WARN) (oh,
> sorry, - elog(ERROR)).

Great. I'd like to help with the remaining parser issues; at the moment "row_expr"
does the right thing with expression comparisions but just parses then ignores
subselect expressions. Let me know what structures you want passed back and I'll put
them in, or if you prefer put in the first one and I'll go through and clean up and
add the rest.

                                                  - Tom


Re: [HACKERS] Re: subselects

От
The Hermit Hacker
Дата:
On Sun, 11 Jan 1998, Vadim B. Mikheev wrote:

> > > No, I don't like to add anything in parser. Example:
> > >
> > >         select *
> > >         from tabA
> > >         where col1 = (select col2
> > >                       from tabB
> > >                       where tabA.col3 = tabB.col4
> > >                       and exists (select *
> > >                                   from tabC
> > >                                   where tabB.colX = tabC.colX and
> > >                                         tabC.colY = tabA.col2)
> > >                      )
> > >
> > > : a column of tabA is referenced in sub-subselect
> >
> > This is a strange case that I don't think we need to handle in our first
> > implementation.
>
> I don't know is this strange case or not :)
> But I would like to know is this allowed by standards - can someone
> comment on this ?
> And I don't see problems with handling this...

    I don't know about "the standards", but in my mind, the above should
work if subselects work...so what if you add a third or fourth level subselect
to the overall query?  IMHO, the "outer most" (inner most?) subselect should
be resolved to provide the "EXISTS" list, the the next should be resolved,
etc...

    Hell...looking at this, I'd almost think that you could use subselects to
force a pseudo-ordering onto a large complex JOIN (ya ya, really messy though)

Marc G. Fournier
Systems Administrator @ hub.org
primary: scrappy@hub.org           secondary: scrappy@{freebsd|postgresql}.org


Re: [HACKERS] Re: subselects

От
"Thomas G. Lockhart"
Дата:
> Are you saying about (a, b, c) or about 'a_constant' ?
> Again, can someone comment on are they in standards or not ?
> Tom ?
> If yes then please add parser' support for them now...

As I mentioned a few minutes ago in my last message, I parse the row descriptors and
the subselects but for subselect expressions (e.g. "(a,b) OP (subselect)" I currently
ignore the result. I didn't want to pass things back as lists until something in the
backend was ready to receive them.

If it is OK, I'll go ahead and start passing back a list of expressions when a row
descriptor is present. So, what you will find is lexpr or rexpr in the A_Expr node
being a list rather than an atomic node.

Also, I can start passing back the subselect expression as the rexpr; right now the
parser calls elog() and quits.

btw, to implement "(a,b,c) OP (d,e,f)" I made a new routine in the parser called
makeRowExpr() which breaks this up into a sequence of "and" and/or "or" expressions.
If lists are handled farther back, this routine should move to there also and the
parser will just pass the lists. Note that some assumptions have to be made about the
meaning of "(a,b) OP (c,d)", since usually we only have knowledge of the behavior of
"a OP c". Easy for the standard SQL operators, unknown for others, but maybe it is OK
to disallow those cases or to look for specific appearance of the operator to guess
the behavior (e.g. if the operator has "<" or "=" or ">" then build as "and"s and if
it has "<>" or "!" then build as "or"s.

Let me know what you want...

                                                       - Tom


Re: [HACKERS] Re: subselects

От
"Thomas G. Lockhart"
Дата:
> I would like to have something done in parser near Jan 17 to get
> subqueries working by Feb 1.

Here are some changes to gram.y and to keywords.c which start to pass through
subselect constructs. I won't commit until/unless you have a chance to look at it and
agree that this is something close to the right direction to head.

                                                                      - Tom

postgres=> create table x (i int);
CREATE
postgres=> insert into x values (1);
INSERT 18121 1
postgres=> select i from x where i = 1;
i
-
1
(1 row)

postgres=> select i from x where i in (select i from x);
ERROR:  transformExpr: does not know how to transform node 604
postgres=> select i from x where (i, 1) in (select i, 1 from x);
ERROR:  transformExpr: does not know how to transform node 501
postgres=>
%{ /* -*-text-*- */

/*#define YYDEBUG 1*/
/*-------------------------------------------------------------------------
 *
 * gram.y--
 *      POSTGRES SQL YACC rules/actions
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *      $Header: /usr/local/cvsroot/pgsql/src/backend/parser/gram.y,v 1.88 1998/01/10 04:29:50 momjian Exp $
 *
 * HISTORY
 *      AUTHOR            DATE            MAJOR EVENT
 *      Andrew Yu            Sept, 1994        POSTQUEL to SQL conversion
 *      Andrew Yu            Oct, 1994        lispy code conversion
 *
 * NOTES
 *      CAPITALS are used to represent terminal symbols.
 *      non-capitals are used to represent non-terminals.
 *      SQL92-specific syntax is separated from plain SQL/Postgres syntax
 *      to help isolate the non-extensible portions of the parser.
 *
 *      if you use list, make sure the datum is a node so that the printing
 *      routines work
 *
 * WARNING
 *      sometimes we assign constants to makeStrings. Make sure we don't free
 *      those.
 *
 *-------------------------------------------------------------------------
 */
#include <string.h>
#include <ctype.h>

#include "postgres.h"
#include "nodes/parsenodes.h"
#include "nodes/print.h"
#include "parser/gramparse.h"
#include "parser/parse_type.h"
#include "utils/acl.h"
#include "utils/palloc.h"
#include "catalog/catname.h"
#include "utils/elog.h"
#include "access/xact.h"

static char saved_relname[NAMEDATALEN];  /* need this for complex attributes */
static bool QueryIsRule = FALSE;
static Node *saved_In_Expr;
static Oid    *param_type_info;
static int    pfunc_num_args;
extern List *parsetree;


/*
 * If you need access to certain yacc-generated variables and find that
 * they're static by default, uncomment the next line.  (this is not a
 * problem, yet.)
 */
/*#define __YYSCLASS*/

static char *xlateSqlType(char *);
static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr);
static Node *makeRowExpr(char *opr, List *largs, List *rargs);
void mapTargetColumns(List *source, List *target);
static List *makeConstantList( A_Const *node);
static char *FlattenStringList(List *list);
static char *fmtId(char *rawid);
static Node *makeIndexable(char *opname, Node *lexpr, Node *rexpr);
static void param_type_init(Oid *typev, int nargs);

Oid    param_type(int t); /* used in parse_expr.c */

/* old versions of flex define this as a macro */
#if defined(yywrap)
#undef yywrap
#endif /* yywrap */
%}


%union
{
    double                dval;
    int                    ival;
    char                chr;
    char                *str;
    bool                boolean;
    bool*                pboolean;    /* for pg_user privileges */
    List                *list;
    Node                *node;
    Value                *value;

    Attr                *attr;

    TypeName            *typnam;
    DefElem                *defelt;
    ParamString            *param;
    SortGroupBy            *sortgroupby;
    IndexElem            *ielem;
    RangeVar            *range;
    RelExpr                *relexp;
    A_Indices            *aind;
    ResTarget            *target;
    ParamNo                *paramno;

    VersionStmt            *vstmt;
    DefineStmt            *dstmt;
    RuleStmt            *rstmt;
    InsertStmt            *astmt;
}

%type <node>    stmt,
        AddAttrStmt, ClosePortalStmt,
        CopyStmt, CreateStmt, CreateAsStmt, CreateSeqStmt, DefineStmt, DestroyStmt,
        ExtendStmt, FetchStmt,    GrantStmt, CreateTrigStmt, DropTrigStmt,
        CreatePLangStmt, DropPLangStmt,
        IndexStmt, ListenStmt, OptimizableStmt,
        ProcedureStmt,     RecipeStmt, RemoveAggrStmt, RemoveOperStmt,
        RemoveFuncStmt, RemoveStmt,
        RenameStmt, RevokeStmt, RuleStmt, TransactionStmt, ViewStmt, LoadStmt,
        CreatedbStmt, DestroydbStmt, VacuumStmt, CursorStmt, SubSelect,
        UpdateStmt, InsertStmt, SelectStmt, NotifyStmt, DeleteStmt, ClusterStmt,
        ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt,
        CreateUserStmt, AlterUserStmt, DropUserStmt

%type <rtstmt>

%type <str>        opt_database, location

%type <pboolean> user_createdb_clause, user_createuser_clause
%type <str>   user_passwd_clause
%type <str>   user_valid_clause
%type <list>  user_group_list, user_group_clause

%type <str>        join_expr, join_outer, join_spec
%type <boolean> TriggerActionTime, TriggerForSpec, PLangTrusted

%type <str>        TriggerEvents, TriggerFuncArg

%type <str>        relation_name, copy_file_name, copy_delimiter, def_name,
        database_name, access_method_clause, access_method, attr_name,
        class, index_name, name, file_name, recipe_name, aggr_argtype

%type <str>        opt_id, opt_portal_name,
        all_Op, MathOp, opt_name, opt_unique,
        result, OptUseOp, opt_class, SpecialRuleRelation

%type <str>        privileges, operation_commalist, grantee
%type <chr>        operation, TriggerOneEvent

%type <list>    stmtblock, stmtmulti,
        relation_name_list, OptTableElementList,
        OptInherit, definition,
        opt_with, def_args, def_name_list, func_argtypes,
        oper_argtypes, OptStmtList, OptStmtBlock, OptStmtMulti,
        opt_column_list, columnList, opt_va_list, va_list,
        sort_clause, sortby_list, index_params, index_list, name_list,
        from_clause, from_list, opt_array_bounds, nest_array_bounds,
        expr_list, attrs, res_target_list, res_target_list2,
        def_list, opt_indirection, group_clause, groupby_list, TriggerFuncArgs

%type <list>    union_clause, select_list
%type <list>    join_list
%type <sortgroupby>
                join_using
%type <boolean>    opt_union

%type <node>    position_expr
%type <list>    extract_list, position_list
%type <list>    substr_list, substr_from, substr_for, trim_list
%type <list>    opt_interval

%type <boolean> opt_inh_star, opt_binary, opt_instead, opt_with_copy,
                index_opt_unique, opt_verbose, opt_analyze

%type <ival>    copy_dirn, def_type, opt_direction, remove_type,
                opt_column, event

%type <ival>    fetch_how_many

%type <list>    OptSeqList
%type <defelt>    OptSeqElem

%type <dstmt>    def_rest
%type <astmt>    insert_rest

%type <node>    OptTableElement, ConstraintElem
%type <node>    columnDef, alter_clause
%type <defelt>    def_elem
%type <node>    def_arg, columnElem, where_clause,
                a_expr, a_expr_or_null, AexprConst,
                in_expr, in_expr_nodes, not_in_expr, not_in_expr_nodes,
                having_clause
%type <list>    row_descriptor, row_list
%type <node>    row_expr
%type <str>        RowOp, row_opt
%type <list>    OptCreateAs, CreateAsList
%type <node>    CreateAsElement
%type <value>    NumConst
%type <attr>    event_object, attr
%type <sortgroupby>        groupby
%type <sortgroupby>        sortby
%type <ielem>    index_elem, func_index
%type <range>    from_val
%type <relexp>    relation_expr
%type <target>    res_target_el, res_target_el2
%type <paramno> ParamNo

%type <typnam>    Typename, opt_type, Array, Generic, Character, Datetime, Numeric
%type <str>        generic, character, datetime
%type <str>        opt_charset, opt_collate
%type <str>        opt_float, opt_numeric, opt_decimal
%type <boolean>    opt_varying, opt_timezone

%type <ival>    Iconst
%type <str>        Sconst
%type <str>        Id, var_value, zone_value
%type <str>        ColId, ColLabel

%type <node>    TableConstraint
%type <list>    constraint_list, constraint_expr
%type <list>    default_list, default_expr
%type <list>    ColQualList, ColQualifier
%type <node>    ColConstraint, ColConstraintElem
%type <list>    key_actions, key_action
%type <str>        key_match, key_reference

/*
 * If you make any token changes, remember to:
 *        - use "yacc -d" and update parse.h
 *        - update the keyword table in parser/keywords.c
 */

/* Reserved word tokens
 * SQL92 syntax has many type-specific constructs.
 * So, go ahead and make these types reserved words,
 *  and call-out the syntax explicitly.
 * This gets annoying when trying to also retain Postgres' nice
 *  type-extensible features, but we don't really have a choice.
 * - thomas 1997-10-11
 */

/* Keywords (in SQL92 reserved words) */
%token    ACTION, ADD, ALL, ALTER, AND, ANY, AS, ASC,
        BEGIN_TRANS, BETWEEN, BOTH, BY,
        CASCADE, CAST, CHAR, CHARACTER, CHECK, CLOSE, COLLATE, COLUMN, COMMIT,
        CONSTRAINT, CREATE, CROSS, CURRENT, CURRENT_DATE, CURRENT_TIME,
        CURRENT_TIMESTAMP, CURRENT_USER, CURSOR,
        DAY_P, DECIMAL, DECLARE, DEFAULT, DELETE, DESC, DISTINCT, DOUBLE, DROP,
        END_TRANS, EXECUTE, EXISTS, EXTRACT,
        FETCH, FLOAT, FOR, FOREIGN, FROM, FULL,
        GRANT, GROUP, HAVING, HOUR_P,
        IN, INNER_P, INSERT, INTERVAL, INTO, IS,
        JOIN, KEY, LANGUAGE, LEADING, LEFT, LIKE, LOCAL,
        MATCH, MINUTE_P, MONTH_P,
        NATIONAL, NATURAL, NCHAR, NO, NOT, NOTIFY, NULL_P, NUMERIC,
        ON, OPTION, OR, ORDER, OUTER_P,
        PARTIAL, POSITION, PRECISION, PRIMARY, PRIVILEGES, PROCEDURE, PUBLIC,
        REFERENCES, REVOKE, RIGHT, ROLLBACK,
        SECOND_P, SELECT, SET, SOME, SUBSTRING,
        TABLE, TIME, TIMESTAMP, TO, TRAILING, TRANSACTION, TRIM,
        UNION, UNIQUE, UPDATE, USING,
        VALUES, VARCHAR, VARYING, VERBOSE, VERSION, VIEW,
        WHERE, WITH, WORK, YEAR_P, ZONE

/* Keywords (in SQL3 reserved words) */
%token    FALSE_P, TRIGGER, TRUE_P

/* Keywords (in SQL92 non-reserved words) */
%token    TYPE_P

/* Keywords for Postgres support (not in SQL92 reserved words) */
%token    ABORT_TRANS, ACL, AFTER, AGGREGATE, ANALYZE,
        APPEND, BACKWARD, BEFORE, BINARY, CHANGE, CLUSTER, COPY,
        DATABASE, DELIMITERS, DO, EXPLAIN, EXTEND,
        FORWARD, FUNCTION, HANDLER,
        INDEX, INHERITS, INSTEAD, ISNULL,
        LANCOMPILER, LISTEN, LOAD, LOCATION, MERGE, MOVE,
        NEW, NONE, NOTHING, NOTNULL, OIDS, OPERATOR, PROCEDURAL,
        RECIPE, RENAME, REPLACE, RESET, RETRIEVE, RETURNS, RULE,
        SEQUENCE, SETOF, SHOW, STDIN, STDOUT, TRUSTED,
        VACUUM, VERBOSE, VERSION

/* Keywords (obsolete; retain temporarily for parser - thomas 1997-12-04) */
%token    ARCHIVE

/*
 * Tokens for pg_passwd support.  The CREATEDB and CREATEUSER tokens should go away
 * when some sort of pg_privileges relation is introduced.
 *
 *                                    Todd A. Brandys
 */
%token    USER, PASSWORD, CREATEDB, NOCREATEDB, CREATEUSER, NOCREATEUSER, VALID, UNTIL

/* Special keywords, not in the query language - see the "lex" file */
%token <str>    IDENT, SCONST, Op
%token <ival>    ICONST, PARAM
%token <dval>    FCONST

/* these are not real. they are here so that they get generated as #define's*/
%token            OP

/* precedence */
%left        OR
%left        AND
%right        NOT
%right        '='
%nonassoc    '<' '>'
%nonassoc    LIKE
%nonassoc    BETWEEN
%nonassoc    IN
%nonassoc    Op                /* multi-character ops and user-defined operators */
%nonassoc    NOTNULL
%nonassoc    ISNULL
%nonassoc    IS
%left        '+' '-'
%left        '*' '/'
%left        '|'                /* this is the relation union op, not logical or */
/* Unary Operators */
%right        ':'
%left        ';'                /* end of statement or natural log */
%right        UMINUS
%left        '.'
%left        '[' ']'
%nonassoc    TYPECAST
%nonassoc    REDUCE
%left        UNION
%%

stmtblock:  stmtmulti
                { parsetree = $1; }
        | stmt
                { parsetree = lcons($1,NIL); }
        ;

stmtmulti:  stmtmulti stmt ';'
                { $$ = lappend($1, $2); }
        | stmtmulti stmt
                { $$ = lappend($1, $2); }
        | stmt ';'
                { $$ = lcons($1,NIL); }
        ;

stmt :      AddAttrStmt
        | AlterUserStmt
        | ClosePortalStmt
        | CopyStmt
        | CreateStmt
        | CreateAsStmt
        | CreateSeqStmt
        | CreatePLangStmt
        | CreateTrigStmt
        | CreateUserStmt
        | ClusterStmt
        | DefineStmt
        | DestroyStmt
        | DropPLangStmt
        | DropTrigStmt
        | DropUserStmt
        | ExtendStmt
        | ExplainStmt
        | FetchStmt
        | GrantStmt
        | IndexStmt
        | ListenStmt
        | ProcedureStmt
        | RecipeStmt
        | RemoveAggrStmt
        | RemoveOperStmt
        | RemoveFuncStmt
        | RemoveStmt
        | RenameStmt
        | RevokeStmt
        | OptimizableStmt
        | RuleStmt
        | TransactionStmt
        | ViewStmt
        | LoadStmt
        | CreatedbStmt
        | DestroydbStmt
        | VacuumStmt
        | VariableSetStmt
        | VariableShowStmt
        | VariableResetStmt
        ;

/*****************************************************************************
 *
 * Create a new Postgres DBMS user
 *
 *
 *****************************************************************************/

CreateUserStmt:  CREATE USER Id user_passwd_clause user_createdb_clause
            user_createuser_clause user_group_clause user_valid_clause
                {
                    CreateUserStmt *n = makeNode(CreateUserStmt);
                    n->user = $3;
                    n->password = $4;
                    n->createdb = $5;
                    n->createuser = $6;
                    n->groupElts = $7;
                    n->validUntil = $8;
                    $$ = (Node *)n;
                }
        ;

/*****************************************************************************
 *
 * Alter a postresql DBMS user
 *
 *
 *****************************************************************************/

AlterUserStmt:  ALTER USER Id user_passwd_clause user_createdb_clause
            user_createuser_clause user_group_clause user_valid_clause
                {
                    AlterUserStmt *n = makeNode(AlterUserStmt);
                    n->user = $3;
                    n->password = $4;
                    n->createdb = $5;
                    n->createuser = $6;
                    n->groupElts = $7;
                    n->validUntil = $8;
                    $$ = (Node *)n;
                }
        ;

/*****************************************************************************
 *
 * Drop a postresql DBMS user
 *
 *
 *****************************************************************************/

DropUserStmt:  DROP USER Id
                {
                    DropUserStmt *n = makeNode(DropUserStmt);
                    n->user = $3;
                    $$ = (Node *)n;
                }
        ;

user_passwd_clause:  WITH PASSWORD Id            { $$ = $3; }
            | /*EMPTY*/                            { $$ = NULL; }
        ;

user_createdb_clause:  CREATEDB
                {
                    bool*  b;
                    $$ = (b = (bool*)palloc(sizeof(bool)));
                    *b = true;
                }
            | NOCREATEDB
                {
                    bool*  b;
                    $$ = (b = (bool*)palloc(sizeof(bool)));
                    *b = false;
                }
            | /*EMPTY*/                            { $$ = NULL; }
        ;

user_createuser_clause:  CREATEUSER
                {
                    bool*  b;
                    $$ = (b = (bool*)palloc(sizeof(bool)));
                    *b = true;
                }
            | NOCREATEUSER
                {
                    bool*  b;
                    $$ = (b = (bool*)palloc(sizeof(bool)));
                    *b = false;
                }
            | /*EMPTY*/                            { $$ = NULL; }
        ;

user_group_list:  user_group_list ',' Id
                {
                    $$ = lcons((void*)makeString($3), $1);
                }
            | Id
                {
                    $$ = lcons((void*)makeString($1), NIL);
                }
        ;

user_group_clause:  IN GROUP user_group_list    { $$ = $3; }
            | /*EMPTY*/                            { $$ = NULL; }
        ;

user_valid_clause:  VALID UNTIL SCONST            { $$ = $3; }
            | /*EMPTY*/                            { $$ = NULL; }
        ;

/*****************************************************************************
 *
 * Set PG internal variable
 *      SET name TO 'var_value'
 * Include SQL92 syntax (thomas 1997-10-22):
 *    SET TIME ZONE 'var_value'
 *
 *****************************************************************************/

VariableSetStmt:  SET ColId TO var_value
                {
                    VariableSetStmt *n = makeNode(VariableSetStmt);
                    n->name  = $2;
                    n->value = $4;
                    $$ = (Node *) n;
                }
        | SET ColId '=' var_value
                {
                    VariableSetStmt *n = makeNode(VariableSetStmt);
                    n->name  = $2;
                    n->value = $4;
                    $$ = (Node *) n;
                }
        | SET TIME ZONE zone_value
                {
                    VariableSetStmt *n = makeNode(VariableSetStmt);
                    n->name  = "timezone";
                    n->value = $4;
                    $$ = (Node *) n;
                }
        ;

var_value:  Sconst            { $$ = $1; }
        | DEFAULT            { $$ = NULL; }
        ;

zone_value:  Sconst            { $$ = $1; }
        | DEFAULT            { $$ = NULL; }
        | LOCAL                { $$ = "default"; }
        ;

VariableShowStmt:  SHOW ColId
                {
                    VariableShowStmt *n = makeNode(VariableShowStmt);
                    n->name  = $2;
                    $$ = (Node *) n;
                }
        | SHOW TIME ZONE
                {
                    VariableShowStmt *n = makeNode(VariableShowStmt);
                    n->name  = "timezone";
                    $$ = (Node *) n;
                }
        ;

VariableResetStmt:    RESET ColId
                {
                    VariableResetStmt *n = makeNode(VariableResetStmt);
                    n->name  = $2;
                    $$ = (Node *) n;
                }
        | RESET TIME ZONE
                {
                    VariableResetStmt *n = makeNode(VariableResetStmt);
                    n->name  = "timezone";
                    $$ = (Node *) n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY :
 *                addattr ( attr1 = type1 .. attrn = typen ) to <relname> [*]
 *
 *****************************************************************************/

AddAttrStmt:  ALTER TABLE relation_name opt_inh_star alter_clause
                {
                    AddAttrStmt *n = makeNode(AddAttrStmt);
                    n->relname = $3;
                    n->inh = $4;
                    n->colDef = $5;
                    $$ = (Node *)n;
                }
        ;

alter_clause:  ADD opt_column columnDef
                {
                    $$ = $3;
                }
            | ADD '(' OptTableElementList ')'
                {
                    Node *lp = lfirst($3);

                    if (length($3) != 1)
                        elog(ERROR,"ALTER TABLE/ADD() allows one column only");
                    $$ = lp;
                }
            | DROP opt_column ColId
                {    elog(ERROR,"ALTER TABLE/DROP COLUMN not yet implemented"); }
            | ALTER opt_column ColId SET DEFAULT default_expr
                {    elog(ERROR,"ALTER TABLE/ALTER COLUMN/SET DEFAULT not yet implemented"); }
            | ALTER opt_column ColId DROP DEFAULT
                {    elog(ERROR,"ALTER TABLE/ALTER COLUMN/DROP DEFAULT not yet implemented"); }
            | ADD ConstraintElem
                {    elog(ERROR,"ALTER TABLE/ADD CONSTRAINT not yet implemented"); }
        ;


/*****************************************************************************
 *
 *        QUERY :
 *                close <optname>
 *
 *****************************************************************************/

ClosePortalStmt:  CLOSE opt_id
                {
                    ClosePortalStmt *n = makeNode(ClosePortalStmt);
                    n->portalname = $2;
                    $$ = (Node *)n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY :
 *                COPY [BINARY] <relname> FROM/TO
 *                [USING DELIMITERS <delimiter>]
 *
 *****************************************************************************/

CopyStmt:  COPY opt_binary relation_name opt_with_copy copy_dirn copy_file_name copy_delimiter
                {
                    CopyStmt *n = makeNode(CopyStmt);
                    n->binary = $2;
                    n->relname = $3;
                    n->oids = $4;
                    n->direction = $5;
                    n->filename = $6;
                    n->delimiter = $7;
                    $$ = (Node *)n;
                }
        ;

copy_dirn:    TO
                { $$ = TO; }
        | FROM
                { $$ = FROM; }
        ;

/*
 * copy_file_name NULL indicates stdio is used. Whether stdin or stdout is
 * used depends on the direction. (It really doesn't make sense to copy from
 * stdout. We silently correct the "typo".         - AY 9/94
 */
copy_file_name:  Sconst                            { $$ = $1; }
        | STDIN                                    { $$ = NULL; }
        | STDOUT                                { $$ = NULL; }
        ;

opt_binary:  BINARY                                { $$ = TRUE; }
        | /*EMPTY*/                                { $$ = FALSE; }
        ;

opt_with_copy:    WITH OIDS                        { $$ = TRUE; }
        | /*EMPTY*/                                { $$ = FALSE; }
        ;

/*
 * the default copy delimiter is tab but the user can configure it
 */
copy_delimiter:  USING DELIMITERS Sconst        { $$ = $3; }
        | /*EMPTY*/                                { $$ = "\t"; }
        ;


/*****************************************************************************
 *
 *        QUERY :
 *                CREATE relname
 *
 *****************************************************************************/

CreateStmt:  CREATE TABLE relation_name '(' OptTableElementList ')'
                OptInherit OptArchiveType
                {
                    CreateStmt *n = makeNode(CreateStmt);
                    n->relname = $3;
                    n->tableElts = $5;
                    n->inhRelnames = $7;
                    n->constraints = NIL;
                    $$ = (Node *)n;
                }
        ;

OptTableElementList:  OptTableElementList ',' OptTableElement
                                                { $$ = lappend($1, $3); }
            | OptTableElement                    { $$ = lcons($1, NIL); }
            | /*EMPTY*/                            { $$ = NULL; }
        ;

OptTableElement:  columnDef                        { $$ = $1; }
            | TableConstraint                    { $$ = $1; }
        ;

columnDef:  ColId Typename ColQualifier
                {
                    ColumnDef *n = makeNode(ColumnDef);
                    n->colname = $1;
                    n->typename = $2;
                    n->defval = NULL;
                    n->is_not_null = FALSE;
                    n->constraints = $3;
                    $$ = (Node *)n;
                }
        ;

ColQualifier:  ColQualList                        { $$ = $1; }
            | /*EMPTY*/                            { $$ = NULL; }
        ;

ColQualList:  ColQualList ColConstraint            { $$ = lappend($1,$2); }
            | ColConstraint                        { $$ = lcons($1, NIL); }
        ;

ColConstraint:
        CONSTRAINT name ColConstraintElem
                {
                        Constraint *n = (Constraint *)$3;
                        n->name = fmtId($2);
                        $$ = $3;
                }
        | ColConstraintElem
                { $$ = $1; }
        ;

ColConstraintElem:  CHECK '(' constraint_expr ')'
                {
                    Constraint *n = makeNode(Constraint);
                    n->contype = CONSTR_CHECK;
                    n->name = NULL;
                    n->def = FlattenStringList($3);
                    n->keys = NULL;
                    $$ = (Node *)n;
                }
            | DEFAULT default_expr
                {
                    Constraint *n = makeNode(Constraint);
                    n->contype = CONSTR_DEFAULT;
                    n->name = NULL;
                    n->def = FlattenStringList($2);
                    n->keys = NULL;
                    $$ = (Node *)n;
                }
            | NOT NULL_P
                {
                    Constraint *n = makeNode(Constraint);
                    n->contype = CONSTR_NOTNULL;
                    n->name = NULL;
                    n->def = NULL;
                    n->keys = NULL;
                    $$ = (Node *)n;
                }
            | UNIQUE
                {
                    Constraint *n = makeNode(Constraint);
                    n->contype = CONSTR_UNIQUE;
                    n->name = NULL;
                    n->def = NULL;
                    n->keys = NULL;
                    $$ = (Node *)n;
                }
            | PRIMARY KEY
                {
                    Constraint *n = makeNode(Constraint);
                    n->contype = CONSTR_PRIMARY;
                    n->name = NULL;
                    n->def = NULL;
                    n->keys = NULL;
                    $$ = (Node *)n;
                }
            | REFERENCES ColId opt_column_list key_match key_actions
                {
                    elog(NOTICE,"CREATE TABLE/FOREIGN KEY clause ignored; not yet implemented");
                    $$ = NULL;
                }
        ;

default_list:  default_list ',' default_expr
                {
                    $$ = lappend($1,makeString(","));
                    $$ = nconc($$, $3);
                }
            | default_expr
                {
                    $$ = $1;
                }
        ;

default_expr:  AexprConst
                {    $$ = makeConstantList((A_Const *) $1); }
            | NULL_P
                {    $$ = lcons( makeString("NULL"), NIL); }
            | '-' default_expr %prec UMINUS
                {    $$ = lcons( makeString( "-"), $2); }
            | default_expr '+' default_expr
                {    $$ = nconc( $1, lcons( makeString( "+"), $3)); }
            | default_expr '-' default_expr
                {    $$ = nconc( $1, lcons( makeString( "-"), $3)); }
            | default_expr '/' default_expr
                {    $$ = nconc( $1, lcons( makeString( "/"), $3)); }
            | default_expr '*' default_expr
                {    $$ = nconc( $1, lcons( makeString( "*"), $3)); }
            | default_expr '=' default_expr
                {    elog(ERROR,"boolean expressions not supported in DEFAULT"); }
            | default_expr '<' default_expr
                {    elog(ERROR,"boolean expressions not supported in DEFAULT"); }
            | default_expr '>' default_expr
                {    elog(ERROR,"boolean expressions not supported in DEFAULT"); }
            | ':' default_expr
                {    $$ = lcons( makeString( ":"), $2); }
            | ';' default_expr
                {    $$ = lcons( makeString( ";"), $2); }
            | '|' default_expr
                {    $$ = lcons( makeString( "|"), $2); }
            | default_expr TYPECAST Typename
                {
                    $3->name = fmtId($3->name);
                    $$ = nconc( lcons( makeString( "CAST"), $1), makeList( makeString("AS"), $3, -1));
                }
            | CAST default_expr AS Typename
                {
                    $4->name = fmtId($4->name);
                    $$ = nconc( lcons( makeString( "CAST"), $2), makeList( makeString("AS"), $4, -1));
                }
            | '(' default_expr ')'
                {    $$ = lappend( lcons( makeString( "("), $2), makeString( ")")); }
            | name '(' ')'
                {
                    $$ = makeList( makeString($1), makeString("("), -1);
                    $$ = lappend( $$, makeString(")"));
                }
            | name '(' default_list ')'
                {
                    $$ = makeList( makeString($1), makeString("("), -1);
                    $$ = nconc( $$, $3);
                    $$ = lappend( $$, makeString(")"));
                }
            | default_expr Op default_expr
                {
                    if (!strcmp("<=", $2) || !strcmp(">=", $2))
                        elog(ERROR,"boolean expressions not supported in DEFAULT");
                    $$ = nconc( $1, lcons( makeString( $2), $3));
                }
            | Op default_expr
                {    $$ = lcons( makeString( $1), $2); }
            | default_expr Op
                {    $$ = lappend( $1, makeString( $2)); }
            /* XXX - thomas 1997-10-07 v6.2 function-specific code to be changed */
            | CURRENT_DATE
                {    $$ = lcons( makeString( "date( 'current'::datetime + '0 sec')"), NIL); }
            | CURRENT_TIME
                {    $$ = lcons( makeString( "'now'::time"), NIL); }
            | CURRENT_TIME '(' Iconst ')'
                {
                    if ($3 != 0)
                        elog(NOTICE,"CURRENT_TIME(%d) precision not implemented; zero used instead",$3);
                    $$ = lcons( makeString( "'now'::time"), NIL);
                }
            | CURRENT_TIMESTAMP
                {    $$ = lcons( makeString( "now()"), NIL); }
            | CURRENT_TIMESTAMP '(' Iconst ')'
                {
                    if ($3 != 0)
                        elog(NOTICE,"CURRENT_TIMESTAMP(%d) precision not implemented; zero used instead",$3);
                    $$ = lcons( makeString( "now()"), NIL);
                }
            | CURRENT_USER
                {    $$ = lcons( makeString( "CURRENT_USER"), NIL); }
        ;

/* ConstraintElem specifies constraint syntax which is not embedded into
 *  a column definition. ColConstraintElem specifies the embedded form.
 * - thomas 1997-12-03
 */
TableConstraint:  CONSTRAINT name ConstraintElem
                {
                        Constraint *n = (Constraint *)$3;
                        n->name = fmtId($2);
                        $$ = $3;
                }
        | ConstraintElem
                { $$ = $1; }
        ;

ConstraintElem:  CHECK '(' constraint_expr ')'
                {
                    Constraint *n = makeNode(Constraint);
                    n->contype = CONSTR_CHECK;
                    n->name = NULL;
                    n->def = FlattenStringList($3);
                    $$ = (Node *)n;
                }
        | UNIQUE '(' columnList ')'
                {
                    Constraint *n = makeNode(Constraint);
                    n->contype = CONSTR_UNIQUE;
                    n->name = NULL;
                    n->def = NULL;
                    n->keys = $3;
                    $$ = (Node *)n;
                }
        | PRIMARY KEY '(' columnList ')'
                {
                    Constraint *n = makeNode(Constraint);
                    n->contype = CONSTR_PRIMARY;
                    n->name = NULL;
                    n->def = NULL;
                    n->keys = $4;
                    $$ = (Node *)n;
                }
        | FOREIGN KEY '(' columnList ')' REFERENCES ColId opt_column_list key_match key_actions
                {    elog(NOTICE,"CREATE TABLE/FOREIGN KEY clause ignored; not yet implemented"); }
        ;

constraint_list:  constraint_list ',' constraint_expr
                {
                    $$ = lappend($1,makeString(","));
                    $$ = nconc($$, $3);
                }
            | constraint_expr
                {
                    $$ = $1;
                }
        ;

constraint_expr:  AexprConst
                {    $$ = makeConstantList((A_Const *) $1); }
            | NULL_P
                {    $$ = lcons( makeString("NULL"), NIL); }
            | ColId
                {
                    $$ = lcons( makeString(fmtId($1)), NIL);
                }
            | '-' constraint_expr %prec UMINUS
                {    $$ = lcons( makeString( "-"), $2); }
            | constraint_expr '+' constraint_expr
                {    $$ = nconc( $1, lcons( makeString( "+"), $3)); }
            | constraint_expr '-' constraint_expr
                {    $$ = nconc( $1, lcons( makeString( "-"), $3)); }
            | constraint_expr '/' constraint_expr
                {    $$ = nconc( $1, lcons( makeString( "/"), $3)); }
            | constraint_expr '*' constraint_expr
                {    $$ = nconc( $1, lcons( makeString( "*"), $3)); }
            | constraint_expr '=' constraint_expr
                {    $$ = nconc( $1, lcons( makeString( "="), $3)); }
            | constraint_expr '<' constraint_expr
                {    $$ = nconc( $1, lcons( makeString( "<"), $3)); }
            | constraint_expr '>' constraint_expr
                {    $$ = nconc( $1, lcons( makeString( ">"), $3)); }
            | ':' constraint_expr
                {    $$ = lcons( makeString( ":"), $2); }
            | ';' constraint_expr
                {    $$ = lcons( makeString( ";"), $2); }
            | '|' constraint_expr
                {    $$ = lcons( makeString( "|"), $2); }
            | constraint_expr TYPECAST Typename
                {
                    $3->name = fmtId($3->name);
                    $$ = nconc( lcons( makeString( "CAST"), $1), makeList( makeString("AS"), $3, -1));
                }
            | CAST constraint_expr AS Typename
                {
                    $4->name = fmtId($4->name);
                    $$ = nconc( lcons( makeString( "CAST"), $2), makeList( makeString("AS"), $4, -1));
                }
            | '(' constraint_expr ')'
                {    $$ = lappend( lcons( makeString( "("), $2), makeString( ")")); }
            | name '(' ')'
                {
                    $$ = makeList( makeString($1), makeString("("), -1);
                    $$ = lappend( $$, makeString(")"));
                }
            | name '(' constraint_list ')'
                {
                    $$ = makeList( makeString($1), makeString("("), -1);
                    $$ = nconc( $$, $3);
                    $$ = lappend( $$, makeString(")"));
                }
            | constraint_expr Op constraint_expr
                {    $$ = nconc( $1, lcons( makeString( $2), $3)); }
            | constraint_expr AND constraint_expr
                {    $$ = nconc( $1, lcons( makeString( "AND"), $3)); }
            | constraint_expr OR constraint_expr
                {    $$ = nconc( $1, lcons( makeString( "OR"), $3)); }
            | NOT constraint_expr
                {    $$ = lcons( makeString( "NOT"), $2); }
            | Op constraint_expr
                {    $$ = lcons( makeString( $1), $2); }
            | constraint_expr Op
                {    $$ = lappend( $1, makeString( $2)); }
            | constraint_expr ISNULL
                {    $$ = lappend( $1, makeString( "IS NULL")); }
            | constraint_expr IS NULL_P
                {    $$ = lappend( $1, makeString( "IS NULL")); }
            | constraint_expr NOTNULL
                {    $$ = lappend( $1, makeString( "IS NOT NULL")); }
            | constraint_expr IS NOT NULL_P
                {    $$ = lappend( $1, makeString( "IS NOT NULL")); }
            | constraint_expr IS TRUE_P
                {    $$ = lappend( $1, makeString( "IS TRUE")); }
            | constraint_expr IS FALSE_P
                {    $$ = lappend( $1, makeString( "IS FALSE")); }
            | constraint_expr IS NOT TRUE_P
                {    $$ = lappend( $1, makeString( "IS NOT TRUE")); }
            | constraint_expr IS NOT FALSE_P
                {    $$ = lappend( $1, makeString( "IS NOT FALSE")); }
        ;

key_match:  MATCH FULL                    { $$ = NULL; }
        | MATCH PARTIAL                    { $$ = NULL; }
        | /*EMPTY*/                        { $$ = NULL; }
        ;

key_actions:  key_action key_action        { $$ = NIL; }
        | key_action                    { $$ = NIL; }
        | /*EMPTY*/                        { $$ = NIL; }
        ;

key_action:  ON DELETE key_reference    { $$ = NIL; }
        | ON UPDATE key_reference        { $$ = NIL; }
        ;

key_reference:  NO ACTION                { $$ = NULL; }
        | CASCADE                        { $$ = NULL; }
        | SET DEFAULT                    { $$ = NULL; }
        | SET NULL_P                    { $$ = NULL; }
        ;

OptInherit:  INHERITS '(' relation_name_list ')'        { $$ = $3; }
        | /*EMPTY*/                                        { $$ = NIL; }
        ;

/*
 *    "ARCHIVE" keyword was removed in 6.3, but we keep it for now
 *  so people can upgrade with old pg_dump scripts. - momjian 1997-11-20(?)
 */
OptArchiveType:  ARCHIVE '=' NONE                        { }
        | /*EMPTY*/                                        { }
        ;

CreateAsStmt:  CREATE TABLE relation_name OptCreateAs AS SubSelect
                {
                    SelectStmt *n = (SelectStmt *)$6;
                    if ($4 != NIL)
                        mapTargetColumns($4, n->targetList);
                    n->into = $3;
                    $$ = (Node *)n;
                }
        ;

OptCreateAs:  '(' CreateAsList ')'                { $$ = $2; }
            | /*EMPTY*/                            { $$ = NULL; }
        ;

CreateAsList:  CreateAsList ',' CreateAsElement    { $$ = lappend($1, $3); }
            | CreateAsElement                    { $$ = lcons($1, NIL); }
        ;

CreateAsElement:  ColId
                {
                    ColumnDef *n = makeNode(ColumnDef);
                    n->colname = $1;
                    n->typename = NULL;
                    n->defval = NULL;
                    n->is_not_null = FALSE;
                    n->constraints = NULL;
                    $$ = (Node *)n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY :
 *                CREATE SEQUENCE seqname
 *
 *****************************************************************************/

CreateSeqStmt:    CREATE SEQUENCE relation_name OptSeqList
                {
                    CreateSeqStmt *n = makeNode(CreateSeqStmt);
                    n->seqname = $3;
                    n->options = $4;
                    $$ = (Node *)n;
                }
        ;

OptSeqList:
                OptSeqList OptSeqElem
                { $$ = lappend($1, $2); }
        |        { $$ = NIL; }
        ;

OptSeqElem:        IDENT NumConst
                {
                    $$ = makeNode(DefElem);
                    $$->defname = $1;
                    $$->arg = (Node *)$2;
                }
        |        IDENT
                {
                    $$ = makeNode(DefElem);
                    $$->defname = $1;
                    $$->arg = (Node *)NULL;
                }
        ;

/*****************************************************************************
 *
 *        QUERIES :
 *                CREATE PROCEDURAL LANGUAGE ...
 *                DROP PROCEDURAL LANGUAGE ...
 *
 *****************************************************************************/

CreatePLangStmt:  CREATE PLangTrusted PROCEDURAL LANGUAGE Sconst
            HANDLER def_name LANCOMPILER Sconst
            {
                CreatePLangStmt *n = makeNode(CreatePLangStmt);
                n->plname = $5;
                n->plhandler = $7;
                n->plcompiler = $9;
                n->pltrusted = $2;
                $$ = (Node *)n;
            }
        ;

PLangTrusted:        TRUSTED { $$ = TRUE; }
            |    { $$ = FALSE; }

DropPLangStmt:  DROP PROCEDURAL LANGUAGE Sconst
            {
                DropPLangStmt *n = makeNode(DropPLangStmt);
                n->plname = $4;
                $$ = (Node *)n;
            }
        ;

/*****************************************************************************
 *
 *        QUERIES :
 *                CREATE TRIGGER ...
 *                DROP TRIGGER ...
 *
 *****************************************************************************/

CreateTrigStmt:  CREATE TRIGGER name TriggerActionTime TriggerEvents ON
                relation_name TriggerForSpec EXECUTE PROCEDURE
                name '(' TriggerFuncArgs ')'
                {
                    CreateTrigStmt *n = makeNode(CreateTrigStmt);
                    n->trigname = $3;
                    n->relname = $7;
                    n->funcname = $11;
                    n->args = $13;
                    n->before = $4;
                    n->row = $8;
                    memcpy (n->actions, $5, 4);
                    $$ = (Node *)n;
                }
        ;

TriggerActionTime:        BEFORE    { $$ = TRUE; }
                |        AFTER    { $$ = FALSE; }
        ;

TriggerEvents:    TriggerOneEvent
                    {
                            char *e = palloc (4);
                            e[0] = $1; e[1] = 0; $$ = e;
                    }
                | TriggerOneEvent OR TriggerOneEvent
                    {
                            char *e = palloc (4);
                            e[0] = $1; e[1] = $3; e[2] = 0; $$ = e;
                    }
                | TriggerOneEvent OR TriggerOneEvent OR TriggerOneEvent
                    {
                            char *e = palloc (4);
                            e[0] = $1; e[1] = $3; e[2] = $5; e[3] = 0;
                            $$ = e;
                    }
        ;

TriggerOneEvent:        INSERT    { $$ = 'i'; }
                |        DELETE    { $$ = 'd'; }
                |        UPDATE    { $$ = 'u'; }
        ;

TriggerForSpec:  FOR name name
                {
                        if ( strcmp ($2, "each") != 0 )
                                elog(ERROR,"parser: syntax error near %s",$2);
                        if ( strcmp ($3, "row") == 0 )
                                $$ = TRUE;
                        else if ( strcmp ($3, "statement") == 0 )
                                $$ = FALSE;
                        else
                                elog(ERROR,"parser: syntax error near %s",$3);
                }
        ;

TriggerFuncArgs:  TriggerFuncArg
                { $$ = lcons($1, NIL); }
        | TriggerFuncArgs ',' TriggerFuncArg
                { $$ = lappend($1, $3); }
        | /*EMPTY*/
                { $$ = NIL; }
        ;

TriggerFuncArg:  ICONST
                    {
                        char *s = (char *) palloc (256);
                        sprintf (s, "%d", $1);
                        $$ = s;
                    }
                | FCONST
                    {
                        char *s = (char *) palloc (256);
                        sprintf (s, "%g", $1);
                        $$ = s;
                    }
                | Sconst        {  $$ = $1; }
                | IDENT            {  $$ = $1; }
        ;

DropTrigStmt:    DROP TRIGGER name ON relation_name
                {
                    DropTrigStmt *n = makeNode(DropTrigStmt);
                    n->trigname = $3;
                    n->relname = $5;
                    $$ = (Node *) n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY :
 *                define (type,operator,aggregate)
 *
 *****************************************************************************/

DefineStmt:  CREATE def_type def_rest
                {
                    $3->defType = $2;
                    $$ = (Node *)$3;
                }
        ;

def_rest:  def_name definition
                {
                    $$ = makeNode(DefineStmt);
                    $$->defname = $1;
                    $$->definition = $2;
                }
        ;

def_type:  OPERATOR                            { $$ = OPERATOR; }
        | TYPE_P                            { $$ = TYPE_P; }
        | AGGREGATE                            { $$ = AGGREGATE; }
        ;

def_name:  PROCEDURE                        { $$ = "procedure"; }
        | JOIN                                { $$ = "join"; }
        | ColId                                { $$ = $1; }
        | MathOp                            { $$ = $1; }
        | Op                                { $$ = $1; }
        ;

definition:  '(' def_list ')'                { $$ = $2; }
        ;

def_list:  def_elem                            { $$ = lcons($1, NIL); }
        | def_list ',' def_elem                { $$ = lappend($1, $3); }
        ;

def_elem:  def_name '=' def_arg
                {
                    $$ = makeNode(DefElem);
                    $$->defname = $1;
                    $$->arg = (Node *)$3;
                }
        | def_name
                {
                    $$ = makeNode(DefElem);
                    $$->defname = $1;
                    $$->arg = (Node *)NULL;
                }
        | DEFAULT '=' def_arg
                {
                    $$ = makeNode(DefElem);
                    $$->defname = "default";
                    $$->arg = (Node *)$3;
                }
        ;

def_arg:  ColId                            {  $$ = (Node *)makeString($1); }
        | all_Op                        {  $$ = (Node *)makeString($1); }
        | NumConst                        {  $$ = (Node *)$1; /* already a Value */ }
        | Sconst                        {  $$ = (Node *)makeString($1); }
        | SETOF ColId
                {
                    TypeName *n = makeNode(TypeName);
                    n->name = $2;
                    n->setof = TRUE;
                    n->arrayBounds = NULL;
                    $$ = (Node *)n;
                }
        | DOUBLE                        {  $$ = (Node *)makeString("double"); }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                destroy <relname1> [, <relname2> .. <relnameN> ]
 *
 *****************************************************************************/

DestroyStmt:  DROP TABLE relation_name_list
                {
                    DestroyStmt *n = makeNode(DestroyStmt);
                    n->relNames = $3;
                    n->sequence = FALSE;
                    $$ = (Node *)n;
                }
        | DROP SEQUENCE relation_name_list
                {
                    DestroyStmt *n = makeNode(DestroyStmt);
                    n->relNames = $3;
                    n->sequence = TRUE;
                    $$ = (Node *)n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *            fetch/move [forward | backward] [number | all ] [ in <portalname> ]
 *
 *****************************************************************************/

FetchStmt:    FETCH opt_direction fetch_how_many opt_portal_name
                {
                    FetchStmt *n = makeNode(FetchStmt);
                    n->direction = $2;
                    n->howMany = $3;
                    n->portalname = $4;
                    n->ismove = false;
                    $$ = (Node *)n;
                }
        |    MOVE opt_direction fetch_how_many opt_portal_name
                {
                    FetchStmt *n = makeNode(FetchStmt);
                    n->direction = $2;
                    n->howMany = $3;
                    n->portalname = $4;
                    n->ismove = TRUE;
                    $$ = (Node *)n;
                }
        ;

opt_direction:    FORWARD                            { $$ = FORWARD; }
        | BACKWARD                                { $$ = BACKWARD; }
        | /*EMPTY*/                                { $$ = FORWARD; /* default */ }
        ;

fetch_how_many:  Iconst
               { $$ = $1;
                 if ($1 <= 0) elog(ERROR,"Please specify nonnegative count for fetch"); }
        | ALL                            { $$ = 0; /* 0 means fetch all tuples*/ }
        | /*EMPTY*/                        { $$ = 1; /*default*/ }
        ;

opt_portal_name:  IN name                { $$ = $2; }
        | /*EMPTY*/                        { $$ = NULL; }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                GRANT [privileges] ON [relation_name_list] TO [GROUP] grantee
 *
 *****************************************************************************/

GrantStmt:  GRANT privileges ON relation_name_list TO grantee opt_with_grant
                {
                    $$ = (Node*)makeAclStmt($2,$4,$6,'+');
                    free($2);
                    free($6);
                }
        ;

privileges:  ALL PRIVILEGES
                {
                 $$ = aclmakepriv("rwaR",0);
                }
        | ALL
                {
                 $$ = aclmakepriv("rwaR",0);
                }
        | operation_commalist
                {
                 $$ = $1;
                }
        ;

operation_commalist:  operation
                {
                        $$ = aclmakepriv("",$1);
                }
        | operation_commalist ',' operation
                {
                        $$ = aclmakepriv($1,$3);
                        free($1);
                }
        ;

operation:  SELECT
                {
                        $$ = ACL_MODE_RD_CHR;
                }
        | INSERT
                {
                        $$ = ACL_MODE_AP_CHR;
                }
        | UPDATE
                {
                        $$ = ACL_MODE_WR_CHR;
                }
        | DELETE
                {
                        $$ = ACL_MODE_WR_CHR;
                }
        | RULE
                {
                        $$ = ACL_MODE_RU_CHR;
                }
        ;

grantee:  PUBLIC
                {
                        $$ = aclmakeuser("A","");
                }
        | GROUP ColId
                {
                        $$ = aclmakeuser("G",$2);
                }
        | ColId
                {
                        $$ = aclmakeuser("U",$1);
                }
        ;

opt_with_grant:  WITH GRANT OPTION
                {
                    yyerror("WITH GRANT OPTION is not supported.  Only relation owners can set privileges");
                 }
        | /*EMPTY*/
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                REVOKE [privileges] ON [relation_name] FROM [user]
 *
 *****************************************************************************/

RevokeStmt:  REVOKE privileges ON relation_name_list FROM grantee
                {
                    $$ = (Node*)makeAclStmt($2,$4,$6,'-');
                    free($2);
                    free($6);
                }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                create index <indexname> on <relname>
 *                  using <access> "(" (<col> with <op>)+ ")" [with
 *                  <target_list>]
 *
 *    [where <qual>] is not supported anymore
 *****************************************************************************/

IndexStmt:    CREATE index_opt_unique INDEX index_name ON relation_name
            access_method_clause '(' index_params ')' opt_with
                {
                    /* should check that access_method is valid,
                       etc ... but doesn't */
                    IndexStmt *n = makeNode(IndexStmt);
                    n->unique = $2;
                    n->idxname = $4;
                    n->relname = $6;
                    n->accessMethod = $7;
                    n->indexParams = $9;
                    n->withClause = $11;
                    n->whereClause = NULL;
                    $$ = (Node *)n;
                }
        ;

index_opt_unique:  UNIQUE                        { $$ = TRUE; }
        | /*EMPTY*/                                { $$ = FALSE; }
        ;

access_method_clause:  USING access_method        { $$ = $2; }
        | /*EMPTY*/                                { $$ = "btree"; }
        ;

index_params:  index_list                        { $$ = $1; }
        | func_index                            { $$ = lcons($1,NIL); }
        ;

index_list:  index_list ',' index_elem            { $$ = lappend($1, $3); }
        | index_elem                            { $$ = lcons($1, NIL); }
        ;

func_index:  name '(' name_list ')' opt_type opt_class
                {
                    $$ = makeNode(IndexElem);
                    $$->name = $1;
                    $$->args = $3;
                    $$->class = $6;
                    $$->tname = $5;
                }
          ;

index_elem:  attr_name opt_type opt_class
                {
                    $$ = makeNode(IndexElem);
                    $$->name = $1;
                    $$->args = NIL;
                    $$->class = $3;
                    $$->tname = $2;
                }
        ;

opt_type:  ':' Typename                            { $$ = $2; }
        | FOR Typename                            { $$ = $2; }
        | /*EMPTY*/                                { $$ = NULL; }
        ;

/* opt_class "WITH class" conflicts with preceeding opt_type
 *  for Typename of "TIMESTAMP WITH TIME ZONE"
 * So, remove "WITH class" from the syntax. OK??
 * - thomas 1997-10-12
 *        | WITH class                            { $$ = $2; }
 */
opt_class:  class                                { $$ = $1; }
        | USING class                            { $$ = $2; }
        | /*EMPTY*/                                { $$ = NULL; }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                extend index <indexname> [where <qual>]
 *
 *****************************************************************************/

ExtendStmt:  EXTEND INDEX index_name where_clause
                {
                    ExtendStmt *n = makeNode(ExtendStmt);
                    n->idxname = $3;
                    n->whereClause = $4;
                    $$ = (Node *)n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                execute recipe <recipeName>
 *
 *****************************************************************************/

RecipeStmt:  EXECUTE RECIPE recipe_name
                {
                    RecipeStmt *n;
                    if (!IsTransactionBlock())
                        elog(ERROR,"EXECUTE RECIPE may only be used in begin/end transaction blocks");

                    n = makeNode(RecipeStmt);
                    n->recipeName = $3;
                    $$ = (Node *)n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                define function <fname>
 *                       (language = <lang>, returntype = <typename>
 *                        [, arch_pct = <percentage | pre-defined>]
 *                        [, disk_pct = <percentage | pre-defined>]
 *                        [, byte_pct = <percentage | pre-defined>]
 *                        [, perbyte_cpu = <int | pre-defined>]
 *                        [, percall_cpu = <int | pre-defined>]
 *                        [, iscachable])
 *                        [arg is (<type-1> { , <type-n>})]
 *                        as <filename or code in language as appropriate>
 *
 *****************************************************************************/

ProcedureStmt:    CREATE FUNCTION def_name def_args
             RETURNS def_arg opt_with AS Sconst LANGUAGE Sconst
                {
                    ProcedureStmt *n = makeNode(ProcedureStmt);
                    n->funcname = $3;
                    n->defArgs = $4;
                    n->returnType = (Node *)$6;
                    n->withClause = $7;
                    n->as = $9;
                    n->language = $11;
                    $$ = (Node *)n;
                };

opt_with:  WITH definition                        { $$ = $2; }
        | /*EMPTY*/                            { $$ = NIL; }
        ;

def_args:  '(' def_name_list ')'                { $$ = $2; }
        | '(' ')'                                { $$ = NIL; }
        ;

def_name_list:    name_list;

/*****************************************************************************
 *
 *        QUERY:
 *
 *        remove function <funcname>
 *                (REMOVE FUNCTION "funcname" (arg1, arg2, ...))
 *        remove aggregate <aggname>
 *                (REMOVE AGGREGATE "aggname" "aggtype")
 *        remove operator <opname>
 *                (REMOVE OPERATOR "opname" (leftoperand_typ rightoperand_typ))
 *        remove type <typename>
 *                (REMOVE TYPE "typename")
 *        remove rule <rulename>
 *                (REMOVE RULE "rulename")
 *
 *****************************************************************************/

RemoveStmt:  DROP remove_type name
                {
                    RemoveStmt *n = makeNode(RemoveStmt);
                    n->removeType = $2;
                    n->name = $3;
                    $$ = (Node *)n;
                }
        ;

remove_type:  TYPE_P                            {  $$ = TYPE_P; }
        | INDEX                                    {  $$ = INDEX; }
        | RULE                                    {  $$ = RULE; }
        | VIEW                                    {  $$ = VIEW; }
        ;

RemoveAggrStmt:  DROP AGGREGATE name aggr_argtype
                {
                        RemoveAggrStmt *n = makeNode(RemoveAggrStmt);
                        n->aggname = $3;
                        n->aggtype = $4;
                        $$ = (Node *)n;
                }
        ;

aggr_argtype:  name                                { $$ = $1; }
        | '*'                                    { $$ = NULL; }
        ;

RemoveFuncStmt:  DROP FUNCTION name '(' func_argtypes ')'
                {
                    RemoveFuncStmt *n = makeNode(RemoveFuncStmt);
                    n->funcname = $3;
                    n->args = $5;
                    $$ = (Node *)n;
                }
        ;

func_argtypes:    name_list                        { $$ = $1; }
        | /*EMPTY*/                                { $$ = NIL; }
        ;

RemoveOperStmt:  DROP OPERATOR all_Op '(' oper_argtypes ')'
                {
                    RemoveOperStmt *n = makeNode(RemoveOperStmt);
                    n->opname = $3;
                    n->args = $5;
                    $$ = (Node *)n;
                }
        ;

all_Op:  Op | MathOp;

MathOp:    '+'                { $$ = "+"; }
        | '-'            { $$ = "-"; }
        | '*'            { $$ = "*"; }
        | '/'            { $$ = "/"; }
        | '<'            { $$ = "<"; }
        | '>'            { $$ = ">"; }
        | '='            { $$ = "="; }
        ;

oper_argtypes:    name
                {
                   elog(ERROR,"parser: argument type missing (use NONE for unary operators)");
                }
        | name ',' name
                { $$ = makeList(makeString($1), makeString($3), -1); }
        | NONE ',' name            /* left unary */
                { $$ = makeList(NULL, makeString($3), -1); }
        | name ',' NONE            /* right unary */
                { $$ = makeList(makeString($1), NULL, -1); }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                rename <attrname1> in <relname> [*] to <attrname2>
 *                rename <relname1> to <relname2>
 *
 *****************************************************************************/

RenameStmt:  ALTER TABLE relation_name opt_inh_star
                  RENAME opt_column opt_name TO name
                {
                    RenameStmt *n = makeNode(RenameStmt);
                    n->relname = $3;
                    n->inh = $4;
                    n->column = $7;
                    n->newname = $9;
                    $$ = (Node *)n;
                }
        ;

opt_name:  name                            { $$ = $1; }
        | /*EMPTY*/                        { $$ = NULL; }
        ;

opt_column:  COLUMN                        { $$ = COLUMN; }
        | /*EMPTY*/                        { $$ = 0; }
        ;


/*****************************************************************************
 *
 *        QUERY:    Define Rewrite Rule , Define Tuple Rule
 *                Define Rule <old rules >
 *
 *        only rewrite rule is supported -- ay 9/94
 *
 *****************************************************************************/

RuleStmt:  CREATE RULE name AS
           { QueryIsRule=TRUE; }
           ON event TO event_object where_clause
           DO opt_instead OptStmtList
                {
                    RuleStmt *n = makeNode(RuleStmt);
                    n->rulename = $3;
                    n->event = $7;
                    n->object = $9;
                    n->whereClause = $10;
                    n->instead = $12;
                    n->actions = $13;
                    $$ = (Node *)n;
                }
        ;

OptStmtList:  NOTHING                    { $$ = NIL; }
        | OptimizableStmt                { $$ = lcons($1, NIL); }
        | '[' OptStmtBlock ']'            { $$ = $2; }
        ;

OptStmtBlock:  OptStmtMulti
                {  $$ = $1; }
        | OptimizableStmt
                { $$ = lcons($1, NIL); }
        ;

OptStmtMulti:  OptStmtMulti OptimizableStmt ';'
                {  $$ = lappend($1, $2); }
        | OptStmtMulti OptimizableStmt
                {  $$ = lappend($1, $2); }
        | OptimizableStmt ';'
                { $$ = lcons($1, NIL); }
        ;

event_object:  relation_name '.' attr_name
                {
                    $$ = makeNode(Attr);
                    $$->relname = $1;
                    $$->paramNo = NULL;
                    $$->attrs = lcons(makeString($3), NIL);
                    $$->indirection = NIL;
                }
        | relation_name
                {
                    $$ = makeNode(Attr);
                    $$->relname = $1;
                    $$->paramNo = NULL;
                    $$->attrs = NIL;
                    $$->indirection = NIL;
                }
        ;

/* change me to select, update, etc. some day */
event:    SELECT                            { $$ = CMD_SELECT; }
        | UPDATE                        { $$ = CMD_UPDATE; }
        | DELETE                        { $$ = CMD_DELETE; }
        | INSERT                        { $$ = CMD_INSERT; }
         ;

opt_instead:  INSTEAD                    { $$ = TRUE; }
        | /*EMPTY*/                    { $$ = FALSE; }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                NOTIFY <relation_name>    can appear both in rule bodies and
 *                as a query-level command
 *
 *****************************************************************************/

NotifyStmt:  NOTIFY relation_name
                {
                    NotifyStmt *n = makeNode(NotifyStmt);
                    n->relname = $2;
                    $$ = (Node *)n;
                }
        ;

ListenStmt:  LISTEN relation_name
                {
                    ListenStmt *n = makeNode(ListenStmt);
                    n->relname = $2;
                    $$ = (Node *)n;
                }
;


/*****************************************************************************
 *
 *        Transactions:
 *
 *        abort transaction
 *                (ABORT)
 *        begin transaction
 *                (BEGIN)
 *        end transaction
 *                (END)
 *
 *****************************************************************************/

TransactionStmt:  ABORT_TRANS TRANSACTION
                {
                    TransactionStmt *n = makeNode(TransactionStmt);
                    n->command = ABORT_TRANS;
                    $$ = (Node *)n;
                }
        | BEGIN_TRANS TRANSACTION
                {
                    TransactionStmt *n = makeNode(TransactionStmt);
                    n->command = BEGIN_TRANS;
                    $$ = (Node *)n;
                }
        | BEGIN_TRANS WORK
                {
                    TransactionStmt *n = makeNode(TransactionStmt);
                    n->command = BEGIN_TRANS;
                    $$ = (Node *)n;
                }
        | COMMIT WORK
                {
                    TransactionStmt *n = makeNode(TransactionStmt);
                    n->command = END_TRANS;
                    $$ = (Node *)n;
                }
        | END_TRANS TRANSACTION
                {
                    TransactionStmt *n = makeNode(TransactionStmt);
                    n->command = END_TRANS;
                    $$ = (Node *)n;
                }
        | ROLLBACK WORK
                {
                    TransactionStmt *n = makeNode(TransactionStmt);
                    n->command = ABORT_TRANS;
                    $$ = (Node *)n;
                }

        | ABORT_TRANS
                {
                    TransactionStmt *n = makeNode(TransactionStmt);
                    n->command = ABORT_TRANS;
                    $$ = (Node *)n;
                }
        | BEGIN_TRANS
                {
                    TransactionStmt *n = makeNode(TransactionStmt);
                    n->command = BEGIN_TRANS;
                    $$ = (Node *)n;
                }
        | COMMIT
                {
                    TransactionStmt *n = makeNode(TransactionStmt);
                    n->command = END_TRANS;
                    $$ = (Node *)n;
                }

        | END_TRANS
                {
                    TransactionStmt *n = makeNode(TransactionStmt);
                    n->command = END_TRANS;
                    $$ = (Node *)n;
                }
        | ROLLBACK
                {
                    TransactionStmt *n = makeNode(TransactionStmt);
                    n->command = ABORT_TRANS;
                    $$ = (Node *)n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                define view <viewname> '('target-list ')' [where <quals> ]
 *
 *****************************************************************************/

ViewStmt:  CREATE VIEW name AS SelectStmt
                {
                    ViewStmt *n = makeNode(ViewStmt);
                    n->viewname = $3;
                    n->query = (Query *)$5;
                    if (((SelectStmt *)n->query)->unionClause != NULL)
                        elog(ERROR,"Views on unions not implemented.");
                    $$ = (Node *)n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                load "filename"
 *
 *****************************************************************************/

LoadStmt:  LOAD file_name
                {
                    LoadStmt *n = makeNode(LoadStmt);
                    n->filename = $2;
                    $$ = (Node *)n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                createdb dbname
 *
 *****************************************************************************/

CreatedbStmt:  CREATE DATABASE database_name opt_database
                {
                    CreatedbStmt *n = makeNode(CreatedbStmt);
                    n->dbname = $3;
                    n->dbpath = $4;
                    $$ = (Node *)n;
                }
        ;

opt_database:  WITH LOCATION '=' location        { $$ = $4; }
        | /*EMPTY*/                                { $$ = NULL; }
        ;

location:  Sconst                                { $$ = $1; }
        | DEFAULT                                { $$ = NULL; }
        | /*EMPTY*/                                { $$ = NULL; }
        ;

/*****************************************************************************
 *
 *        QUERY:
 *                destroydb dbname
 *
 *****************************************************************************/

DestroydbStmt:    DROP DATABASE database_name
                {
                    DestroydbStmt *n = makeNode(DestroydbStmt);
                    n->dbname = $3;
                    $$ = (Node *)n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                cluster <index_name> on <relation_name>
 *
 *****************************************************************************/

ClusterStmt:  CLUSTER index_name ON relation_name
                {
                   ClusterStmt *n = makeNode(ClusterStmt);
                   n->relname = $4;
                   n->indexname = $2;
                   $$ = (Node*)n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                vacuum
 *
 *****************************************************************************/

VacuumStmt:  VACUUM opt_verbose opt_analyze
                {
                    VacuumStmt *n = makeNode(VacuumStmt);
                    n->verbose = $2;
                    n->analyze = $3;
                    n->vacrel = NULL;
                    n->va_spec = NIL;
                    $$ = (Node *)n;
                }
        | VACUUM opt_verbose opt_analyze relation_name opt_va_list
                {
                    VacuumStmt *n = makeNode(VacuumStmt);
                    n->verbose = $2;
                    n->analyze = $3;
                    n->vacrel = $4;
                    n->va_spec = $5;
                    if ( $5 != NIL && !$4 )
                        elog(ERROR,"parser: syntax error at or near \"(\"");
                    $$ = (Node *)n;
                }
        ;

opt_verbose:  VERBOSE                            { $$ = TRUE; }
        | /*EMPTY*/                                { $$ = FALSE; }
        ;

opt_analyze:  ANALYZE                            { $$ = TRUE; }
        | /*EMPTY*/                                { $$ = FALSE; }
        ;

opt_va_list:  '(' va_list ')'
                { $$ = $2; }
        | /* EMPTY */
                { $$ = NIL; }
        ;

va_list:  name
                { $$=lcons($1,NIL); }
        | va_list ',' name
                { $$=lappend($1,$3); }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                EXPLAIN query
 *
 *****************************************************************************/

ExplainStmt:  EXPLAIN opt_verbose OptimizableStmt
                {
                    ExplainStmt *n = makeNode(ExplainStmt);
                    n->verbose = $2;
                    n->query = (Query*)$3;
                    $$ = (Node *)n;
                }
        ;


/*****************************************************************************
 *                                                                             *
 *        Optimizable Stmts:                                                     *
 *                                                                             *
 *        one of the five queries processed by the planner                     *
 *                                                                             *
 *        [ultimately] produces query-trees as specified                         *
 *        in the query-spec document in ~postgres/ref                             *
 *                                                                             *
 *****************************************************************************/

OptimizableStmt:  SelectStmt
        | CursorStmt
        | UpdateStmt
        | InsertStmt
        | NotifyStmt
        | DeleteStmt                    /* by default all are $$=$1 */
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                INSERT STATEMENTS
 *
 *****************************************************************************/

InsertStmt:  INSERT INTO relation_name opt_column_list insert_rest
                {
                    $5->relname = $3;
                    $5->cols = $4;
                    $$ = (Node *)$5;
                }
        ;

insert_rest:  VALUES '(' res_target_list2 ')'
                {
                    $$ = makeNode(InsertStmt);
                    $$->targetList = $3;
                    $$->fromClause = NIL;
                    $$->whereClause = NULL;
                }
        | SELECT res_target_list2 from_clause where_clause
                {
                    $$ = makeNode(InsertStmt);
                    $$->targetList = $2;
                    $$->fromClause = $3;
                    $$->whereClause = $4;
                }
        ;

opt_column_list:  '(' columnList ')'            { $$ = $2; }
        | /*EMPTY*/                                { $$ = NIL; }
        ;

columnList:
          columnList ',' columnElem
                { $$ = lappend($1, $3); }
        | columnElem
                { $$ = lcons($1, NIL); }
        ;

columnElem:  ColId opt_indirection
                {
                    Ident *id = makeNode(Ident);
                    id->name = $1;
                    id->indirection = $2;
                    $$ = (Node *)id;
                }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                DELETE STATEMENTS
 *
 *****************************************************************************/

DeleteStmt:  DELETE FROM relation_name
             where_clause
                {
                    DeleteStmt *n = makeNode(DeleteStmt);
                    n->relname = $3;
                    n->whereClause = $4;
                    $$ = (Node *)n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                UpdateStmt (UPDATE)
 *
 *****************************************************************************/

UpdateStmt:  UPDATE relation_name
              SET res_target_list
              from_clause
              where_clause
                {
                    UpdateStmt *n = makeNode(UpdateStmt);
                    n->relname = $2;
                    n->targetList = $4;
                    n->fromClause = $5;
                    n->whereClause = $6;
                    $$ = (Node *)n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                CURSOR STATEMENTS
 *
 *****************************************************************************/
CursorStmt:  DECLARE name opt_binary CURSOR FOR
              SELECT opt_unique res_target_list2
             from_clause where_clause
             group_clause having_clause
             union_clause sort_clause
                {
                    SelectStmt *n = makeNode(SelectStmt);

                    /* from PORTAL name */
                    /*
                     *    15 august 1991 -- since 3.0 postgres does locking
                     *    right, we discovered that portals were violating
                     *    locking protocol.  portal locks cannot span xacts.
                     *    as a short-term fix, we installed the check here.
                     *                            -- mao
                     */
                    if (!IsTransactionBlock())
                        elog(ERROR,"Named portals may only be used in begin/end transaction blocks");

                    n->portalname = $2;
                    n->binary = $3;
                    n->unique = $7;
                    n->targetList = $8;
                    n->fromClause = $9;
                    n->whereClause = $10;
                    n->groupClause = $11;
                    n->havingClause = $12;
                    n->unionClause = $13;
                    n->sortClause = $14;
                    $$ = (Node *)n;
                }
        ;


/*****************************************************************************
 *
 *        QUERY:
 *                SELECT STATEMENTS
 *
 *****************************************************************************/

SelectStmt:  SELECT opt_unique res_target_list2
             result from_clause where_clause
             group_clause having_clause
             union_clause sort_clause
                {
                    SelectStmt *n = makeNode(SelectStmt);
                    n->unique = $2;
                    n->targetList = $3;
                    n->into = $4;
                    n->fromClause = $5;
                    n->whereClause = $6;
                    n->groupClause = $7;
                    n->havingClause = $8;
                    n->unionClause = $9;
                    n->sortClause = $10;
                    $$ = (Node *)n;
                }
        ;

union_clause:  UNION opt_union select_list
                {
                    SelectStmt *n = (SelectStmt *)lfirst($3);
                    n->unionall = $2;
                    $$ = $3;
                }
        | /*EMPTY*/
                { $$ = NIL; }
        ;

select_list:  select_list UNION opt_union SubSelect
                {
                    SelectStmt *n = (SelectStmt *)$4;
                    n->unionall = $3;
                    $$ = lappend($1, $4);
                }
        | SubSelect
                { $$ = lcons($1, NIL); }
        ;

SubSelect:    SELECT opt_unique res_target_list2
             from_clause where_clause
             group_clause having_clause
                {
                    SelectStmt *n = makeNode(SelectStmt);
                    n->unique = $2;
                    n->unionall = FALSE;
                    n->targetList = $3;
                    n->fromClause = $4;
                    n->whereClause = $5;
                    n->groupClause = $6;
                    n->havingClause = $7;
                    $$ = (Node *)n;
                }
        ;

result:  INTO TABLE relation_name
                {    $$= $3; }
        | /*EMPTY*/
                {    $$ = NULL; }
        ;

opt_union:  ALL                                    { $$ = TRUE; }
        | /*EMPTY*/                                { $$ = FALSE; }
        ;

opt_unique:  DISTINCT                            { $$ = "*"; }
        | DISTINCT ON ColId                        { $$ = $3; }
        | ALL                                    { $$ = NULL; }
        | /*EMPTY*/                                { $$ = NULL; }
        ;

sort_clause:  ORDER BY sortby_list                { $$ = $3; }
        | /*EMPTY*/                                { $$ = NIL; }
        ;

sortby_list:  sortby                            { $$ = lcons($1, NIL); }
        | sortby_list ',' sortby                { $$ = lappend($1, $3); }
        ;

sortby:  ColId OptUseOp
                {
                    $$ = makeNode(SortGroupBy);
                    $$->resno = 0;
                    $$->range = NULL;
                    $$->name = $1;
                    $$->useOp = $2;
                }
        | ColId '.' ColId OptUseOp
                {
                    $$ = makeNode(SortGroupBy);
                    $$->resno = 0;
                    $$->range = $1;
                    $$->name = $3;
                    $$->useOp = $4;
                }
        | Iconst OptUseOp
                {
                    $$ = makeNode(SortGroupBy);
                    $$->resno = $1;
                    $$->range = NULL;
                    $$->name = NULL;
                    $$->useOp = $2;
                }
        ;

OptUseOp:  USING Op                                { $$ = $2; }
        | USING '<'                                { $$ = "<"; }
        | USING '>'                                { $$ = ">"; }
        | ASC                                    { $$ = "<"; }
        | DESC                                    { $$ = ">"; }
        | /*EMPTY*/                                { $$ = "<"; /*default*/ }
        ;

/*
 *    jimmy bell-style recursive queries aren't supported in the
 *    current system.
 *
 *    ...however, recursive addattr and rename supported.  make special
 *    cases for these.
 */
opt_inh_star:  '*'                                { $$ = TRUE; }
        | /*EMPTY*/                                { $$ = FALSE; }
        ;

relation_name_list:  name_list;

name_list:  name
                {    $$ = lcons(makeString($1),NIL); }
        | name_list ',' name
                {    $$ = lappend($1,makeString($3)); }
        ;

group_clause:  GROUP BY groupby_list            { $$ = $3; }
        | /*EMPTY*/                                { $$ = NIL; }
        ;

groupby_list:  groupby                            { $$ = lcons($1, NIL); }
        | groupby_list ',' groupby                { $$ = lappend($1, $3); }
        ;

groupby:  ColId
                {
                    $$ = makeNode(SortGroupBy);
                    $$->resno = 0;
                    $$->range = NULL;
                    $$->name = $1;
                    $$->useOp = NULL;
                }
        | ColId '.' ColId
                {
                    $$ = makeNode(SortGroupBy);
                    $$->resno = 0;
                    $$->range = $1;
                    $$->name = $3;
                    $$->useOp = NULL;
                }
        | Iconst
                {
                    $$ = makeNode(SortGroupBy);
                    $$->resno = $1;
                    $$->range = NULL;
                    $$->name = NULL;
                    $$->useOp = NULL;
                }
        ;

having_clause:  HAVING a_expr                    { $$ = $2; }
        | /*EMPTY*/                                { $$ = NULL; }
        ;


/*****************************************************************************
 *
 *    clauses common to all Optimizable Stmts:
 *        from_clause        -
 *        where_clause    -
 *
 *****************************************************************************/

from_clause:  FROM '(' relation_expr join_expr JOIN relation_expr join_spec ')'
                {
                    $$ = NIL;
                    elog(ERROR,"JOIN not yet implemented");
                }
        | FROM from_list                        { $$ = $2; }
        | /*EMPTY*/                                { $$ = NIL; }
        ;

from_list:    from_list ',' from_val
                { $$ = lappend($1, $3); }
        | from_val CROSS JOIN from_val
                { elog(ERROR,"CROSS JOIN not yet implemented"); }
        | from_val
                { $$ = lcons($1, NIL); }
        ;

from_val:  relation_expr AS ColLabel
                {
                    $$ = makeNode(RangeVar);
                    $$->relExpr = $1;
                    $$->name = $3;
                }
        | relation_expr ColId
                {
                    $$ = makeNode(RangeVar);
                    $$->relExpr = $1;
                    $$->name = $2;
                }
        | relation_expr
                {
                    $$ = makeNode(RangeVar);
                    $$->relExpr = $1;
                    $$->name = NULL;
                }
        ;

join_expr:  NATURAL join_expr                    { $$ = NULL; }
        | FULL join_outer
                { elog(ERROR,"FULL OUTER JOIN not yet implemented"); }
        | LEFT join_outer
                { elog(ERROR,"LEFT OUTER JOIN not yet implemented"); }
        | RIGHT join_outer
                { elog(ERROR,"RIGHT OUTER JOIN not yet implemented"); }
        | OUTER_P
                { elog(ERROR,"OUTER JOIN not yet implemented"); }
        | INNER_P
                { elog(ERROR,"INNER JOIN not yet implemented"); }
        | UNION
                { elog(ERROR,"UNION JOIN not yet implemented"); }
        | /*EMPTY*/
                { elog(ERROR,"INNER JOIN not yet implemented"); }
        ;

join_outer:  OUTER_P                            { $$ = NULL; }
        | /*EMPTY*/                                { $$ = NULL;  /* no qualifiers */ }
        ;

join_spec:    ON '(' a_expr ')'                    { $$ = NULL; }
        | USING '(' join_list ')'                { $$ = NULL; }
        | /*EMPTY*/                                { $$ = NULL;  /* no qualifiers */ }
        ;

join_list:  join_using                            { $$ = lcons($1, NIL); }
        | join_list ',' join_using                { $$ = lappend($1, $3); }
        ;

join_using:  ColId
                {
                    $$ = makeNode(SortGroupBy);
                    $$->resno = 0;
                    $$->range = NULL;
                    $$->name = $1;
                    $$->useOp = NULL;
                }
        | ColId '.' ColId
                {
                    $$ = makeNode(SortGroupBy);
                    $$->resno = 0;
                    $$->range = $1;
                    $$->name = $3;
                    $$->useOp = NULL;
                }
        | Iconst
                {
                    $$ = makeNode(SortGroupBy);
                    $$->resno = $1;
                    $$->range = NULL;
                    $$->name = NULL;
                    $$->useOp = NULL;
                }
        ;

where_clause:  WHERE a_expr                        { $$ = $2; }
        | /*EMPTY*/                                { $$ = NULL;  /* no qualifiers */ }
        ;

relation_expr:    relation_name
                {
                    /* normal relations */
                    $$ = makeNode(RelExpr);
                    $$->relname = $1;
                    $$->inh = FALSE;
                }
        | relation_name '*'                  %prec '='
                {
                    /* inheritance query */
                    $$ = makeNode(RelExpr);
                    $$->relname = $1;
                    $$->inh = TRUE;
                }

opt_array_bounds:  '[' ']' nest_array_bounds
                {  $$ = lcons(makeInteger(-1), $3); }
        | '[' Iconst ']' nest_array_bounds
                {  $$ = lcons(makeInteger($2), $4); }
        | /* EMPTY */
                {  $$ = NIL; }
        ;

nest_array_bounds:    '[' ']' nest_array_bounds
                {  $$ = lcons(makeInteger(-1), $3); }
        | '[' Iconst ']' nest_array_bounds
                {  $$ = lcons(makeInteger($2), $4); }
        | /*EMPTY*/
                {  $$ = NIL; }
        ;


/*****************************************************************************
 *
 *    Type syntax
 *        SQL92 introduces a large amount of type-specific syntax.
 *        Define individual clauses to handle these cases, and use
 *         the generic case to handle regular type-extensible Postgres syntax.
 *        - thomas 1997-10-10
 *
 *****************************************************************************/

Typename:  Array opt_array_bounds
                {
                    $$ = $1;
                    $$->arrayBounds = $2;

                    /* Is this the name of a complex type? If so, implement
                     * it as a set.
                     */
                    if (!strcmp(saved_relname, $$->name))
                        /* This attr is the same type as the relation
                         * being defined. The classic example: create
                         * emp(name=text,mgr=emp)
                         */
                        $$->setof = TRUE;
                    else if (typeTypeRelid(typenameType($$->name)) != InvalidOid)
                         /* (Eventually add in here that the set can only
                          * contain one element.)
                          */
                        $$->setof = TRUE;
                    else
                        $$->setof = FALSE;
                }
        | Character
        | SETOF Array
                {
                    $$ = $2;
                    $$->setof = TRUE;
                }
        ;

Array:  Generic
        | Datetime
        | Numeric
        ;

Generic:  generic
                {
                    $$ = makeNode(TypeName);
                    $$->name = xlateSqlType($1);
                }
        ;

generic:  Id                                    { $$ = $1; }
        | TYPE_P                                { $$ = xlateSqlType("type"); }
        | DOUBLE PRECISION                        { $$ = xlateSqlType("float8"); }
        ;

/* SQL92 numeric data types
 * Check FLOAT() precision limits assuming IEEE floating types.
 * Provide rudimentary DECIMAL() and NUMERIC() implementations
 *  by checking parameters and making sure they match what is possible with INTEGER.
 * - thomas 1997-09-18
 */
Numeric:  FLOAT opt_float
                {
                    $$ = makeNode(TypeName);
                    $$->name = xlateSqlType($2);
                }
        | DECIMAL opt_decimal
                {
                    $$ = makeNode(TypeName);
                    $$->name = xlateSqlType("integer");
                }
        | NUMERIC opt_numeric
                {
                    $$ = makeNode(TypeName);
                    $$->name = xlateSqlType("integer");
                }
        ;

opt_float:  '(' Iconst ')'
                {
                    if ($2 < 1)
                        elog(ERROR,"precision for FLOAT must be at least 1");
                    else if ($2 < 7)
                        $$ = xlateSqlType("float4");
                    else if ($2 < 16)
                        $$ = xlateSqlType("float8");
                    else
                        elog(ERROR,"precision for FLOAT must be less than 16");
                }
        | /*EMPTY*/
                {
                    $$ = xlateSqlType("float8");
                }
        ;

opt_numeric:  '(' Iconst ',' Iconst ')'
                {
                    if ($2 != 9)
                        elog(ERROR,"NUMERIC precision %d must be 9",$2);
                    if ($4 != 0)
                        elog(ERROR,"NUMERIC scale %d must be zero",$4);
                }
        | '(' Iconst ')'
                {
                    if ($2 != 9)
                        elog(ERROR,"NUMERIC precision %d must be 9",$2);
                }
        | /*EMPTY*/
                {
                    $$ = NULL;
                }
        ;

opt_decimal:  '(' Iconst ',' Iconst ')'
                {
                    if ($2 > 9)
                        elog(ERROR,"DECIMAL precision %d exceeds implementation limit of 9",$2);
                    if ($4 != 0)
                        elog(ERROR,"DECIMAL scale %d must be zero",$4);
                    $$ = NULL;
                }
        | '(' Iconst ')'
                {
                    if ($2 > 9)
                        elog(ERROR,"DECIMAL precision %d exceeds implementation limit of 9",$2);
                    $$ = NULL;
                }
        | /*EMPTY*/
                {
                    $$ = NULL;
                }
        ;

/* SQL92 character data types
 * The following implements CHAR() and VARCHAR().
 * We do it here instead of the 'Generic' production
 * because we don't want to allow arrays of VARCHAR().
 * I haven't thought about whether that will work or not.
 *                                - ay 6/95
 */
Character:  character '(' Iconst ')'
                {
                    $$ = makeNode(TypeName);
                    if (!strcasecmp($1, "char"))
                        $$->name = xlateSqlType("bpchar");
                    else if (!strcasecmp($1, "varchar"))
                        $$->name = xlateSqlType("varchar");
                    else
                        yyerror("parse error");
                    if ($3 < 1)
                        elog(ERROR,"length for '%s' type must be at least 1",$1);
                    else if ($3 > 4096)
                        /* we can store a char() of length up to the size
                         * of a page (8KB) - page headers and friends but
                         * just to be safe here...    - ay 6/95
                         * XXX note this hardcoded limit - thomas 1997-07-13
                         */
                        elog(ERROR,"length for type '%s' cannot exceed 4096",$1);

                    /* we actually implement this sort of like a varlen, so
                     * the first 4 bytes is the length. (the difference
                     * between this and "text" is that we blank-pad and
                     * truncate where necessary
                     */
                    $$->typlen = VARHDRSZ + $3;
                }
        | character
                {
                    $$ = makeNode(TypeName);
                    $$->name = xlateSqlType($1);
                }
        ;

character:  CHARACTER opt_varying opt_charset opt_collate
                {
                    char *type, *c;
                    if (($3 == NULL) || (strcasecmp($3, "sql_text") == 0)) {
                        if ($2) type = xlateSqlType("varchar");
                        else type = xlateSqlType("char");
                    } else {
                        if ($2) {
                            c = palloc(strlen("var") + strlen($3) + 1);
                            strcpy(c, "var");
                            strcat(c, $3);
                            type = xlateSqlType(c);
                        } else {
                            type = xlateSqlType($3);
                        }
                    };
                    if ($4 != NULL)
                    elog(ERROR,"COLLATE %s not yet implemented",$4);
                    $$ = type;
                }
        | CHAR opt_varying                        { $$ = xlateSqlType($2? "varchar": "char"); }
        | VARCHAR                                { $$ = xlateSqlType("varchar"); }
        | NATIONAL CHARACTER opt_varying        { $$ = xlateSqlType($3? "varchar": "char"); }
        | NCHAR opt_varying                        { $$ = xlateSqlType($2? "varchar": "char"); }
        ;

opt_varying:  VARYING                            { $$ = TRUE; }
        | /*EMPTY*/                                { $$ = FALSE; }
        ;

opt_charset:  CHARACTER SET ColId                { $$ = $3; }
        | /*EMPTY*/                                { $$ = NULL; }
        ;

opt_collate:  COLLATE ColId                        { $$ = $2; }
        | /*EMPTY*/                                { $$ = NULL; }
        ;

Datetime:  datetime
                {
                    $$ = makeNode(TypeName);
                    $$->name = xlateSqlType($1);
                }
        | TIMESTAMP opt_timezone
                {
                    $$ = makeNode(TypeName);
                    $$->name = xlateSqlType("timestamp");
                    $$->timezone = $2;
                }
        | TIME
                {
                    $$ = makeNode(TypeName);
                    $$->name = xlateSqlType("time");
                }
        | INTERVAL opt_interval
                {
                    $$ = makeNode(TypeName);
                    $$->name = xlateSqlType("interval");
                }
        ;

datetime:  YEAR_P                                { $$ = "year"; }
        | MONTH_P                                { $$ = "month"; }
        | DAY_P                                    { $$ = "day"; }
        | HOUR_P                                { $$ = "hour"; }
        | MINUTE_P                                { $$ = "minute"; }
        | SECOND_P                                { $$ = "second"; }
        ;

opt_timezone:  WITH TIME ZONE                    { $$ = TRUE; }
        | /*EMPTY*/                                { $$ = FALSE; }
        ;

opt_interval:  datetime                            { $$ = lcons($1, NIL); }
        | YEAR_P TO MONTH_P                        { $$ = NIL; }
        | DAY_P TO HOUR_P                        { $$ = NIL; }
        | DAY_P TO MINUTE_P                        { $$ = NIL; }
        | DAY_P TO SECOND_P                        { $$ = NIL; }
        | HOUR_P TO MINUTE_P                    { $$ = NIL; }
        | HOUR_P TO SECOND_P                    { $$ = NIL; }
        | /*EMPTY*/                                { $$ = NIL; }
        ;


/*****************************************************************************
 *
 *    expression grammar, still needs some cleanup
 *
 *****************************************************************************/

a_expr_or_null:  a_expr
                { $$ = $1; }
        | NULL_P
                {
                    A_Const *n = makeNode(A_Const);
                    n->val.type = T_Null;
                    $$ = (Node *)n;
                }
        ;

/* Expressions using row descriptors
 * Define row_descriptor to allow yacc to break the reduce/reduce conflict
 *  with singleton expressions.
 *
 * Note that "SOME" is the same as "ANY" in syntax.
 * - thomas 1998-01-10
 */
row_expr: '(' row_descriptor ')' IN '(' SubSelect ')'
                {
                    $$ = makeA_Expr(OP, "=any", (Node *)$2, (Node *)$6);
                }
        | '(' row_descriptor ')' NOT IN '(' SubSelect ')'
                {
                    $$ = makeA_Expr(OP, "<>any", (Node *)$2, (Node *)$7);
                }
        | '(' row_descriptor ')' RowOp row_opt '(' SubSelect ')'
                {
                    char *opr;
                    opr = palloc(strlen($4)+strlen($5)+1);
                    strcpy(opr, $4);
                    strcat(opr, $5);
                    $$ = makeA_Expr(OP, opr, (Node *)$2, (Node *)$7);
                }
        | '(' row_descriptor ')' '=' '(' row_descriptor ')'
                {
                    $$ = makeRowExpr("=", $2, $6);
                }
        | '(' row_descriptor ')' '<' '(' row_descriptor ')'
                {
                    $$ = makeRowExpr("<", $2, $6);
                }
        | '(' row_descriptor ')' '>' '(' row_descriptor ')'
                {
                    $$ = makeRowExpr("<", $2, $6);
                }
        | '(' row_descriptor ')' Op '(' row_descriptor ')'
                {
                    $$ = makeRowExpr($4, $2, $6);
                }
        ;

RowOp:  '='                        { $$ = "="; }
        | '<'                    { $$ = "<"; }
        | '>'                    { $$ = ">"; }
        ;

row_opt:  ALL                    { $$ = "all"; }
        | ANY                    { $$ = "any"; }
        | SOME                    { $$ = "any"; }
        ;

row_descriptor:  row_list ',' a_expr
                {
                    $$ = lappend($1, $3);
                }
        ;

row_list:  row_list ',' a_expr
                {
                    $$ = lappend($1, $3);
                }
        | a_expr
                {
                    $$ = lcons($1, NIL);
                }
        ;

a_expr:  attr opt_indirection
                {
                    $1->indirection = $2;
                    $$ = (Node *)$1;
                }
        | row_expr
                {    $$ = $1;  }
        | AexprConst
                {    $$ = $1;  }
        | '-' a_expr %prec UMINUS
                {    $$ = makeA_Expr(OP, "-", NULL, $2); }
        | a_expr '+' a_expr
                {    $$ = makeA_Expr(OP, "+", $1, $3); }
        | a_expr '-' a_expr
                {    $$ = makeA_Expr(OP, "-", $1, $3); }
        | a_expr '/' a_expr
                {    $$ = makeA_Expr(OP, "/", $1, $3); }
        | a_expr '*' a_expr
                {    $$ = makeA_Expr(OP, "*", $1, $3); }
        | a_expr '<' a_expr
                {    $$ = makeA_Expr(OP, "<", $1, $3); }
        | a_expr '>' a_expr
                {    $$ = makeA_Expr(OP, ">", $1, $3); }
        | a_expr '=' a_expr
                {    $$ = makeA_Expr(OP, "=", $1, $3); }
        | ':' a_expr
                {    $$ = makeA_Expr(OP, ":", NULL, $2); }
        | ';' a_expr
                {    $$ = makeA_Expr(OP, ";", NULL, $2); }
        | '|' a_expr
                {    $$ = makeA_Expr(OP, "|", NULL, $2); }
        | a_expr TYPECAST Typename
                {
                    $$ = (Node *)$1;
                    /* AexprConst can be either A_Const or ParamNo */
                    if (nodeTag($1) == T_A_Const) {
                        ((A_Const *)$1)->typename = $3;
                    } else if (nodeTag($1) == T_Param) {
                        ((ParamNo *)$1)->typename = $3;
                    /* otherwise, try to transform to a function call */
                    } else {
                        FuncCall *n = makeNode(FuncCall);
                        n->funcname = $3->name;
                        n->args = lcons($1,NIL);
                        $$ = (Node *)n;
                    }
                }
        | CAST a_expr AS Typename
                {
                    $$ = (Node *)$2;
                    /* AexprConst can be either A_Const or ParamNo */
                    if (nodeTag($2) == T_A_Const) {
                        ((A_Const *)$2)->typename = $4;
                    } else if (nodeTag($2) == T_Param) {
                        ((ParamNo *)$2)->typename = $4;
                    /* otherwise, try to transform to a function call */
                    } else {
                        FuncCall *n = makeNode(FuncCall);
                        n->funcname = $4->name;
                        n->args = lcons($2,NIL);
                        $$ = (Node *)n;
                    }
                }
        | '(' a_expr_or_null ')'
                {    $$ = $2; }
        | a_expr Op a_expr
                {    $$ = makeIndexable($2,$1,$3);    }
        | a_expr LIKE a_expr
                {    $$ = makeIndexable("~~", $1, $3); }
        | a_expr NOT LIKE a_expr
                {    $$ = makeA_Expr(OP, "!~~", $1, $4); }
        | Op a_expr
                {    $$ = makeA_Expr(OP, $1, NULL, $2); }
        | a_expr Op
                {    $$ = makeA_Expr(OP, $2, $1, NULL); }
        | ColId
                {
                    /* could be a column name or a relation_name */
                    Ident *n = makeNode(Ident);
                    n->name = $1;
                    n->indirection = NULL;
                    $$ = (Node *)n;
                }
        | name '(' '*' ')'
                {
                    /* cheap hack for aggregate (eg. count) */
                    FuncCall *n = makeNode(FuncCall);
                    A_Const *star = makeNode(A_Const);

                    star->val.type = T_String;
                    star->val.val.str = "";
                    n->funcname = $1;
                    n->args = lcons(star, NIL);
                    $$ = (Node *)n;
                }
        | name '(' ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = $1;
                    n->args = NIL;
                    $$ = (Node *)n;
                }
        | name '(' expr_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = $1;
                    n->args = $3;
                    $$ = (Node *)n;
                }
        | CURRENT_DATE
                {
                    A_Const *n = makeNode(A_Const);
                    TypeName *t = makeNode(TypeName);

                    n->val.type = T_String;
                    n->val.val.str = "now";
                    n->typename = t;

                    t->name = xlateSqlType("date");
                    t->setof = FALSE;

                    $$ = (Node *)n;
                }
        | CURRENT_TIME
                {
                    A_Const *n = makeNode(A_Const);
                    TypeName *t = makeNode(TypeName);

                    n->val.type = T_String;
                    n->val.val.str = "now";
                    n->typename = t;

                    t->name = xlateSqlType("time");
                    t->setof = FALSE;

                    $$ = (Node *)n;
                }
        | CURRENT_TIME '(' Iconst ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    A_Const *s = makeNode(A_Const);
                    TypeName *t = makeNode(TypeName);

                    n->funcname = xlateSqlType("time");
                    n->args = lcons(s, NIL);

                    s->val.type = T_String;
                    s->val.val.str = "now";
                    s->typename = t;

                    t->name = xlateSqlType("time");
                    t->setof = FALSE;

                    if ($3 != 0)
                        elog(NOTICE,"CURRENT_TIME(%d) precision not implemented; zero used instead",$3);

                    $$ = (Node *)n;
                }
        | CURRENT_TIMESTAMP
                {
                    A_Const *n = makeNode(A_Const);
                    TypeName *t = makeNode(TypeName);

                    n->val.type = T_String;
                    n->val.val.str = "now";
                    n->typename = t;

                    t->name = xlateSqlType("timestamp");
                    t->setof = FALSE;

                    $$ = (Node *)n;
                }
        | CURRENT_TIMESTAMP '(' Iconst ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    A_Const *s = makeNode(A_Const);
                    TypeName *t = makeNode(TypeName);

                    n->funcname = xlateSqlType("timestamp");
                    n->args = lcons(s, NIL);

                    s->val.type = T_String;
                    s->val.val.str = "now";
                    s->typename = t;

                    t->name = xlateSqlType("timestamp");
                    t->setof = FALSE;

                    if ($3 != 0)
                        elog(NOTICE,"CURRENT_TIMESTAMP(%d) precision not implemented; zero used instead",$3);

                    $$ = (Node *)n;
                }
        | CURRENT_USER
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "getpgusername";
                    n->args = NIL;
                    $$ = (Node *)n;
                }
        /* We probably need to define an "exists" node,
         *    since the optimizer could choose to find only one match.
         * Perhaps the first implementation could just check for
         *    count(*) > 0? - thomas 1997-07-19
         */
        | EXISTS '(' SubSelect ')'
                {
                    elog(ERROR,"EXISTS not yet implemented");
                    $$ = $3;
                }
        | EXTRACT '(' extract_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "date_part";
                    n->args = $3;
                    $$ = (Node *)n;
                }
        | POSITION '(' position_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "strpos";
                    n->args = $3;
                    $$ = (Node *)n;
                }
        | SUBSTRING '(' substr_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "substr";
                    n->args = $3;
                    $$ = (Node *)n;
                }
        /* various trim expressions are defined in SQL92 - thomas 1997-07-19 */
        | TRIM '(' BOTH trim_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "btrim";
                    n->args = $4;
                    $$ = (Node *)n;
                }
        | TRIM '(' LEADING trim_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "ltrim";
                    n->args = $4;
                    $$ = (Node *)n;
                }
        | TRIM '(' TRAILING trim_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "rtrim";
                    n->args = $4;
                    $$ = (Node *)n;
                }
        | TRIM '(' trim_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "btrim";
                    n->args = $3;
                    $$ = (Node *)n;
                }
        | a_expr ISNULL
                {    $$ = makeA_Expr(ISNULL, NULL, $1, NULL); }
        | a_expr IS NULL_P
                {    $$ = makeA_Expr(ISNULL, NULL, $1, NULL); }
        | a_expr NOTNULL
                {    $$ = makeA_Expr(NOTNULL, NULL, $1, NULL); }
        | a_expr IS NOT NULL_P
                {    $$ = makeA_Expr(NOTNULL, NULL, $1, NULL); }
        /* IS TRUE, IS FALSE, etc used to be function calls
         *  but let's make them expressions to allow the optimizer
         *  a chance to eliminate them if a_expr is a constant string.
         * - thomas 1997-12-22
         */
        | a_expr IS TRUE_P
                {
                    A_Const *n = makeNode(A_Const);
                    n->val.type = T_String;
                    n->val.val.str = "t";
                    n->typename = makeNode(TypeName);
                    n->typename->name = xlateSqlType("bool");
                    $$ = makeA_Expr(OP, "=", $1,(Node *)n);
                }
        | a_expr IS NOT FALSE_P
                {
                    A_Const *n = makeNode(A_Const);
                    n->val.type = T_String;
                    n->val.val.str = "t";
                    n->typename = makeNode(TypeName);
                    n->typename->name = xlateSqlType("bool");
                    $$ = makeA_Expr(OP, "=", $1,(Node *)n);
                }
        | a_expr IS FALSE_P
                {
                    A_Const *n = makeNode(A_Const);
                    n->val.type = T_String;
                    n->val.val.str = "f";
                    n->typename = makeNode(TypeName);
                    n->typename->name = xlateSqlType("bool");
                    $$ = makeA_Expr(OP, "=", $1,(Node *)n);
                }
        | a_expr IS NOT TRUE_P
                {
                    A_Const *n = makeNode(A_Const);
                    n->val.type = T_String;
                    n->val.val.str = "f";
                    n->typename = makeNode(TypeName);
                    n->typename->name = xlateSqlType("bool");
                    $$ = makeA_Expr(OP, "=", $1,(Node *)n);
                }
        | a_expr BETWEEN AexprConst AND AexprConst
                {
                    $$ = makeA_Expr(AND, NULL,
                        makeA_Expr(OP, ">=", $1, $3),
                        makeA_Expr(OP, "<=", $1, $5));
                }
        | a_expr NOT BETWEEN AexprConst AND AexprConst
                {
                    $$ = makeA_Expr(OR, NULL,
                        makeA_Expr(OP, "<", $1, $4),
                        makeA_Expr(OP, ">", $1, $6));
                }
        | a_expr IN { saved_In_Expr = $1; } '(' in_expr ')'
                {    $$ = $5; }
        | a_expr NOT IN { saved_In_Expr = $1; } '(' not_in_expr ')'
                {    $$ = $6; }
        | a_expr AND a_expr
                {    $$ = makeA_Expr(AND, NULL, $1, $3); }
        | a_expr OR a_expr
                {    $$ = makeA_Expr(OR, NULL, $1, $3); }
        | NOT a_expr
                {    $$ = makeA_Expr(NOT, NULL, NULL, $2); }
        ;

opt_indirection:  '[' a_expr ']' opt_indirection
                {
                    A_Indices *ai = makeNode(A_Indices);
                    ai->lidx = NULL;
                    ai->uidx = $2;
                    $$ = lcons(ai, $4);
                }
        | '[' a_expr ':' a_expr ']' opt_indirection
                {
                    A_Indices *ai = makeNode(A_Indices);
                    ai->lidx = $2;
                    ai->uidx = $4;
                    $$ = lcons(ai, $6);
                }
        | /* EMPTY */
                {    $$ = NIL; }
        ;

expr_list:  a_expr_or_null
                { $$ = lcons($1, NIL); }
        | expr_list ',' a_expr_or_null
                { $$ = lappend($1, $3); }
        | expr_list USING a_expr
                { $$ = lappend($1, $3); }
        ;

extract_list:  datetime FROM a_expr
                {
                    A_Const *n = makeNode(A_Const);
                    n->val.type = T_String;
                    n->val.val.str = $1;
                    $$ = lappend(lcons((Node *)n,NIL), $3);
                }
        | /* EMPTY */
                {    $$ = NIL; }
        ;

position_list:  position_expr IN position_expr
                {    $$ = makeList($3, $1, -1); }
        | /* EMPTY */
                {    $$ = NIL; }
        ;

position_expr:  attr opt_indirection
                {
                    $1->indirection = $2;
                    $$ = (Node *)$1;
                }
        | AexprConst
                {    $$ = $1;  }
        | '-' position_expr %prec UMINUS
                {    $$ = makeA_Expr(OP, "-", NULL, $2); }
        | position_expr '+' position_expr
                {    $$ = makeA_Expr(OP, "+", $1, $3); }
        | position_expr '-' position_expr
                {    $$ = makeA_Expr(OP, "-", $1, $3); }
        | position_expr '/' position_expr
                {    $$ = makeA_Expr(OP, "/", $1, $3); }
        | position_expr '*' position_expr
                {    $$ = makeA_Expr(OP, "*", $1, $3); }
        | '|' position_expr
                {    $$ = makeA_Expr(OP, "|", NULL, $2); }
        | position_expr TYPECAST Typename
                {
                    $$ = (Node *)$1;
                    /* AexprConst can be either A_Const or ParamNo */
                    if (nodeTag($1) == T_A_Const) {
                        ((A_Const *)$1)->typename = $3;
                    } else if (nodeTag($1) == T_Param) {
                        ((ParamNo *)$1)->typename = $3;
                    /* otherwise, try to transform to a function call */
                    } else {
                        FuncCall *n = makeNode(FuncCall);
                        n->funcname = $3->name;
                        n->args = lcons($1,NIL);
                        $$ = (Node *)n;
                    }
                }
        | CAST position_expr AS Typename
                {
                    $$ = (Node *)$2;
                    /* AexprConst can be either A_Const or ParamNo */
                    if (nodeTag($2) == T_A_Const) {
                        ((A_Const *)$2)->typename = $4;
                    } else if (nodeTag($2) == T_Param) {
                        ((ParamNo *)$2)->typename = $4;
                    /* otherwise, try to transform to a function call */
                    } else {
                        FuncCall *n = makeNode(FuncCall);
                        n->funcname = $4->name;
                        n->args = lcons($2,NIL);
                        $$ = (Node *)n;
                    }
                }
        | '(' position_expr ')'
                {    $$ = $2; }
        | position_expr Op position_expr
                {    $$ = makeA_Expr(OP, $2, $1, $3); }
        | Op position_expr
                {    $$ = makeA_Expr(OP, $1, NULL, $2); }
        | position_expr Op
                {    $$ = makeA_Expr(OP, $2, $1, NULL); }
        | ColId
                {
                    /* could be a column name or a relation_name */
                    Ident *n = makeNode(Ident);
                    n->name = $1;
                    n->indirection = NULL;
                    $$ = (Node *)n;
                }
        | name '(' ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = $1;
                    n->args = NIL;
                    $$ = (Node *)n;
                }
        | name '(' expr_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = $1;
                    n->args = $3;
                    $$ = (Node *)n;
                }
        | POSITION '(' position_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "strpos";
                    n->args = $3;
                    $$ = (Node *)n;
                }
        | SUBSTRING '(' substr_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "substr";
                    n->args = $3;
                    $$ = (Node *)n;
                }
        /* various trim expressions are defined in SQL92 - thomas 1997-07-19 */
        | TRIM '(' BOTH trim_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "btrim";
                    n->args = $4;
                    $$ = (Node *)n;
                }
        | TRIM '(' LEADING trim_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "ltrim";
                    n->args = $4;
                    $$ = (Node *)n;
                }
        | TRIM '(' TRAILING trim_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "rtrim";
                    n->args = $4;
                    $$ = (Node *)n;
                }
        | TRIM '(' trim_list ')'
                {
                    FuncCall *n = makeNode(FuncCall);
                    n->funcname = "btrim";
                    n->args = $3;
                    $$ = (Node *)n;
                }
        ;

substr_list:  expr_list substr_from substr_for
                {
                    $$ = nconc(nconc($1,$2),$3);
                }
        | /* EMPTY */
                {    $$ = NIL; }
        ;

substr_from:  FROM expr_list
                {    $$ = $2; }
        | /* EMPTY */
                {
                    A_Const *n = makeNode(A_Const);
                    n->val.type = T_Integer;
                    n->val.val.ival = 1;
                    $$ = lcons((Node *)n,NIL);
                }
        ;

substr_for:  FOR expr_list
                {    $$ = $2; }
        | /* EMPTY */
                {    $$ = NIL; }
        ;

trim_list:  a_expr FROM expr_list
                { $$ = lappend($3, $1); }
        | FROM expr_list
                { $$ = $2; }
        | expr_list
                { $$ = $1; }
        ;

in_expr:  SubSelect
                {    $$ = makeA_Expr(OP, "=", saved_In_Expr, (Node *)$1); }
        | in_expr_nodes
                {    $$ = $1; }
        ;

in_expr_nodes:  AexprConst
                {    $$ = makeA_Expr(OP, "=", saved_In_Expr, $1); }
        | in_expr_nodes ',' AexprConst
                {    $$ = makeA_Expr(OR, NULL, $1,
                        makeA_Expr(OP, "=", saved_In_Expr, $3));
                }
        ;

not_in_expr:  SubSelect
                {    $$ = makeA_Expr(OP, "<>", saved_In_Expr, (Node *)$1); }
        | not_in_expr_nodes
                {    $$ = $1; }
        ;

not_in_expr_nodes:  AexprConst
                {    $$ = makeA_Expr(OP, "<>", saved_In_Expr, $1); }
        | not_in_expr_nodes ',' AexprConst
                {    $$ = makeA_Expr(AND, NULL, $1,
                        makeA_Expr(OP, "<>", saved_In_Expr, $3));
                }
        ;

attr:  relation_name '.' attrs
                {
                    $$ = makeNode(Attr);
                    $$->relname = $1;
                    $$->paramNo = NULL;
                    $$->attrs = $3;
                    $$->indirection = NULL;
                }
        | ParamNo '.' attrs
                {
                    $$ = makeNode(Attr);
                    $$->relname = NULL;
                    $$->paramNo = $1;
                    $$->attrs = $3;
                    $$->indirection = NULL;
                }
        ;

attrs:      attr_name
                { $$ = lcons(makeString($1), NIL); }
        | attrs '.' attr_name
                { $$ = lappend($1, makeString($3)); }
        | attrs '.' '*'
                { $$ = lappend($1, makeString("*")); }
        ;


/*****************************************************************************
 *
 *    target lists
 *
 *****************************************************************************/

res_target_list:  res_target_list ',' res_target_el
                {    $$ = lappend($1,$3);  }
        | res_target_el
                {    $$ = lcons($1, NIL);  }
        | '*'
                {
                    ResTarget *rt = makeNode(ResTarget);
                    Attr *att = makeNode(Attr);
                    att->relname = "*";
                    att->paramNo = NULL;
                    att->attrs = NULL;
                    att->indirection = NIL;
                    rt->name = NULL;
                    rt->indirection = NULL;
                    rt->val = (Node *)att;
                    $$ = lcons(rt, NIL);
                }
        ;

res_target_el:  ColId opt_indirection '=' a_expr_or_null
                {
                    $$ = makeNode(ResTarget);
                    $$->name = $1;
                    $$->indirection = $2;
                    $$->val = (Node *)$4;
                }
        | attr opt_indirection
                {
                    $$ = makeNode(ResTarget);
                    $$->name = NULL;
                    $$->indirection = $2;
                    $$->val = (Node *)$1;
                }
        | relation_name '.' '*'
                {
                    Attr *att = makeNode(Attr);
                    att->relname = $1;
                    att->paramNo = NULL;
                    att->attrs = lcons(makeString("*"), NIL);
                    att->indirection = NIL;
                    $$ = makeNode(ResTarget);
                    $$->name = NULL;
                    $$->indirection = NULL;
                    $$->val = (Node *)att;
                }
        ;

/*
** target list for select.
** should get rid of the other but is still needed by the defunct retrieve into
** and update (uses a subset)
*/
res_target_list2:  res_target_list2 ',' res_target_el2
                {    $$ = lappend($1, $3);  }
        | res_target_el2
                {    $$ = lcons($1, NIL);  }
        ;

/* AS is not optional because shift/red conflict with unary ops */
res_target_el2:  a_expr_or_null AS ColLabel
                {
                    $$ = makeNode(ResTarget);
                    $$->name = $3;
                    $$->indirection = NULL;
                    $$->val = (Node *)$1;
                }
        | a_expr_or_null
                {
                    $$ = makeNode(ResTarget);
                    $$->name = NULL;
                    $$->indirection = NULL;
                    $$->val = (Node *)$1;
                }
        | relation_name '.' '*'
                {
                    Attr *att = makeNode(Attr);
                    att->relname = $1;
                    att->paramNo = NULL;
                    att->attrs = lcons(makeString("*"), NIL);
                    att->indirection = NIL;
                    $$ = makeNode(ResTarget);
                    $$->name = NULL;
                    $$->indirection = NULL;
                    $$->val = (Node *)att;
                }
        | '*'
                {
                    Attr *att = makeNode(Attr);
                    att->relname = "*";
                    att->paramNo = NULL;
                    att->attrs = NULL;
                    att->indirection = NIL;
                    $$ = makeNode(ResTarget);
                    $$->name = NULL;
                    $$->indirection = NULL;
                    $$->val = (Node *)att;
                }
        ;

opt_id:  ColId                                    { $$ = $1; }
        | /* EMPTY */                            { $$ = NULL; }
        ;

relation_name:    SpecialRuleRelation
                {
                    $$ = $1;
                    StrNCpy(saved_relname, $1, NAMEDATALEN);
                }
        | ColId
                {
                    /* disallow refs to variable system tables */
                    if (strcmp(LogRelationName, $1) == 0
                       || strcmp(VariableRelationName, $1) == 0)
                        elog(ERROR,"%s cannot be accessed by users",$1);
                    else
                        $$ = $1;
                    StrNCpy(saved_relname, $1, NAMEDATALEN);
                }
        ;

database_name:            ColId            { $$ = $1; };
access_method:            Id                { $$ = $1; };
attr_name:                ColId            { $$ = $1; };
class:                    Id                { $$ = $1; };
index_name:                ColId            { $$ = $1; };

/* Functions
 * Include date/time keywords as SQL92 extension.
 * Include TYPE as a SQL92 unreserved keyword. - thomas 1997-10-05
 */
name:                    ColId            { $$ = $1; };

file_name:                Sconst            { $$ = $1; };
recipe_name:            Id                { $$ = $1; };

/* Constants
 * Include TRUE/FALSE for SQL3 support. - thomas 1997-10-24
 */
AexprConst:  Iconst
                {
                    A_Const *n = makeNode(A_Const);
                    n->val.type = T_Integer;
                    n->val.val.ival = $1;
                    $$ = (Node *)n;
                }
        | FCONST
                {
                    A_Const *n = makeNode(A_Const);
                    n->val.type = T_Float;
                    n->val.val.dval = $1;
                    $$ = (Node *)n;
                }
        | Sconst
                {
                    A_Const *n = makeNode(A_Const);
                    n->val.type = T_String;
                    n->val.val.str = $1;
                    $$ = (Node *)n;
                }
        | Typename Sconst
                {
                    A_Const *n = makeNode(A_Const);
                    n->typename = $1;
                    n->val.type = T_String;
                    n->val.val.str = $2;
                    $$ = (Node *)n;
                }
        | ParamNo
                {    $$ = (Node *)$1;  }
        | TRUE_P
                {
                    A_Const *n = makeNode(A_Const);
                    n->val.type = T_String;
                    n->val.val.str = "t";
                    n->typename = makeNode(TypeName);
                    n->typename->name = xlateSqlType("bool");
                    $$ = (Node *)n;
                }
        | FALSE_P
                {
                    A_Const *n = makeNode(A_Const);
                    n->val.type = T_String;
                    n->val.val.str = "f";
                    n->typename = makeNode(TypeName);
                    n->typename->name = xlateSqlType("bool");
                    $$ = (Node *)n;
                }
        ;

ParamNo:  PARAM
                {
                    $$ = makeNode(ParamNo);
                    $$->number = $1;
                }
        ;

NumConst:  Iconst                        { $$ = makeInteger($1); }
        | FCONST                        { $$ = makeFloat($1); }
        ;

Iconst:  ICONST                            { $$ = $1; };
Sconst:  SCONST                            { $$ = $1; };

/* Column and type identifier
 * Does not include explicit datetime types
 *  since these must be decoupled in Typename syntax.
 * Use ColId for most identifiers. - thomas 1997-10-21
 */
Id:  IDENT                                { $$ = $1; };

/* Column identifier
 * Include date/time keywords as SQL92 extension.
 * Include TYPE as a SQL92 unreserved keyword. - thomas 1997-10-05
 * Add other keywords. Note that as the syntax expands,
 *  some of these keywords will have to be removed from this
 *  list due to shift/reduce conflicts in yacc. If so, move
 *  down to the ColLabel entity. - thomas 1997-11-06
 */
ColId:  Id                                { $$ = $1; }
        | datetime                        { $$ = $1; }
        | ACTION                        { $$ = "action"; }
        | DATABASE                        { $$ = "database"; }
        | DELIMITERS                    { $$ = "delimiters"; }
        | FUNCTION                        { $$ = "function"; }
        | INDEX                            { $$ = "index"; }
        | KEY                            { $$ = "key"; }
        | LANGUAGE                        { $$ = "language"; }
        | LOCATION                        { $$ = "location"; }
        | MATCH                            { $$ = "match"; }
        | OPERATOR                        { $$ = "operator"; }
        | OPTION                        { $$ = "option"; }
        | PRIVILEGES                    { $$ = "privileges"; }
        | RECIPE                        { $$ = "recipe"; }
        | TIME                            { $$ = "time"; }
        | TRIGGER                        { $$ = "trigger"; }
        | TYPE_P                        { $$ = "type"; }
        | VERSION                        { $$ = "version"; }
        | ZONE                            { $$ = "zone"; }
        ;

/* Column label
 * Allowed labels in "AS" clauses.
 * Include TRUE/FALSE SQL3 reserved words for Postgres backward
 *  compatibility. Cannot allow this for column names since the
 *  syntax would not distinguish between the constant value and
 *  a column name. - thomas 1997-10-24
 * Add other keywords to this list. Note that they appear here
 *  rather than in ColId if there was a shift/reduce conflict
 *  when used as a full identifier. - thomas 1997-11-06
 */
ColLabel:  ColId                        { $$ = $1; }
        | ARCHIVE                        { $$ = "archive"; }
        | CLUSTER                        { $$ = "cluster"; }
        | CONSTRAINT                    { $$ = "constraint"; }
        | CROSS                            { $$ = "cross"; }
        | FOREIGN                        { $$ = "foreign"; }
        | GROUP                            { $$ = "group"; }
        | LOAD                            { $$ = "load"; }
        | ORDER                            { $$ = "order"; }
        | POSITION                        { $$ = "position"; }
        | PRECISION                        { $$ = "precision"; }
        | TABLE                            { $$ = "table"; }
        | TRANSACTION                    { $$ = "transaction"; }
        | TRUE_P                        { $$ = "true"; }
        | FALSE_P                        { $$ = "false"; }
        ;

SpecialRuleRelation:  CURRENT
                {
                    if (QueryIsRule)
                        $$ = "*CURRENT*";
                    else
                        elog(ERROR,"CURRENT used in non-rule query");
                }
        | NEW
                {
                    if (QueryIsRule)
                        $$ = "*NEW*";
                    else
                        elog(ERROR,"NEW used in non-rule query");
                }
        ;

%%

static Node *
makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr)
{
    A_Expr *a = makeNode(A_Expr);
    a->oper = oper;
    a->opname = opname;
    a->lexpr = lexpr;
    a->rexpr = rexpr;
    return (Node *)a;
}

/* makeRowExpr()
 * Generate separate operator nodes for a single row descriptor expression.
 * Perhaps this should go deeper in the parser someday... - thomas 1997-12-22
 */
static Node *
makeRowExpr(char *opr, List *largs, List *rargs)
{
    Node *expr = NULL;
    Node *larg, *rarg;

    if (length(largs) != length(rargs))
        elog(ERROR,"Unequal number of entries in row expression");

    if (lnext(largs) != NIL)
        expr = makeRowExpr(opr,lnext(largs),lnext(rargs));

    larg = lfirst(largs);
    rarg = lfirst(rargs);

    if ((strcmp(opr, "=") == 0)
     || (strcmp(opr, "<") == 0)
     || (strcmp(opr, "<=") == 0)
     || (strcmp(opr, ">") == 0)
     || (strcmp(opr, ">=") == 0))
    {
        if (expr == NULL)
            expr = makeA_Expr(OP, opr, larg, rarg);
        else
            expr = makeA_Expr(AND, NULL, expr, makeA_Expr(OP, opr, larg, rarg));
    }
    else if (strcmp(opr, "<>") == 0)
    {
        if (expr == NULL)
            expr = makeA_Expr(OP, opr, larg, rarg);
        else
            expr = makeA_Expr(OR, NULL, expr, makeA_Expr(OP, opr, larg, rarg));
    }
    else
    {
        elog(ERROR,"Operator '%s' not implemented for row expressions",opr);
    }

#if FALSE
    while ((largs != NIL) && (rargs != NIL))
    {
        larg = lfirst(largs);
        rarg = lfirst(rargs);

        if (expr == NULL)
            expr = makeA_Expr(OP, opr, larg, rarg);
        else
            expr = makeA_Expr(AND, NULL, expr, makeA_Expr(OP, opr, larg, rarg));

        largs = lnext(largs);
        rargs = lnext(rargs);
    }
    pprint(expr);
#endif

    return expr;
} /* makeRowExpr() */

void
mapTargetColumns(List *src, List *dst)
{
    ColumnDef *s;
    ResTarget *d;

    if (length(src) != length(dst))
        elog(ERROR,"CREATE TABLE/AS SELECT has mismatched column count");

    while ((src != NIL) && (dst != NIL))
    {
        s = (ColumnDef *)lfirst(src);
        d = (ResTarget *)lfirst(dst);

        d->name = s->colname;

        src = lnext(src);
        dst = lnext(dst);
    }

    return;
} /* mapTargetColumns() */

static Node *makeIndexable(char *opname, Node *lexpr, Node *rexpr)
{
    Node *result = NULL;

    /* we do this so indexes can be used */
    if (strcmp(opname,"~") == 0 ||
        strcmp(opname,"~*") == 0)
    {
        if (nodeTag(rexpr) == T_A_Const &&
           ((A_Const *)rexpr)->val.type == T_String &&
           ((A_Const *)rexpr)->val.val.str[0] == '^')
        {
            A_Const *n = (A_Const *)rexpr;
            char *match_least = palloc(strlen(n->val.val.str)+2);
            char *match_most = palloc(strlen(n->val.val.str)+2);
            int pos, match_pos=0;

            /* skip leading ^ */
            for (pos = 1; n->val.val.str[pos]; pos++)
            {
                if (n->val.val.str[pos] == '.' ||
                    n->val.val.str[pos] == '?' ||
                    n->val.val.str[pos] == '*' ||
                    n->val.val.str[pos] == '[' ||
                    n->val.val.str[pos] == '$' ||
                    (strcmp(opname,"~*") == 0 && isalpha(n->val.val.str[pos])))
                     break;
                 if (n->val.val.str[pos] == '\\')
                    pos++;
                match_least[match_pos] = n->val.val.str[pos];
                match_most[match_pos++] = n->val.val.str[pos];
            }

            if (match_pos != 0)
            {
                A_Const *least = makeNode(A_Const);
                A_Const *most = makeNode(A_Const);

                /* make strings to be used in index use */
                match_least[match_pos] = '\0';
                match_most[match_pos] = '\377';
                match_most[match_pos+1] = '\0';
                least->val.type = T_String;
                least->val.val.str = match_least;
                most->val.type = T_String;
                most->val.val.str = match_most;
                result = makeA_Expr(AND, NULL,
                        makeA_Expr(OP, "~", lexpr, rexpr),
                        makeA_Expr(AND, NULL,
                            makeA_Expr(OP, ">=", lexpr, (Node *)least),
                            makeA_Expr(OP, "<=", lexpr, (Node *)most)));
            }
        }
    }
    else if (strcmp(opname,"~~") == 0)
    {
        if (nodeTag(rexpr) == T_A_Const &&
           ((A_Const *)rexpr)->val.type == T_String)
        {
            A_Const *n = (A_Const *)rexpr;
            char *match_least = palloc(strlen(n->val.val.str)+2);
            char *match_most = palloc(strlen(n->val.val.str)+2);
            int pos, match_pos=0;

            for (pos = 0; n->val.val.str[pos]; pos++)
            {
                if ((n->val.val.str[pos] == '%' &&
                     n->val.val.str[pos+1] != '%') ||
                    (n->val.val.str[pos] == '_' &&
                      n->val.val.str[pos+1] != '_'))
                     break;
                 if (n->val.val.str[pos] == '%' ||
                    n->val.val.str[pos] == '_' ||
                    n->val.val.str[pos] == '\\')
                    pos++;
                match_least[match_pos] = n->val.val.str[pos];
                match_most[match_pos++] = n->val.val.str[pos];
            }

            if (match_pos != 0)
            {
                A_Const *least = makeNode(A_Const);
                A_Const *most = makeNode(A_Const);

                /* make strings to be used in index use */
                match_least[match_pos] = '\0';
                match_most[match_pos] = '\377';
                match_most[match_pos+1] = '\0';
                least->val.type = T_String;
                least->val.val.str = match_least;
                most->val.type = T_String;
                most->val.val.str = match_most;
                result = makeA_Expr(AND, NULL,
                        makeA_Expr(OP, "~~", lexpr, rexpr),
                        makeA_Expr(AND, NULL,
                            makeA_Expr(OP, ">=", lexpr, (Node *)least),
                            makeA_Expr(OP, "<=", lexpr, (Node *)most)));
            }
        }
    }

    if (result == NULL)
        result = makeA_Expr(OP, opname, lexpr, rexpr);
    return result;
} /* makeIndexable() */


/* xlateSqlType()
 * Convert alternate type names to internal Postgres types.
 * Do not convert "float", since that is handled elsewhere
 *  for FLOAT(p) syntax.
 */
static char *
xlateSqlType(char *name)
{
    if (!strcasecmp(name,"int")
     || !strcasecmp(name,"integer"))
        return "int4";
    else if (!strcasecmp(name, "smallint"))
        return "int2";
    else if (!strcasecmp(name, "real"))
        return "float8";
    else if (!strcasecmp(name, "interval"))
        return "timespan";
    else if (!strcasecmp(name, "boolean"))
        return "bool";
    else
        return name;
} /* xlateSqlName() */


void parser_init(Oid *typev, int nargs)
{
    QueryIsRule = FALSE;
    saved_relname[0]= '\0';
    saved_In_Expr = NULL;

    param_type_init(typev, nargs);
}


/* FlattenStringList()
 * Traverse list of string nodes and convert to a single string.
 * Used for reconstructing string form of complex expressions.
 *
 * Allocate at least one byte for terminator.
 */
static char *
FlattenStringList(List *list)
{
    List *l;
    Value *v;
    char *s;
    char *sp;
    int nlist, len = 0;

    nlist = length(list);
    l = list;
    while(l != NIL) {
        v = (Value *)lfirst(l);
        sp = v->val.str;
        l = lnext(l);
        len += strlen(sp);
    };
    len += nlist;

    s = (char*) palloc(len+1);
    *s = '\0';

    l = list;
    while(l != NIL) {
        v = (Value *)lfirst(l);
        sp = v->val.str;
        l = lnext(l);
        strcat(s,sp);
        if (l != NIL) strcat(s," ");
    };
    *(s+len) = '\0';

#ifdef PARSEDEBUG
printf( "flattened string is \"%s\"\n", s);
#endif

    return(s);
} /* FlattenStringList() */


/* makeConstantList()
 * Convert constant value node into string node.
 */
static List *
makeConstantList( A_Const *n)
{
    char *defval = NULL;
    if (nodeTag(n) != T_A_Const) {
        elog(ERROR,"Cannot handle non-constant parameter");

    } else if (n->val.type == T_Float) {
        defval = (char*) palloc(20+1);
        sprintf( defval, "%g", n->val.val.dval);

    } else if (n->val.type == T_Integer) {
        defval = (char*) palloc(20+1);
        sprintf( defval, "%ld", n->val.val.ival);

    } else if (n->val.type == T_String) {
        defval = (char*) palloc(strlen( ((A_Const *) n)->val.val.str) + 3);
        strcpy( defval, "'");
        strcat( defval, ((A_Const *) n)->val.val.str);
        strcat( defval, "'");

    } else {
        elog(ERROR,"Internal error in makeConstantList(): cannot encode node");
    };

#ifdef PARSEDEBUG
printf( "AexprConst argument is \"%s\"\n", defval);
#endif

    return( lcons( makeString(defval), NIL));
} /* makeConstantList() */


/* fmtId()
 * Check input string for non-lowercase/non-numeric characters.
 * Returns either input string or input surrounded by double quotes.
 */
static char *
fmtId(char *rawid)
{
    static char *cp;

    for (cp = rawid; *cp != '\0'; cp++)
        if (! (islower(*cp) || isdigit(*cp) || (*cp == '_'))) break;

    if (*cp != '\0') {
        cp = palloc(strlen(rawid)+1);
        strcpy(cp,"\"");
        strcat(cp,rawid);
        strcat(cp,"\"");
    } else {
        cp = rawid;
    };

#ifdef PARSEDEBUG
printf("fmtId- %sconvert %s to %s\n", ((cp == rawid)? "do not ": ""), rawid, cp);
#endif

    return(cp);
}

/*
 * param_type_init()
 *
 * keep enough information around fill out the type of param nodes
 * used in postquel functions
 */
static void
param_type_init(Oid *typev, int nargs)
{
    pfunc_num_args = nargs;
    param_type_info = typev;
}

Oid param_type(int t)
{
    if ((t > pfunc_num_args) || (t == 0))
        return InvalidOid;
    return param_type_info[t - 1];
}
/*-------------------------------------------------------------------------
 *
 * keywords.c--
 *      lexical token lookup for reserved words in postgres SQL
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *      $Header: /usr/local/cvsroot/pgsql/src/backend/parser/keywords.c,v 1.29 1998/01/05 03:32:22 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */
#include <ctype.h>
#include <string.h>

#include "postgres.h"
#include "nodes/parsenodes.h"
#include "nodes/pg_list.h"
#include "parse.h"
#include "parser/keywords.h"
#include "utils/elog.h"

/*
 * List of (keyword-name, keyword-token-value) pairs.
 *
 * !!WARNING!!: This list must be sorted, because binary
 *         search is used to locate entries.
 */
static ScanKeyword ScanKeywords[] = {
    /* name                    value            */
    {"abort", ABORT_TRANS},
    {"acl", ACL},
    {"action", ACTION},
    {"add", ADD},
    {"after", AFTER},
    {"aggregate", AGGREGATE},
    {"all", ALL},
    {"alter", ALTER},
    {"analyze", ANALYZE},
    {"and", AND},
    {"any", ANY},
    {"append", APPEND},
    {"archive", ARCHIVE},
    {"as", AS},
    {"asc", ASC},
    {"backward", BACKWARD},
    {"before", BEFORE},
    {"begin", BEGIN_TRANS},
    {"between", BETWEEN},
    {"binary", BINARY},
    {"both", BOTH},
    {"by", BY},
    {"cascade", CASCADE},
    {"cast", CAST},
    {"change", CHANGE},
    {"char", CHAR},
    {"character", CHARACTER},
    {"check", CHECK},
    {"close", CLOSE},
    {"cluster", CLUSTER},
    {"collate", COLLATE},
    {"column", COLUMN},
    {"commit", COMMIT},
    {"constraint", CONSTRAINT},
    {"copy", COPY},
    {"create", CREATE},
    {"createdb", CREATEDB},
    {"createuser", CREATEUSER},
    {"cross", CROSS},
    {"current", CURRENT},
    {"current_date", CURRENT_DATE},
    {"current_time", CURRENT_TIME},
    {"current_timestamp", CURRENT_TIMESTAMP},
    {"current_user", CURRENT_USER},
    {"cursor", CURSOR},
    {"database", DATABASE},
    {"day", DAY_P},
    {"decimal", DECIMAL},
    {"declare", DECLARE},
    {"default", DEFAULT},
    {"delete", DELETE},
    {"delimiters", DELIMITERS},
    {"desc", DESC},
    {"distinct", DISTINCT},
    {"do", DO},
    {"double", DOUBLE},
    {"drop", DROP},
    {"end", END_TRANS},
    {"execute", EXECUTE},
    {"exists", EXISTS},
    {"explain", EXPLAIN},
    {"extend", EXTEND},
    {"extract", EXTRACT},
    {"false", FALSE_P},
    {"fetch", FETCH},
    {"float", FLOAT},
    {"for", FOR},
    {"foreign", FOREIGN},
    {"forward", FORWARD},
    {"from", FROM},
    {"full", FULL},
    {"function", FUNCTION},
    {"grant", GRANT},
    {"group", GROUP},
    {"handler", HANDLER},
    {"having", HAVING},
    {"hour", HOUR_P},
    {"in", IN},
    {"index", INDEX},
    {"inherits", INHERITS},
    {"inner", INNER_P},
    {"insert", INSERT},
    {"instead", INSTEAD},
    {"interval", INTERVAL},
    {"into", INTO},
    {"is", IS},
    {"isnull", ISNULL},
    {"join", JOIN},
    {"key", KEY},
    {"lancompiler", LANCOMPILER},
    {"language", LANGUAGE},
    {"leading", LEADING},
    {"left", LEFT},
    {"like", LIKE},
    {"listen", LISTEN},
    {"load", LOAD},
    {"local", LOCAL},
    {"location", LOCATION},
    {"match", MATCH},
    {"merge", MERGE},
    {"minute", MINUTE_P},
    {"month", MONTH_P},
    {"move", MOVE},
    {"national", NATIONAL},
    {"natural", NATURAL},
    {"nchar", NCHAR},
    {"new", NEW},
    {"no", NO},
    {"nocreatedb", NOCREATEDB},
    {"nocreateuser", NOCREATEUSER},
    {"none", NONE},
    {"not", NOT},
    {"nothing", NOTHING},
    {"notify", NOTIFY},
    {"notnull", NOTNULL},
    {"null", NULL_P},
    {"numeric", NUMERIC},
    {"oids", OIDS},
    {"on", ON},
    {"operator", OPERATOR},
    {"option", OPTION},
    {"or", OR},
    {"order", ORDER},
    {"outer", OUTER_P},
    {"partial", PARTIAL},
    {"password", PASSWORD},
    {"position", POSITION},
    {"precision", PRECISION},
    {"primary", PRIMARY},
    {"privileges", PRIVILEGES},
    {"procedural", PROCEDURAL},
    {"procedure", PROCEDURE},
    {"public", PUBLIC},
    {"recipe", RECIPE},
    {"references", REFERENCES},
    {"rename", RENAME},
    {"replace", REPLACE},
    {"reset", RESET},
    {"retrieve", RETRIEVE},
    {"returns", RETURNS},
    {"revoke", REVOKE},
    {"right", RIGHT},
    {"rollback", ROLLBACK},
    {"rule", RULE},
    {"second", SECOND_P},
    {"select", SELECT},
    {"sequence", SEQUENCE},
    {"set", SET},
    {"setof", SETOF},
    {"show", SHOW},
    {"some", SOME},
    {"stdin", STDIN},
    {"stdout", STDOUT},
    {"substring", SUBSTRING},
    {"table", TABLE},
    {"time", TIME},
    {"to", TO},
    {"trailing", TRAILING},
    {"transaction", TRANSACTION},
    {"trigger", TRIGGER},
    {"trim", TRIM},
    {"true", TRUE_P},
    {"trusted", TRUSTED},
    {"type", TYPE_P},
    {"union", UNION},
    {"unique", UNIQUE},
    {"until", UNTIL},
    {"update", UPDATE},
    {"user", USER},
    {"using", USING},
    {"vacuum", VACUUM},
    {"valid", VALID},
    {"values", VALUES},
    {"varchar", VARCHAR},
    {"varying", VARYING},
    {"verbose", VERBOSE},
    {"version", VERSION},
    {"view", VIEW},
    {"where", WHERE},
    {"with", WITH},
    {"work", WORK},
    {"year", YEAR_P},
    {"zone", ZONE},
};

ScanKeyword *
ScanKeywordLookup(char *text)
{
    ScanKeyword *low = &ScanKeywords[0];
    ScanKeyword *high = endof(ScanKeywords) - 1;
    ScanKeyword *middle;
    int            difference;

    while (low <= high)
    {
        middle = low + (high - low) / 2;
        difference = strcmp(middle->name, text);
        if (difference == 0)
            return (middle);
        else if (difference < 0)
            low = middle + 1;
        else
            high = middle - 1;
    }

    return (NULL);
}

#ifdef NOT_USED
char       *
AtomValueGetString(int atomval)
{
    ScanKeyword *low = &ScanKeywords[0];
    ScanKeyword *high = endof(ScanKeywords) - 1;
    int            keyword_list_length = (high - low);
    int            i;

    for (i = 0; i < keyword_list_length; i++)
        if (ScanKeywords[i].value == atomval)
            return (ScanKeywords[i].name);

    elog(ERROR, "AtomGetString called with bogus atom # : %d", atomval);
    return (NULL);
}

#endif

Re: [HACKERS] Re: subselects

От
Bruce Momjian
Дата:
>
> This is a multi-part message in MIME format.
> --------------130974A8F3C8025EB9E3F25C
> Content-Type: text/plain; charset=us-ascii
> Content-Transfer-Encoding: 7bit
>
> > I would like to have something done in parser near Jan 17 to get
> > subqueries working by Feb 1.
>
> Here are some changes to gram.y and to keywords.c which start to pass through
> subselect constructs. I won't commit until/unless you have a chance to look at it and
> agree that this is something close to the right direction to head.
>

Do you realize these are the files, and not context diffs?

--
Bruce Momjian
maillist@candle.pha.pa.us

Re: [HACKERS] Re: subselects

От
"Thomas G. Lockhart"
Дата:
> > > I would like to have something done in parser near Jan 17 to get
> > > subqueries working by Feb 1.
> >
> > Here are some changes to gram.y and to keywords.c which start to pass through
> > subselect constructs. I won't commit until/unless you have a chance to look at it and
> > agree that this is something close to the right direction to head.
> >
>
> Do you realize these are the files, and not context diffs?

Yup. Thought it would be easier for you, but probably should have sent a diff. Sorry.

                                              - Tom


Re: subselects

От
Bruce Momjian
Дата:
> I would like to have something done in parser near Jan 17 to get
> subqueries working by Feb 1. I vote for support of all standard
> things (1. - 3.) in parser right now - if there will be no time
> to implement something like (a, b, c) then optimizer will call
> elog(WARN) (oh, sorry, - elog(ERROR)).

First, let me say I am glad we are still on schedule for Feb 1.  I was
panicking because I thought we wouldn't make it in time.


> > > (is it allowable by standards ?) - in this case it's better
> > > to don't add tabA to 1st subselect but add tabA to second one
> > > and change tabA.col3 in 1st to reference col3 in 2nd subquery temp table -
> > > this gives us 2-tables join in 1st subquery instead of 3-tables join.
> > > (And I'm still not sure that using temp tables is best of what can be
> > > done in all cases...)
> >
> > I don't see any use for temp tables in subselects anymore.  After having
> > implemented UNIONS, I now see how much can be done in the upper
> > optimizer.  I see you just putting the subquery PLAN into the proper
> > place in the plan tree, with some proper JOIN nodes for IN, NOT IN.
>
> When saying about temp tables, I meant tables created by node Material
> for subquery plan. This is one of two ways - run subquery once for all
> possible upper plan tuples and then just join result table with upper
> query. Another way is re-run subquery for each upper query tuple,
> without temp table but may be with caching results by some ways.
> Actually, there is special case - when subquery can be alternatively
> formulated as joins, - but this is just special case.

This is interesting.  It really only applies for correlated subqueries,
and certainly it may help sometimes to just evaluate the subquery for
valid values that are going to come from the upper query than for all
possible values.  Perhaps we can use the 'cost' value of each query to
decide how to handle this.

>
> > > > In the parent query, to parse the WHERE clause, we create a new operator
> > > > type, called IN or NOT_IN, or ALL, where the left side is a Var, and the
> > >                                                ^^^^^^^^^^^^^^^^^^
> > > No. We have to handle (a,b,c) OP (select x, y, z ...) and
> > > '_a_constant_' OP (select ...) - I don't know is last in standards,
> > > Sybase has this.
> >
> > I have never seen this in my eight years of SQL.  Perhaps we can leave
> > this for later, maybe much later.
>
> Are you saying about (a, b, c) or about 'a_constant' ?
> Again, can someone comment on are they in standards or not ?
> Tom ?
> If yes then please add parser' support for them now...

OK, Thomas says it is, so we will put in as much code as we can to handle
it.

> Should we say users that subselect will work for standard data types only ?
> I don't see why subquery can't be used with ~, ~*, @@, ... operators, do you ?
> Is there difference between handling = ANY and ~ ANY ? I don't see any.
> Currently we can't get IN working properly for boxes (and may be for others too)
> and I don't like to try to resolve these problems now, but hope that someday
> we'll be able to do this. At the moment - just convert IN into = ANY and
> NOT IN into <> ALL in parser.

OK.

>
> (BTW, do you know how DISTINCT is implemented ? It doesn't use = but
> use type_out funcs and uses strcmp()... DISTINCT is standard SQL thing...)

I did not know that either.

> There is big difference between subqueries and queries in UNION -
> there are not dependences between UNION queries.

Yes, I know UNIONS are trivial compared to subselects.

>
> Ok, opened issues:
>
> 1. Is using upper query' vars in all subquery levels in standard ?
> 2. Is (a, b, c) OP (subselect) in standard ?
> 3. What types of expressions (Var, Const, ...) are allowed on the left
>    side of operator with subquery on the right ?
> 4. What types of operators should we support (=, >, ..., like, ~, ...) ?
>    (My vote for all boolean operators).
>
> And - did we get consensus on presentation subqueries stuff in Query,
> Expr and Var ?

OK, here are my concrete ideas on changes and structures.

I think we all agreed that Query needs new fields:

        Query *parentQuery;
        List *subqueries;

Maybe query level too, but I don't think so (see later ideas on Var).

We need a new Node structure, call it Sublink:

    int     linkType    (IN, NOTIN, ANY, EXISTS, OPERATOR...)
    Oid    operator    /* subquery must return single row */
    List    *lefthand;    /* parent stuff */
    Node     *subquery;    /* represents nodes from parser */
    Index    Subindex;    /* filled in to index Query->subqueries */

Of course, the names are just suggestions.  Every time we run through
the parsenodes of a query to create a Query* structure, when we do the
WHERE clause, if we come upon one of these Sublink nodes (created in the
parser), we move the supplied Query* in Sublink->subquery to a local
List variable, and we set Subquery->subindex to equal the index of the
new query, i.e. is it the first subquery we found, 1, or the second, 2,
etc.

After we have created the parent Query structure, we run through our
local List variable of subquery parsenodes we created above, and add
Query* entries to Query->subqueries.  In each subquery Query*, we set
the parentQuery pointer.

Also, when parsing the subqueries, we need to keep track of correlated
references.  I recommend we add a field to the Var structure:

    Index    sublevel;    /* range table reference:
                   = 0  current level of query
                   < 0  parent above this many levels
                   > 0  index into subquery list
                 */

This way, a Var node with sublevel 0 is the current level, and is true
in most cases.  This helps us not have to change much code.  sublevel =
-1 means it references the range table in the parent query. sublevel =
-2 means the parent's parent. sublevel = 2 means it references the range
table of the second entry in Query->subqueries.  Varno and varattno are
still meaningful.  Of course, we can't reference variables in the
subqueries from the parent in the parser code, but Vadim may want to.

When doing a Var lookup in the parser, we look in the current level
first, but if not found, if it is a subquery, we can look at the parent
and parent's parent to set the sublevel, varno, and varatno properly.

We create no phantom range table entries in the subquery, and no phantom
target list entries.   We can leave that all for the upper optimizer.

--
Bruce Momjian
maillist@candle.pha.pa.us

Re: [HACKERS] Re: subselects

От
"Thomas G. Lockhart"
Дата:
Here are context diffs of gram.y and keywords.c; sorry about sending the full files.
These start sending lists of arguments toward the backend from the parser to
implement row descriptors and subselects.

They should apply OK even over Bruce's recent changes...

                                             - Tom
*** ../src/backend/parser/gram.y.orig    Sat Jan 10 05:44:36 1998
--- ../src/backend/parser/gram.y    Sat Jan 10 19:29:37 1998
***************
*** 195,200 ****
--- 195,201 ----
                  having_clause
  %type <list>    row_descriptor, row_list
  %type <node>    row_expr
+ %type <str>        RowOp, row_opt
  %type <list>    OptCreateAs, CreateAsList
  %type <node>    CreateAsElement
  %type <value>    NumConst
***************
*** 242,248 ****
   */

  /* Keywords (in SQL92 reserved words) */
! %token    ACTION, ADD, ALL, ALTER, AND, AS, ASC,
          BEGIN_TRANS, BETWEEN, BOTH, BY,
          CASCADE, CAST, CHAR, CHARACTER, CHECK, CLOSE, COLLATE, COLUMN, COMMIT,
          CONSTRAINT, CREATE, CROSS, CURRENT, CURRENT_DATE, CURRENT_TIME,
--- 243,249 ----
   */

  /* Keywords (in SQL92 reserved words) */
! %token    ACTION, ADD, ALL, ALTER, AND, ANY, AS, ASC,
          BEGIN_TRANS, BETWEEN, BOTH, BY,
          CASCADE, CAST, CHAR, CHARACTER, CHECK, CLOSE, COLLATE, COLUMN, COMMIT,
          CONSTRAINT, CREATE, CROSS, CURRENT, CURRENT_DATE, CURRENT_TIME,
***************
*** 258,264 ****
          ON, OPTION, OR, ORDER, OUTER_P,
          PARTIAL, POSITION, PRECISION, PRIMARY, PRIVILEGES, PROCEDURE, PUBLIC,
          REFERENCES, REVOKE, RIGHT, ROLLBACK,
!         SECOND_P, SELECT, SET, SUBSTRING,
          TABLE, TIME, TIMESTAMP, TO, TRAILING, TRANSACTION, TRIM,
          UNION, UNIQUE, UPDATE, USING,
          VALUES, VARCHAR, VARYING, VERBOSE, VERSION, VIEW,
--- 259,265 ----
          ON, OPTION, OR, ORDER, OUTER_P,
          PARTIAL, POSITION, PRECISION, PRIMARY, PRIVILEGES, PROCEDURE, PUBLIC,
          REFERENCES, REVOKE, RIGHT, ROLLBACK,
!         SECOND_P, SELECT, SET, SOME, SUBSTRING,
          TABLE, TIME, TIMESTAMP, TO, TRAILING, TRANSACTION, TRIM,
          UNION, UNIQUE, UPDATE, USING,
          VALUES, VARCHAR, VARYING, VERBOSE, VERSION, VIEW,
***************
*** 2853,2866 ****
  /* Expressions using row descriptors
   * Define row_descriptor to allow yacc to break the reduce/reduce conflict
   *  with singleton expressions.
   */
  row_expr: '(' row_descriptor ')' IN '(' SubSelect ')'
                  {
!                     $$ = NULL;
                  }
          | '(' row_descriptor ')' NOT IN '(' SubSelect ')'
                  {
!                     $$ = NULL;
                  }
          | '(' row_descriptor ')' '=' '(' row_descriptor ')'
                  {
--- 2854,2878 ----
  /* Expressions using row descriptors
   * Define row_descriptor to allow yacc to break the reduce/reduce conflict
   *  with singleton expressions.
+  *
+  * Note that "SOME" is the same as "ANY" in syntax.
+  * - thomas 1998-01-10
   */
  row_expr: '(' row_descriptor ')' IN '(' SubSelect ')'
                  {
!                     $$ = makeA_Expr(OP, "=any", (Node *)$2, (Node *)$6);
                  }
          | '(' row_descriptor ')' NOT IN '(' SubSelect ')'
                  {
!                     $$ = makeA_Expr(OP, "<>any", (Node *)$2, (Node *)$7);
!                 }
!         | '(' row_descriptor ')' RowOp row_opt '(' SubSelect ')'
!                 {
!                     char *opr;
!                     opr = palloc(strlen($4)+strlen($5)+1);
!                     strcpy(opr, $4);
!                     strcat(opr, $5);
!                     $$ = makeA_Expr(OP, opr, (Node *)$2, (Node *)$7);
                  }
          | '(' row_descriptor ')' '=' '(' row_descriptor ')'
                  {
***************
*** 2880,2885 ****
--- 2892,2907 ----
                  }
          ;

+ RowOp:  '='                        { $$ = "="; }
+         | '<'                    { $$ = "<"; }
+         | '>'                    { $$ = ">"; }
+         ;
+
+ row_opt:  ALL                    { $$ = "all"; }
+         | ANY                    { $$ = "any"; }
+         | SOME                    { $$ = "any"; }
+         ;
+
  row_descriptor:  row_list ',' a_expr
                  {
                      $$ = lappend($1, $3);
***************
*** 3432,3441 ****
          ;

  in_expr:  SubSelect
!                 {
!                     elog(ERROR,"IN (SUBSELECT) not yet implemented");
!                     $$ = $1;
!                 }
          | in_expr_nodes
                  {    $$ = $1; }
          ;
--- 3454,3460 ----
          ;

  in_expr:  SubSelect
!                 {    $$ = makeA_Expr(OP, "=", saved_In_Expr, (Node *)$1); }
          | in_expr_nodes
                  {    $$ = $1; }
          ;
***************
*** 3449,3458 ****
          ;

  not_in_expr:  SubSelect
!                 {
!                     elog(ERROR,"NOT IN (SUBSELECT) not yet implemented");
!                     $$ = $1;
!                 }
          | not_in_expr_nodes
                  {    $$ = $1; }
          ;
--- 3468,3474 ----
          ;

  not_in_expr:  SubSelect
!                 {    $$ = makeA_Expr(OP, "<>", saved_In_Expr, (Node *)$1); }
          | not_in_expr_nodes
                  {    $$ = $1; }
          ;
*** ../src/backend/parser/keywords.c.orig    Mon Jan  5 07:51:33 1998
--- ../src/backend/parser/keywords.c    Sat Jan 10 19:22:07 1998
***************
*** 39,44 ****
--- 39,45 ----
      {"alter", ALTER},
      {"analyze", ANALYZE},
      {"and", AND},
+     {"any", ANY},
      {"append", APPEND},
      {"archive", ARCHIVE},
      {"as", AS},
***************
*** 178,183 ****
--- 179,185 ----
      {"set", SET},
      {"setof", SETOF},
      {"show", SHOW},
+     {"some", SOME},
      {"stdin", STDIN},
      {"stdout", STDOUT},
      {"substring", SUBSTRING},

Re: [HACKERS] Re: subselects

От
"Vadim B. Mikheev"
Дата:
Bruce Momjian wrote:
>
> We need a new Node structure, call it Sublink:
>
>         int     linkType        (IN, NOTIN, ANY, EXISTS, OPERATOR...)
>         Oid     operator        /* subquery must return single row */
>         List    *lefthand;      /* parent stuff */
>         Node    *subquery;      /* represents nodes from parser */
>         Index   Subindex;       /* filled in to index Query->subqueries */

Ok, I agreed that it's better to have new node and don't put subquery stuff
into Expr node.

int linkType
        is one of EXISTS, ANY, ALL, EXPR. EXPR is for the case of expression
        subqueries (following Sybase naming) which must return single row -
        (a, b, c) = (subquery).
        Note again, that there are no linkType for IN and NOTIN here.
        User' IN and NOT IN must be converted to = ANY and <> ALL by parser.

We need not in Oid operator! In all cases we need in

List *oper
        list of Oper nodes for each of a, b, c, ... and operator (=, ...)
        corresponding to data type of a, b, c, ...

List *lefthand
        is list of Var/Const nodes - representation of (a, b, c, ...)

What is Node *subquery ?
In optimizer we need either in Subindex (to get subquery from Query->subqueries
when beeing in Sublink) or in Node *subquery inside Sublink itself.
BTW, after some thought I don't see how Query->subqueries will be usefull.
So, may be just add bool hassubqueries to Query (and Query *parentQuery)
and use Query *subquery in Sublink, but not subindex ?

>
> Also, when parsing the subqueries, we need to keep track of correlated
> references.  I recommend we add a field to the Var structure:
>
>         Index   sublevel;       /* range table reference:
>                                    = 0  current level of query
>                                    < 0  parent above this many levels
>                                    > 0  index into subquery list
>                                  */
>
> This way, a Var node with sublevel 0 is the current level, and is true
> in most cases.  This helps us not have to change much code.  sublevel =
> -1 means it references the range table in the parent query. sublevel =
> -2 means the parent's parent. sublevel = 2 means it references the range
> table of the second entry in Query->subqueries.  Varno and varattno are
> still meaningful.  Of course, we can't reference variables in the
> subqueries from the parent in the parser code, but Vadim may want to.
                                                     ^^^^^^^^^^^^^^^^^
No. So, just use sublevel >= 0: 0 - current level, 1 - one level up, ...
sublevel is for optimizer only - executor will not use it.

>
> When doing a Var lookup in the parser, we look in the current level
> first, but if not found, if it is a subquery, we can look at the parent
> and parent's parent to set the sublevel, varno, and varatno properly.
>
> We create no phantom range table entries in the subquery, and no phantom
> target list entries.   We can leave that all for the upper optimizer.

Ok.

Vadim

Re: [HACKERS] Re: subselects

От
"Vadim B. Mikheev"
Дата:
Thomas G. Lockhart wrote:
>
> btw, to implement "(a,b,c) OP (d,e,f)" I made a new routine in the parser called
> makeRowExpr() which breaks this up into a sequence of "and" and/or "or" expressions.
> If lists are handled farther back, this routine should move to there also and the
> parser will just pass the lists. Note that some assumptions have to be made about the
> meaning of "(a,b) OP (c,d)", since usually we only have knowledge of the behavior of
> "a OP c". Easy for the standard SQL operators, unknown for others, but maybe it is OK
> to disallow those cases or to look for specific appearance of the operator to guess
> the behavior (e.g. if the operator has "<" or "=" or ">" then build as "and"s and if
> it has "<>" or "!" then build as "or"s.

Oh, god! I never thought about this!
Ok, I have to agree:

1. Only <, <=, =, >, >=, <> is allowed with subselects
2. Use OR's for <>, and so - we need in bool useor in SubLink
   for <>, <> ANY and <> ALL:

typedef struct SubLink {
    NodeTag        type;
    int        linkType; /* EXISTS, ALL, ANY, EXPR */
    bool        useor;    /* TRUE for <> */
    List            *lefthand; /* List of Var/Const nodes on the left */
    List            *oper;     /* List of Oper nodes */
    Query            *subquery; /* */
} SubLink;

Vadim

Re: [HACKERS] Re: subselects

От
"Vadim B. Mikheev"
Дата:
Thomas G. Lockhart wrote:
>
> btw, to implement "(a,b,c) OP (d,e,f)" I made a new routine in the parser called
> makeRowExpr() which breaks this up into a sequence of "and" and/or "or" expressions.
> If lists are handled farther back, this routine should move to there also and the
> parser will just pass the lists. Note that some assumptions have to be made about the
> meaning of "(a,b) OP (c,d)", since usually we only have knowledge of the behavior of
> "a OP c". Easy for the standard SQL operators, unknown for others, but maybe it is OK
> to disallow those cases or to look for specific appearance of the operator to guess
> the behavior (e.g. if the operator has "<" or "=" or ">" then build as "and"s and if
> it has "<>" or "!" then build as "or"s.

Sorry, I forgot something: is (a, b) OP (x, y) in standard ?
If not then I suggest to don't implement it at all and allow
(a, b) OP [ANY|ALL] (subselect) only.

Vadim

Re: [HACKERS] Re: subselects

От
"Thomas G. Lockhart"
Дата:
> > btw, to implement "(a,b,c) OP (d,e,f)" I made a new routine in the parser called
> > makeRowExpr() which breaks this up into a sequence of "and" and/or "or" expressions.
> > If lists are handled farther back, this routine should move to there also and the
> > parser will just pass the lists. Note that some assumptions have to be made about the
> > meaning of "(a,b) OP (c,d)", since usually we only have knowledge of the behavior of
> > "a OP c". Easy for the standard SQL operators, unknown for others, but maybe it is OK
> > to disallow those cases or to look for specific appearance of the operator to guess
> > the behavior (e.g. if the operator has "<" or "=" or ">" then build as "and"s and if
> > it has "<>" or "!" then build as "or"s.
>
> Sorry, I forgot something: is (a, b) OP (x, y) in standard ?

Yes. The problem wouldn't be very interesting otherwise :)

                                               - Tom

> If not then I suggest to don't implement it at all and allow
> (a, b) OP [ANY|ALL] (subselect) only.




Re: [HACKERS] Re: subselects

От
Bruce Momjian
Дата:
>
> Thomas G. Lockhart wrote:
> >
> > btw, to implement "(a,b,c) OP (d,e,f)" I made a new routine in the parser called
> > makeRowExpr() which breaks this up into a sequence of "and" and/or "or" expressions.
> > If lists are handled farther back, this routine should move to there also and the
> > parser will just pass the lists. Note that some assumptions have to be made about the
> > meaning of "(a,b) OP (c,d)", since usually we only have knowledge of the behavior of
> > "a OP c". Easy for the standard SQL operators, unknown for others, but maybe it is OK
> > to disallow those cases or to look for specific appearance of the operator to guess
> > the behavior (e.g. if the operator has "<" or "=" or ">" then build as "and"s and if
> > it has "<>" or "!" then build as "or"s.
>
> Oh, god! I never thought about this!
> Ok, I have to agree:
>
> 1. Only <, <=, =, >, >=, <> is allowed with subselects
> 2. Use OR's for <>, and so - we need in bool useor in SubLink
>    for <>, <> ANY and <> ALL:

Ah, but this is just a problem when there are multiple fields on the
left.

>
> typedef struct SubLink {
>     NodeTag        type;
>     int        linkType; /* EXISTS, ALL, ANY, EXPR */
>     bool        useor;    /* TRUE for <> */
>     List            *lefthand; /* List of Var/Const nodes on the left */
>     List            *oper;     /* List of Oper nodes */
>     Query            *subquery; /* */
> } SubLink;

--
Bruce Momjian
maillist@candle.pha.pa.us

Re: [HACKERS] Re: subselects

От
Bruce Momjian
Дата:
>
> Bruce Momjian wrote:
> >
> > We need a new Node structure, call it Sublink:
> >
> >         int     linkType        (IN, NOTIN, ANY, EXISTS, OPERATOR...)
> >         Oid     operator        /* subquery must return single row */
> >         List    *lefthand;      /* parent stuff */
> >         Node    *subquery;      /* represents nodes from parser */
> >         Index   Subindex;       /* filled in to index Query->subqueries */
>
> Ok, I agreed that it's better to have new node and don't put subquery stuff
> into Expr node.
>
> int linkType
>         is one of EXISTS, ANY, ALL, EXPR. EXPR is for the case of expression
>         subqueries (following Sybase naming) which must return single row -
>         (a, b, c) = (subquery).
>         Note again, that there are no linkType for IN and NOTIN here.
>         User' IN and NOT IN must be converted to = ANY and <> ALL by parser.
>
> We need not in Oid operator! In all cases we need in
>
> List *oper
>         list of Oper nodes for each of a, b, c, ... and operator (=, ...)
>         corresponding to data type of a, b, c, ...
>
> List *lefthand
>         is list of Var/Const nodes - representation of (a, b, c, ...)

I see, the opoids would be different for '=' if different variable types
are used in (a,b,c) in (subselect).  Got it.

>
> What is Node *subquery ?
> In optimizer we need either in Subindex (to get subquery from Query->subqueries
> when beeing in Sublink) or in Node *subquery inside Sublink itself.
> BTW, after some thought I don't see how Query->subqueries will be usefull.
> So, may be just add bool hassubqueries to Query (and Query *parentQuery)
> and use Query *subquery in Sublink, but not subindex ?

OK, I originally created it because the parser would have trouble
filling in a List* field in SelectStmt while it was parsing a WHERE
clause.  I decided to just stick the SelectStmt* into Sublink->subquery.

While we are going through the parse output to fill in the Query*, I
thought we should move the actual subquery parse output to a separate
place, and once the Query* was completed, spin through the saved
subquery parse list and stuff Query->subqueries with a list of Query*
for the subqueries.  I thought this would be easier, because we would
then have all the subqueries in a nice list that we can manage easier.

In fact, we can fill Query->subqueries with SelectStmt* as we process
the WHERE clause, then convert them to Query* at the end.

If you would rather keep the subquery Query* entries in the Sublink
structure, we can do that.  The only issue I see is that when you want
to get to them, you have to wade through the WHERE clause to find them.
For example, we will have to run the subquery Query* through the rewrite
system.  Right now, for UNION, I have a nice union List* in Query, and I
just spin through it in postgres.c for each Union query.  If we keep the
subquery Query* inside Sublink, we have to have some logic to go through
and find them.

If we just have an Index in Sublink to the Query->subqueries, we can use
the nth() macro to find them quite easily.

But it is up to you.  I really don't know how you are going to handle
things like:

    select *
    from taba
    where x = 3 and y = 5 and (z=6 or q in (select g from tabb ))

My logic was to break the problem down to single queries as much as
possible, so we would be breaking the problem up into pieces.  Whatever
is easier for you.

>
> >
> > Also, when parsing the subqueries, we need to keep track of correlated
> > references.  I recommend we add a field to the Var structure:
> >
> >         Index   sublevel;       /* range table reference:
> >                                    = 0  current level of query
> >                                    < 0  parent above this many levels
> >                                    > 0  index into subquery list
> >                                  */
> >
> > This way, a Var node with sublevel 0 is the current level, and is true
> > in most cases.  This helps us not have to change much code.  sublevel =
> > -1 means it references the range table in the parent query. sublevel =
> > -2 means the parent's parent. sublevel = 2 means it references the range
> > table of the second entry in Query->subqueries.  Varno and varattno are
> > still meaningful.  Of course, we can't reference variables in the
> > subqueries from the parent in the parser code, but Vadim may want to.
>                                                      ^^^^^^^^^^^^^^^^^
> No. So, just use sublevel >= 0: 0 - current level, 1 - one level up, ...
> sublevel is for optimizer only - executor will not use it.

OK, if you don't need to reference range tables DOWN in subqueries, we
can use positive numbers.

> > When doing a Var lookup in the parser, we look in the current level
> > first, but if not found, if it is a subquery, we can look at the parent
> > and parent's parent to set the sublevel, varno, and varatno properly.
> >
> > We create no phantom range table entries in the subquery, and no phantom
> > target list entries.   We can leave that all for the upper optimizer.
>
> Ok.
>
> Vadim
>


--
Bruce Momjian
maillist@candle.pha.pa.us

Re: [HACKERS] Re: subselects

От
Bruce Momjian
Дата:
> typedef struct SubLink {
>     NodeTag        type;
>     int        linkType; /* EXISTS, ALL, ANY, EXPR */
>     bool        useor;    /* TRUE for <> */
>     List            *lefthand; /* List of Var/Const nodes on the left */
>     List            *oper;     /* List of Oper nodes */
>     Query            *subquery; /* */
> } SubLink;

If you want Query* inside Sublink, rather than a separate Query* field,
this can be our SubLink structure.

--
Bruce Momjian
maillist@candle.pha.pa.us

Re: [HACKERS] Re: subselects

От
"Vadim B. Mikheev"
Дата:
Ok. I don't see how Query->subqueries could me help, but I foresee
that Query->sublinks can do it. Could you add this ?

Bruce Momjian wrote:
>
> >
> > What is Node *subquery ?
> > In optimizer we need either in Subindex (to get subquery from Query->subqueries
> > when beeing in Sublink) or in Node *subquery inside Sublink itself.
> > BTW, after some thought I don't see how Query->subqueries will be usefull.
> > So, may be just add bool hassubqueries to Query (and Query *parentQuery)
> > and use Query *subquery in Sublink, but not subindex ?
>
> OK, I originally created it because the parser would have trouble
> filling in a List* field in SelectStmt while it was parsing a WHERE
> clause.  I decided to just stick the SelectStmt* into Sublink->subquery.
>
> While we are going through the parse output to fill in the Query*, I
> thought we should move the actual subquery parse output to a separate
> place, and once the Query* was completed, spin through the saved
> subquery parse list and stuff Query->subqueries with a list of Query*
> for the subqueries.  I thought this would be easier, because we would
> then have all the subqueries in a nice list that we can manage easier.
>
> In fact, we can fill Query->subqueries with SelectStmt* as we process
> the WHERE clause, then convert them to Query* at the end.
>
> If you would rather keep the subquery Query* entries in the Sublink
> structure, we can do that.  The only issue I see is that when you want
> to get to them, you have to wade through the WHERE clause to find them.
> For example, we will have to run the subquery Query* through the rewrite
> system.  Right now, for UNION, I have a nice union List* in Query, and I
> just spin through it in postgres.c for each Union query.  If we keep the
> subquery Query* inside Sublink, we have to have some logic to go through
> and find them.
>
> If we just have an Index in Sublink to the Query->subqueries, we can use
> the nth() macro to find them quite easily.
>
> But it is up to you.  I really don't know how you are going to handle
> things like:
>
>         select *
>         from taba
>         where x = 3 and y = 5 and (z=6 or q in (select g from tabb ))

No problems.

>
> My logic was to break the problem down to single queries as much as
> possible, so we would be breaking the problem up into pieces.  Whatever
> is easier for you.

Vadim

Re: [HACKERS] Re: subselects

От
"Vadim B. Mikheev"
Дата:
Thomas G. Lockhart wrote:
>
> > > btw, to implement "(a,b,c) OP (d,e,f)" I made a new routine in the parser called
> > > makeRowExpr() which breaks this up into a sequence of "and" and/or "or" expressions.
> > > If lists are handled farther back, this routine should move to there also and the
> > > parser will just pass the lists. Note that some assumptions have to be made about the
> > > meaning of "(a,b) OP (c,d)", since usually we only have knowledge of the behavior of
> > > "a OP c". Easy for the standard SQL operators, unknown for others, but maybe it is OK
> > > to disallow those cases or to look for specific appearance of the operator to guess
> > > the behavior (e.g. if the operator has "<" or "=" or ">" then build as "and"s and if
> > > it has "<>" or "!" then build as "or"s.
> >
> > Sorry, I forgot something: is (a, b) OP (x, y) in standard ?
>
> Yes. The problem wouldn't be very interesting otherwise :)

Could we restrict OPs to standard ones (like we do for subselects) - I don't
like assumption about ORs for operators with "!" ?
"Assume as little as possible" is good rule...

Vadim

Re: [HACKERS] Re: subselects

От
"Thomas G. Lockhart"
Дата:
> > > > Note that some assumptions have to be made about the
> > > > meaning of "(a,b) OP (c,d)", since usually we only have knowledge of the behavior of
> > > > "a OP c". Easy for the standard SQL operators, unknown for others, but maybe it is OK
> > > > to disallow those cases or to look for specific appearance of the operator to guess
> > > > the behavior (e.g. if the operator has "<" or "=" or ">" then build as "and"s and if
> > > > it has "<>" or "!" then build as "or"s.
>
> Could we restrict OPs to standard ones (like we do for subselects) - I don't
> like assumption about ORs for operators with "!" ?
> "Assume as little as possible" is good rule...

Yes, I agree. The suggestion about "!" was made without thinking very hard just to raise the
possibility. Extending to other operators in a reliable way is an interesting problem, but is
not required and can be explicitly disallowed for now.

                                                - Tom


Re: [HACKERS] Re: subselects

От
Bruce Momjian
Дата:
>
> Ok. I don't see how Query->subqueries could me help, but I foresee
> that Query->sublinks can do it. Could you add this ?

OK, so instead of moving the query out of the SubLink structure, you
want the Query* in the Sublink structure, and a List* of SubLink
pointers in the query structure?

    Query
    {
        ...
        List *sublink;  /* list of pointers to Sublinks
        ...
    }

I can do that.  Let me know.
--
Bruce Momjian
maillist@candle.pha.pa.us

Re: [HACKERS] Re: subselects

От
"Vadim B. Mikheev"
Дата:
Bruce Momjian wrote:
>
> >
> > Ok. I don't see how Query->subqueries could me help, but I foresee
> > that Query->sublinks can do it. Could you add this ?
>
> OK, so instead of moving the query out of the SubLink structure, you
> want the Query* in the Sublink structure, and a List* of SubLink
> pointers in the query structure?

Yes.

>
>         Query
>         {
>                 ...
>                 List *sublink;  /* list of pointers to Sublinks
>                 ...
>         }
>
> I can do that.  Let me know.

Thanks!

Are there any opened issues ?

Vadim

Re: [HACKERS] Re: subselects

От
Bruce Momjian
Дата:
> typedef struct SubLink {
>     NodeTag        type;
>     int        linkType; /* EXISTS, ALL, ANY, EXPR */
>     bool        useor;    /* TRUE for <> */
>     List            *lefthand; /* List of Var/Const nodes on the left */
>     List            *oper;     /* List of Oper nodes */
>     Query            *subquery; /* */
> } SubLink;

OK, we add this structure above.  During parsing, *subquery actually
will hold Node *parsetree, not Query *.

And add to Query:

    bool    hasSubLinks;

Also need a function to return a List* of SubLink*.  I just did a
similar thing with Aggreg*.  And Var gets:

    int uplevels;

Is that it?


--
Bruce Momjian
maillist@candle.pha.pa.us

Re: [HACKERS] Re: subselects

От
Bruce Momjian
Дата:
>
> Bruce Momjian wrote:
> >
> > >
> > > Ok. I don't see how Query->subqueries could me help, but I foresee
> > > that Query->sublinks can do it. Could you add this ?
> >
> > OK, so instead of moving the query out of the SubLink structure, you
> > want the Query* in the Sublink structure, and a List* of SubLink
> > pointers in the query structure?
>
> Yes.
>
> >
> >         Query
> >         {
> >                 ...
> >                 List *sublink;  /* list of pointers to Sublinks
> >                 ...
> >         }
> >
> > I can do that.  Let me know.
>
> Thanks!
>
> Are there any opened issues ?

OK, what do you need me to do.  Do you want me to create the Sublink
support stuff, fill them in in the parser, and pass them through the
rewrite section and into the optimizer.  I will prepare a list of
changes.


--
Bruce Momjian
maillist@candle.pha.pa.us

Re: [HACKERS] Re: subselects

От
"Vadim B. Mikheev"
Дата:
Bruce Momjian wrote:
>
> > typedef struct SubLink {
> >       NodeTag         type;
> >       int             linkType; /* EXISTS, ALL, ANY, EXPR */
> >       bool            useor;    /* TRUE for <> */
> >       List            *lefthand; /* List of Var/Const nodes on the left */
> >       List            *oper;     /* List of Oper nodes */
> >       Query           *subquery; /* */
> > } SubLink;
>
> OK, we add this structure above.  During parsing, *subquery actually
> will hold Node *parsetree, not Query *.
            ^^^^^^^^^^^^^^^
But optimizer will get node Query here, yes ?

>
> And add to Query:
>
>         bool    hasSubLinks;
>
> Also need a function to return a List* of SubLink*.  I just did a
> similar thing with Aggreg*.  And Var gets:
>
>         int uplevels;
>
> Is that it?

Yes.

Vadim

Re: [HACKERS] Re: subselects

От
"Vadim B. Mikheev"
Дата:
Bruce Momjian wrote:
>
> >
> > Are there any opened issues ?
>
> OK, what do you need me to do.  Do you want me to create the Sublink
> support stuff, fill them in in the parser, and pass them through the
> rewrite section and into the optimizer.  I will prepare a list of
> changes.

Please do this. I'm ready to start coding of things in optimizer.

Vadim