backport stable into oldstable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 05 May 2011 10:50:51 +0200
changeset 619 62ad98cc1e64
parent 480 4025f1f02d1d (current diff)
parent 615 7d368dd97930 (diff)
child 620 b81599d59bb1
backport stable into oldstable
DEPENDS
--- a/.hgtags	Thu Oct 15 20:20:29 2009 +0200
+++ b/.hgtags	Thu May 05 10:50:51 2011 +0200
@@ -41,3 +41,24 @@
 bf6e2008180dab292fe49c70a47883852fb8d46b rql-debian-version-0.22.2-1
 42004883d4cd6b42f7898a93df8537d5b87a662d rql-version-0.23.0
 7dd29e42751ebebae4b50126dfb2071d9b2e8de1 rql-debian-version-0.23.0-1
+5cb31b7a463ea8fcc56da4e768648a2f818ec0ee rql-version-0.24.0
+4f8562728585d53053e914171180e623e73ac235 rql-debian-version-0.24.0-1
+4025f1f02d1da65d26eada37708409984942c432 oldstable
+3d59f6b1cbb90278f3b4374dce36b6e31c7e9884 rql-version-0.25.0
+360a6c3a48393f8d5353198d45fbcf25f9ef5369 rql-debian-version-0.25.0-1
+ae4cba1cf0240c615a8e78c94d81fa05e5ad8bc9 rql-version-0.26.0
+677736b455f5fb7a31882e37165dbd4879c4bf11 rql-debian-version-0.26.0-1
+42ae413193a8403a749fb1a206a86cec09f5efdb rql-version-0.26.1
+3142115086127f3e9995081fff3fef3d420838cf rql-debian-version-0.26.1-1
+7d5bef1742bc302309668982af10409bcc96eadf rql-version-0.26.2
+cb66c5a9918dd8958dd3cdf48f8bdd0c2786b76a rql-debian-version-0.26.2-1
+7fb422fc2032ecc5a93528ed382e083b212b1cbf rql-version-0.26.3
+aca033de456a6b526045f9be0dbdb770e67912ab rql-debian-version-0.26.3-1
+bcf24f8a29c07146220816565a132ba148cdf82a rql-version-0.26.4
+88b739e85c615fc41a964f39e853fe77aaf3f207 rql-debian-version-0.26.4-1
+7a1df18b3a3ed41aa49d4baf10246a8e2e65a7d6 rql-version-0.26.6
+23bd1f36ec77f30cd525327d408ef6836f88eb24 rql-debian-version-0.26.6-1
+3c59bf663ec78dad82016b43f58348d5e35058ad rql-version-0.27.0
+0a5a70c34c65fccaf64603613d5d295b332e85cb rql-debian-version-0.27.0-1
+ae02408da51e63aa2d1be6ac7170d77060bd0910 rql-version-0.28.0
+21e94bc12c1fcb7f97826fe6aae5dbe62cc4bd06 rql-debian-version-0.28.0-1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING	Thu May 05 10:50:51 2011 +0200
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING.LESSER	Thu May 05 10:50:51 2011 +0200
@@ -0,0 +1,510 @@
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+	51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard.  To achieve this, non-free programs must
+be allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at least
+    three years, to give the same user the materials specified in
+    Subsection 6a, above, for a charge no more than the cost of
+    performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James
+  Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
--- a/ChangeLog	Thu Oct 15 20:20:29 2009 +0200
+++ b/ChangeLog	Thu May 05 10:50:51 2011 +0200
@@ -1,6 +1,109 @@
 ChangeLog for RQL
 =================
 
+--
+* suport != operator for non equality
+
+2011-01-12  --  0.28.0
+    * enhance rewrite_shared_optional so one can specify where the new identity
+      relation should be added (used by cw multi-sources planner)
+
+
+
+2010-10-13  --  0.27.0
+    * select.undefine_variable properly cleanup solutions (and restore them on
+      undo)
+
+    * fix potential crash in Referenceable.get_description
+
+    * introduce make_constant_restriction function, useful to build a
+      restriction without adding it yet to the tree
+
+
+
+2010-09-10  --  0.26.6
+    * enhance bad rql query detection with ordered distinct (can't use distinct
+      if an attribute is selected and we sort on another attribute)
+
+    * fix subquery_selection_index responsability mess-up: it wasn't doing what
+      it should have done (see cw.rset related_entity implementation)
+
+    * consider subquery aliases in Select.clean_solutions
+
+    * add constraint package to setuptools dependencies so we've fallback
+      opportunity if gecode is not installed
+
+    * fix setuptools dependency on yapps by forcing install of our custom
+      package, so it don't try to install pypi's one which doesn't work well
+      with both pip and easy_install
+
+
+
+2010-08-02  --  0.26.5
+    * fix solutions computation crash with some query using sub-queries (closes #37423)
+
+
+
+2010-07-28  --  0.26.4
+    * fix re-annotation pb: some stinfo keys were not properly reinitialized
+      which may cause pb later (at sql generation time for instance)
+
+
+
+2010-06-21  --  0.26.3
+    * support for node from having in Select.remove
+
+    * enhanced Select.replace method
+
+    * rql st checker now checks function avaibility according to backend (if specified)
+
+
+
+2010-06-11  --  0.26.2
+    * totally remove 'IS' operator
+
+    * replace get_variable_variables by get_variable_indicies
+
+    * fix rule order so 'HAVING (X op Y)' is now parseable while 'HAVING (1+2) op Y' isn't anymore parseable
+
+    * fix simplification bug with ored uid relations
+
+
+
+2010-06-04  --  0.26.1
+    * normalize NOT() to NOT EXISTS() when it makes sense
+
+    * fix grammar bug in HAVING clause: should all arbitrary expression and fix to deal with IN() hack
+
+
+
+2010-04-20  --  0.26.0
+    * setuptools support
+
+    * variable and column alias stinfo optimization
+
+    * analyzer return key used in args to unambiguify solutions
+
+    * rewrite_shared_optional refactoring
+
+
+
+2010-03-16  --  0.25.0
+    * depends on logilab-database
+
+    * raise BadRQLQuery when using optional on attribute relation
+
+
+
+2010-02-10  --  0.24.0
+    * update to yams 0.27 api
+
+    * fully dropped mx support
+
+    * various bugs fixed
+
+
+
 2009-08-26  --  0.23.0
     * Union.locate_subquery now return a 2-uple (select subquery, column index in the subquery)
 
--- a/DEPENDS	Thu Oct 15 20:20:29 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-python-logilab-common
-python-constraint (>= 0.2.7)
--- a/MANIFEST.in	Thu Oct 15 20:20:29 2009 +0200
+++ b/MANIFEST.in	Thu May 05 10:50:51 2011 +0200
@@ -10,7 +10,8 @@
 include README
 include TODO 
 include ChangeLog
-include DEPENDS
+include COPYING 
+include COPYING.LESSER 
 include data/gecode_version.cc
 
 recursive-include tools *.py *.rql
--- a/__init__.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/__init__.py	Thu May 05 10:50:51 2011 +0200
@@ -1,17 +1,21 @@
-"""RQL library (implementation independant).
-
-:copyright:
-  2001-2009 `LOGILAB S.A. <http://www.logilab.fr>`_ (Paris, FRANCE),
-  all rights reserved.
-
-:contact:
-  http://www.logilab.org/project/rql --
-  mailto:python-projects@logilab.org
-
-:license:
-  `General Public License version 2
-  <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>`_
-"""
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""RQL library (implementation independant)."""
 __docformat__ = "restructuredtext en"
 
 from rql.__pkginfo__ import version as __version__
@@ -34,7 +38,7 @@
       - comparison of two queries
     """
     def __init__(self, schema, uid_func_mapping=None, special_relations=None,
-                 resolver_class=None):
+                 resolver_class=None, backend=None):
         # chech schema
         #for e_type in REQUIRED_TYPES:
         #    if not schema.has_entity(e_type):
@@ -45,7 +49,7 @@
         if uid_func_mapping:
             for key in uid_func_mapping:
                 special_relations[key] = 'uid'
-        self._checker = RQLSTChecker(schema)
+        self._checker = RQLSTChecker(schema, special_relations, backend)
         self._annotator = RQLSTAnnotator(schema, special_relations)
         self._analyser_lock = threading.Lock()
         if resolver_class is None:
@@ -72,6 +76,12 @@
         self._annotator.schema = schema
         self._analyser.set_schema(schema)
 
+    def get_backend(self):
+        return self._checker.backend
+    def set_backend(self, backend):
+        self._checker.backend = backend
+    backend = property(get_backend, set_backend)
+
     def parse(self, rqlstring, annotate=True):
         """Return a syntax tree created from a RQL string."""
         rqlst = parse(rqlstring, False)
@@ -93,8 +103,8 @@
         """
         self._analyser_lock.acquire()
         try:
-            self._analyser.visit(rqlst, uid_func_mapping, kwargs,
-                                 debug)
+            return self._analyser.visit(rqlst, uid_func_mapping, kwargs,
+                                        debug)
         finally:
             self._analyser_lock.release()
 
@@ -128,19 +138,16 @@
         for subquery in select.with_:
             for select in subquery.query.children:
                 self._simplify(select)
+        rewritten = False
         for var in select.defined_vars.values():
             stinfo = var.stinfo
-            if stinfo['constnode'] and not stinfo['blocsimplification']:
-                #assert len(stinfo['uidrels']) == 1, var
-                uidrel = stinfo['uidrels'].pop()
+            if stinfo['constnode'] and not stinfo.get('blocsimplification'):
+                uidrel = stinfo['uidrel']
                 var = uidrel.children[0].variable
                 vconsts = []
                 rhs = uidrel.children[1].children[0]
-                #from rql.nodes import Constant
-                #assert isinstance(rhs, nodes.Constant), rhs
                 for vref in var.references():
                     rel = vref.relation()
-                    #assert vref.parent
                     if rel is None:
                         term = vref
                         while not term.parent is select:
@@ -163,22 +170,19 @@
                             select.groupby[select.groupby.index(vref)] = rhs
                             rhs.parent = select
                     elif rel is uidrel:
-                        # drop this relation
-                        rel.parent.remove(rel)
+                        uidrel.parent.remove(uidrel)
                     elif rel.is_types_restriction():
-                        stinfo['typerels'].remove(rel)
-                        rel.parent.remove(rel)
-                    elif rel in stinfo['uidrels']:
-                        # XXX check equivalence not necessary else we wouldn't be here right?
-                        stinfo['uidrels'].remove(rel)
+                        stinfo['typerel'] = None
                         rel.parent.remove(rel)
                     else:
                         rhs = copy_uid_node(select, rhs, vconsts)
                         vref.parent.replace(vref, rhs)
                 del select.defined_vars[var.name]
+                stinfo['uidrel'] = None
+                rewritten = True
                 if vconsts:
                     select.stinfo['rewritten'][var.name] = vconsts
-        if select.stinfo['rewritten'] and select.solutions:
+        if rewritten and select.solutions:
             select.clean_solutions()
 
     def compare(self, rqlstring1, rqlstring2):
--- a/__pkginfo__.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/__pkginfo__.py	Thu May 05 10:50:51 2011 +0200
@@ -1,24 +1,34 @@
 # pylint: disable-msg=W0622
-"""RQL packaging information.
-
-:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
-"""
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""RQL packaging information."""
 __docformat__ = "restructuredtext en"
 
 modname = "rql"
-numversion = (0, 23, 0)
+numversion = (0, 28, 0)
 version = '.'.join(str(num) for num in numversion)
 
 license = 'LGPL'
-copyright = '''Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE).
-http://www.logilab.fr/ -- mailto:contact@logilab.fr'''
 
-author = "Sylvain Thenault"
+author = "Logilab"
 author_email = "contact@logilab.fr"
 
-short_desc = "relationship query language (RQL) utilities"
+description = "relationship query language (RQL) utilities"
 long_desc = """A library providing the base utilities to handle RQL queries,
 such as a parser, a type inferencer.
 """
@@ -26,12 +36,6 @@
 ftp = "ftp://ftp.logilab.org/pub/rql"
 
 
-# debianize info
-debian_maintainer = 'Sylvain Thenault'
-debian_maintainer_email = 'sylvain.thenault@logilab.fr'
-pyversions = ['2.4']
-
-
 import os, subprocess, sys
 from distutils.core import Extension
 
@@ -39,7 +43,7 @@
 
 def gecode_version():
     import os, subprocess
-    version = [1, 3, 1]
+    version = [3,3,1]
     if os.path.exists('data/gecode_version.cc'):
         try:
             res = os.system("g++ -o gecode_version data/gecode_version.cc")
@@ -58,17 +62,31 @@
 if sys.platform != 'win32':
     ext_modules = [Extension('rql_solve',
                              ['gecode_solver.cpp'],
-                              libraries=['gecodeint', 'gecodekernel', 
-                                         'gecodesearch','gecodesupport'],
+                              libraries=['gecodeint', 'gecodekernel', 'gecodesearch',],
                              extra_compile_args=['-DGE_VERSION=%s' % GECODE_VERSION],
                          )
                    ]
 else:
     ext_modules = [ Extension('rql_solve',
                               ['gecode_solver.cpp'],
-                              libraries=['gecodeint', 'gecodekernel', 
-                                         'gecodesearch','gecodesupport'],
-                              extra_compile_args=['-DGE_VERSION=%s' % GECODE_VERSION],
-                              extra_link_args=['-static-libgcc'],
+                              libraries=['GecodeInt-3-3-1-r-x86',
+                                         'GecodeKernel-3-3-1-r-x86',
+                                         'GecodeSearch-3-3-1-r-x86',
+                                         'GecodeSupport-3-3-1-r-x86',
+                                         ],
+                              extra_compile_args=['/DGE_VERSION=%s' % GECODE_VERSION, '/EHsc'],
+                              #extra_link_args=['-static-libgcc'],
                               )
                     ]
+
+install_requires = [
+    'logilab-common >= 0.47.0',
+    'logilab-database',
+    'yapps == 2.1.1', # XXX to ensure we don't use the broken pypi version
+    'constraint', # fallback if the gecode compiled module is missing
+    ]
+
+# links to download yapps2 package that is not (yet) registered in pypi
+dependency_links = [
+    "http://ftp.logilab.org/pub/yapps/yapps2-2.1.1.zip#egg=yapps-2.1.1",
+    ]
--- a/_exceptions.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/_exceptions.py	Thu May 05 10:50:51 2011 +0200
@@ -1,9 +1,22 @@
-"""Exceptions used in the RQL package.
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""Exceptions used in the RQL package."""
 
-:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
-"""
 __docformat__ = "restructuredtext en"
 
 class RQLException(Exception):
--- a/analyze.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/analyze.py	Thu May 05 10:50:51 2011 +0200
@@ -1,14 +1,26 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 """Analyze of the RQL syntax tree to get possible types for RQL variables.
 
-:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
 from cStringIO import StringIO
-import warnings
-warnings.filterwarnings(action='ignore', module='logilab.constraint.propagation')
 
 from rql import TypeResolverException, nodes
 from pprint import pprint
@@ -20,6 +32,8 @@
     import rql_solve
 except ImportError:
     rql_solve = None
+    import warnings
+    warnings.filterwarnings(action='ignore', module='logilab.constraint.propagation')
     from logilab.constraint import Repository, Solver, fd
 
     # Gecode solver not available
@@ -340,9 +354,10 @@
             assert cst.type
             if cst.type == 'Substitute':
                 eid = self.kwargs[cst.value]
+                self.deambiguifiers.add(cst.value)
             else:
                 eid = cst.value
-            cst.uidtype = self.uid_func(eid)
+            cst.uidtype = self.uid_func(cst.eval(self.kwargs))
             types.add(cst.uidtype)
         return types
 
@@ -362,18 +377,23 @@
                     alltypes.add(targettypes)
         else:
             alltypes = get_target_types()
-
-        constraints.var_has_types( var, [ str(t) for t in alltypes] )
+        domain = constraints.domains[var]
+        constraints.var_has_types( var, [str(t) for t in alltypes if t in domain] )
 
     def visit(self, node, uid_func_mapping=None, kwargs=None, debug=False):
         # FIXME: not thread safe
         self.debug = debug
-        if uid_func_mapping:
+        if uid_func_mapping is not None:
             assert len(uid_func_mapping) <= 1
             self.uid_func_mapping = uid_func_mapping
             self.uid_func = uid_func_mapping.values()[0]
         self.kwargs = kwargs
+        self.deambiguifiers = set()
         self._visit(node)
+        if uid_func_mapping is not None:
+            self.uid_func_mapping = None
+            self.uid_func = None
+        return self.deambiguifiers
 
     def visit_union(self, node):
         for select in node.children:
@@ -489,18 +509,26 @@
                         samevar = True
                     else:
                         rhsvars.append(v.name)
+            lhsdomain = constraints.domains[lhsvar]
             if rhsvars:
                 s2 = '=='.join(rhsvars)
+                # filter according to domain necessary for column aliases
+                rhsdomain = constraints.domains[rhsvars[0]]
                 res = []
                 for fromtype, totypes in rschema.associations():
-                    res.append( [ ( [lhsvar], [str(fromtype)]), (rhsvars, [ str(t) for t in totypes]) ] )
+                    if not fromtype in lhsdomain:
+                        continue
+                    ptypes = [str(t) for t in totypes if t in rhsdomain]
+                    res.append( [ ( [lhsvar], [str(fromtype)]), (rhsvars, ptypes) ] )
                 constraints.or_and( res )
             else:
-                constraints.var_has_types( lhsvar, [ str(subj) for subj in rschema.subjects()] )
+                ptypes = [str(subj) for subj in rschema.subjects()
+                          if subj in lhsdomain]
+                constraints.var_has_types( lhsvar, ptypes )
             if samevar:
                 res = []
                 for fromtype, totypes in rschema.associations():
-                    if not fromtype in totypes:
+                    if not (fromtype in totypes and fromtype in lhsdomain):
                         continue
                     res.append(str(fromtype))
                 constraints.var_has_types( lhsvar, res )
--- a/base.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/base.py	Thu May 05 10:50:51 2011 +0200
@@ -1,11 +1,25 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 """Base classes for RQL syntax tree nodes.
 
 Note: this module uses __slots__ to limit memory usage.
+"""
 
-:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
-"""
 __docformat__ = "restructuredtext en"
 
 class BaseNode(object):
@@ -43,13 +57,6 @@
         """
         return self.parent.scope
 
-    @property
-    def sqlscope(self):
-        """Return the SQL scope node to which this node belong (eg Select,
-        Exists or Not node)
-        """
-        return self.parent.sqlscope
-
     def get_nodes(self, klass):
         """Return the list of nodes of a given class in the subtree.
 
@@ -133,9 +140,14 @@
         child.parent = self
 
     def remove(self, child):
-        """remove a child node"""
-        self.children.remove(child)
+        """Remove a child node. Return the removed node, its old parent and
+        index in the children list.
+        """
+        index = self.children.index(child)
+        del self.children[index]
+        parent = child.parent
         child.parent = None
+        return child, parent, index
 
     def insert(self, index, child):
         """insert a child node"""
@@ -148,7 +160,7 @@
         self.children.pop(i)
         self.children.insert(i, new_child)
         new_child.parent = self
-
+        return old_child, self, i
 
 class BinaryNode(Node):
     __slots__ = ()
@@ -162,8 +174,8 @@
 
     def remove(self, child):
         """Remove the child and replace this node with the other child."""
-        self.children.remove(child)
-        self.parent.replace(self, self.children[0])
+        index = self.children.index(child)
+        return self.parent.replace(self, self.children[not index])
 
     def get_parts(self):
         """Return the left hand side and the right hand side of this node."""
--- a/compare.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/compare.py	Thu May 05 10:50:51 2011 +0200
@@ -1,8 +1,22 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 """Comparing syntax trees.
 
-:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/debian.hardy/control	Thu Oct 15 20:20:29 2009 +0200
+++ b/debian.hardy/control	Thu May 05 10:50:51 2011 +0200
@@ -12,7 +12,8 @@
 Package: python-rql
 Architecture: any
 XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, ${misc:Depends}, ${shlibs:Depends}, python-logilab-common (>= 0.35.3-1), yapps2-runtime
+Depends: ${python:Depends}, ${misc:Depends}, ${shlibs:Depends}, python-logilab-common (>= 0.35.3-1), yapps2-runtime, python-logilab-database
+Conflicts: cubicweb-common (< 3.8.0)
 Provides: ${python:Provides}
 Description: relationship query language (RQL) utilities
  A library providing the base utilities to handle RQL queries,
--- a/debian.lenny/control	Thu Oct 15 20:20:29 2009 +0200
+++ b/debian.lenny/control	Thu May 05 10:50:51 2011 +0200
@@ -2,17 +2,17 @@
 Section: python
 Priority: optional
 Maintainer: Logilab Packaging Team <contact@logilab.fr>
-Uploaders: Sylvain Thenault <sylvain.thenault@logilab.fr>, Ludovic Aubry <ludovic.aubry@logilab.fr>, Nicolas Chauvat <nicolas.chauvat@logilab.fr>
-Build-Depends: debhelper (>= 5.0.37.1), python-all-dev (>=2.4), python-all (>=2.4), libgecode12-dev, python-sphinx, g++
-Build-Depends-Indep: python-support
+Uploaders: Sylvain Thenault <sylvain.thenault@logilab.fr>, Nicolas Chauvat <nicolas.chauvat@logilab.fr>
+Build-Depends: debhelper (>= 5.0.37.1), python-support, python-all-dev (>=2.4), python-all (>=2.4), libgecode12-dev, python-sphinx, g++ (>= 4)
 XS-Python-Version: >= 2.4
-Standards-Version: 3.8.0
+Standards-Version: 3.9.1
 Homepage: http://www.logilab.org/project/rql
 
 Package: python-rql
 Architecture: any
 XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, ${misc:Depends}, ${shlibs:Depends}, python-logilab-common (>= 0.35.3-1), python-constraint (>= 0.4.0-1), yapps2-runtime
+Depends: ${python:Depends}, ${misc:Depends}, ${shlibs:Depends}, python-logilab-common (>= 0.35.3-1), yapps2-runtime, python-logilab-database
+Conflicts: cubicweb-common (<= 3.8.3)
 Provides: ${python:Provides}
 Description: relationship query language (RQL) utilities
  A library providing the base utilities to handle RQL queries,
--- a/debian/changelog	Thu Oct 15 20:20:29 2009 +0200
+++ b/debian/changelog	Thu May 05 10:50:51 2011 +0200
@@ -1,3 +1,83 @@
+rql (0.28.0-2) UNRELEASED; urgency=low
+
+  * debian/control:
+    - remove Ludovic Aubry from Uploaders
+  * lintian fixes
+
+ -- 
+
+rql (0.28.0-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Wed, 12 Jan 2011 09:21:26 +0100
+
+rql (0.27.0-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Wed, 13 Oct 2010 07:55:35 +0200
+
+rql (0.26.6-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Fri, 10 Sep 2010 11:09:22 +0200
+
+rql (0.26.5-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Mon, 02 Aug 2010 14:22:00 +0200
+
+rql (0.26.4-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Wed, 28 Jul 2010 10:29:47 +0200
+
+rql (0.26.3-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Mon, 21 Jun 2010 09:34:41 +0200
+
+rql (0.26.2-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Fri, 11 Jun 2010 10:04:46 +0200
+
+rql (0.26.1-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Mon, 07 Jun 2010 10:12:50 +0200
+
+rql (0.26.0-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Tue, 20 Apr 2010 11:10:27 +0200
+
+rql (0.25.0-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Tue, 16 Mar 2010 13:41:03 +0100
+
+rql (0.24.1-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Pierre-Yves David <pierre-yves.david@logilab.fr>  Thu, 04 Mar 2010 12:08:01 +0100
+
+rql (0.24.0-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Wed, 10 Feb 2010 08:34:01 +0100
+
 rql (0.23.0-1) unstable; urgency=low
 
   * new upstream release
--- a/debian/control	Thu Oct 15 20:20:29 2009 +0200
+++ b/debian/control	Thu May 05 10:50:51 2011 +0200
@@ -2,17 +2,17 @@
 Section: python
 Priority: optional
 Maintainer: Logilab Packaging Team <contact@logilab.fr>
-Uploaders: Sylvain Thenault <sylvain.thenault@logilab.fr>, Ludovic Aubry <ludovic.aubry@logilab.fr>, Nicolas Chauvat <nicolas.chauvat@logilab.fr>
-Build-Depends: debhelper (>= 5.0.37.1), python-all-dev (>=2.4), python-all (>=2.4), libgecode-dev, python-sphinx, g++
-Build-Depends-Indep: python-support
-XS-Python-Version: >= 2.4
-Standards-Version: 3.8.0
+Uploaders: Sylvain Thenault <sylvain.thenault@logilab.fr>, Nicolas Chauvat <nicolas.chauvat@logilab.fr>
+Build-Depends: debhelper (>= 5.0.37.1), python-support, python-all-dev (>=2.5), python-all (>=2.5), libgecode-dev, python-sphinx, g++ (>= 4)
+XS-Python-Version: >= 2.5
+Standards-Version: 3.9.1
 Homepage: http://www.logilab.org/project/rql
 
 Package: python-rql
 Architecture: any
 XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, ${misc:Depends}, ${shlibs:Depends}, python-logilab-common (>= 0.35.3-1), yapps2-runtime
+Depends: ${python:Depends}, ${misc:Depends}, ${shlibs:Depends}, python-logilab-common (>= 0.35.3-1), yapps2-runtime, python-logilab-database
+Conflicts: cubicweb-common (<= 3.8.3)
 Provides: ${python:Provides}
 Description: relationship query language (RQL) utilities
  A library providing the base utilities to handle RQL queries,
--- a/debian/copyright	Thu Oct 15 20:20:29 2009 +0200
+++ b/debian/copyright	Thu May 05 10:50:51 2011 +0200
@@ -1,16 +1,30 @@
 This package was debianized by Logilab <contact@logilab.fr>.
 
-Upstream Author: 
+Upstream Author:
 
   Logilab <contact@logilab.fr>
 
 Copyright:
 
-Copyright (c) 2003-2008 LOGILAB S.A. (Paris, FRANCE).
-http://www.logilab.fr/ -- mailto:contact@logilab.fr
+    Copyright (c) 2004-2010 LOGILAB S.A. (Paris, FRANCE).
+    http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+License:
 
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Lesser General Public License as published by the
+    Free Software Foundation; either version 2.1 of the License, or (at your
+    option) any later version.
 
-Logilab Closed source License. This code is *NOT* open-source. Usage of this
-code is subject to a licence agreement. If you want to use it, you should
-contact logilab's sales service at commercial@logilab.fr
+    This program is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+    for more details.
 
+    You should have received a copy of the GNU Lessser General Public License
+    along with this program; if not, write to the Free Software Foundation,
+    Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+On Debian systems, the complete text of the GNU Lesser General Public License
+may be found in '/usr/share/common-licenses/LGPL-2.1'.
+
--- a/debian/rules	Thu Oct 15 20:20:29 2009 +0200
+++ b/debian/rules	Thu May 05 10:50:51 2011 +0200
@@ -12,13 +12,13 @@
 #export DH_VERBOSE=1
 
 build: build-stamp
-build-stamp: 
+build-stamp:
 	dh_testdir
 	(for PYTHON in `pyversions -r`; do \
-	    $${PYTHON} setup.py build ; done )
+	    NO_SETUPTOOLS=1 $${PYTHON} setup.py build ; done )
 	${MAKE} -C doc html || true
 	touch build-stamp
-clean: 
+clean:
 	dh_testdir
 	dh_testroot
 	rm -f build-stamp configure-stamp
@@ -33,7 +33,7 @@
 	dh_clean -k
 	dh_installdirs
 	(for PYTHON in `pyversions -r`; do \
-		$${PYTHON} setup.py install --no-compile --prefix=debian/python-rql/usr/ ; \
+		NO_SETUPTOOLS=1 $${PYTHON} setup.py install --no-compile --prefix=debian/python-rql/usr/ ; \
 	done)
 	# remove test directory (installed in in the doc directory)
 	rm -rf debian/python-rql/usr/lib/python*/site-packages/rql/test
@@ -45,21 +45,23 @@
 
 # Build architecture-dependent files here.
 binary-arch: build install
-	dh_testdir 
-	dh_testroot 
+	dh_testdir
+	dh_testroot
 	dh_install -a
-	dh_pysupport -a 
+	dh_pysupport -a
 	gzip -9 -c ChangeLog > changelog.gz
 	dh_installchangelogs -a
 	dh_installexamples -a
 	dh_installdocs -a README TODO changelog.gz
 	dh_installman -a
 	dh_link -a
-	dh_compress -a -X.py -X.ini -X.xml -Xtest
+	# .js, .txt and .json are coming from sphinx build
+	dh_compress -a -X.py -X.ini -X.xml -Xtest/ -X.js -X.txt -X.json
 	dh_fixperms -a
+	dh_strip
 	dh_shlibdeps -a
 	dh_installdeb -a
-	dh_gencontrol -a 
+	dh_gencontrol -a
 	dh_md5sums -a
 	dh_builddeb -a
 
--- a/doc/conf.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/doc/conf.py	Thu May 05 10:50:51 2011 +0200
@@ -10,6 +10,23 @@
 #
 # All configuration values have a default value; values that are commented out
 # serve to show the default value.
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 
 import sys, os
 
--- a/editextensions.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/editextensions.py	Thu May 05 10:50:51 2011 +0200
@@ -1,9 +1,22 @@
-"""RQL functions for manipulating syntax trees.
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""RQL functions for manipulating syntax trees."""
 
-:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
-"""
 __docformat__ = "restructuredtext en"
 
 from rql.nodes import Constant, Variable, VariableRef, Relation, make_relation
--- a/gecode_solver.cpp	Thu Oct 15 20:20:29 2009 +0200
+++ b/gecode_solver.cpp	Thu May 05 10:50:51 2011 +0200
@@ -365,7 +365,11 @@
 	unsigned int n_b = 0;
 	if (s->status() != SS_FAILED) {
 	    n_p = s->propagators();
+#if GE_VERSION<PM_VERSION(3,2,0)
 	    n_b = s->branchings();
+#else
+	    n_b = s->branchers();
+#endif
 	}
 #if GE_VERSION<PM_VERSION(2,0,0)
     Engine<RqlSolver> e(s);
--- a/interfaces.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/interfaces.py	Thu May 05 10:50:51 2011 +0200
@@ -1,8 +1,22 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 """Interfaces used by the RQL package.
 
-:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/nodes.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/nodes.py	Thu May 05 10:50:51 2011 +0200
@@ -1,12 +1,26 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 """RQL syntax tree nodes.
 
 This module defines all the nodes we can find in a RQL Syntax tree, except
 root nodes, defined in the `stmts` module.
+"""
 
-:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
-"""
 __docformat__ = "restructuredtext en"
 
 from itertools import chain
@@ -23,12 +37,6 @@
                             'String', 'Substitute', 'etype'))
 
 
-# keep using mx DateTime by default for bw compat
-def use_py_datetime():
-    global KEYWORD_MAP
-    KEYWORD_MAP = {'NOW' : datetime.now,
-                   'TODAY': date.today}
-
 ETYPE_PYOBJ_MAP = { bool: 'Boolean',
                     int: 'Int',
                     long: 'Int',
@@ -42,21 +50,14 @@
                     timedelta: 'Interval',
                     }
 
-
-try:
-    from mx.DateTime import DateTimeType, DateTimeDeltaType, today, now
-    KEYWORD_MAP = {'NOW' : now,
-                   'TODAY': today}
-    ETYPE_PYOBJ_MAP[DateTimeType] = 'Datetime'
-    ETYPE_PYOBJ_MAP[DateTimeDeltaType] = 'Datetime'
-except:
-    use_py_datetime()
+KEYWORD_MAP = {'NOW' : datetime.now,
+               'TODAY': date.today}
 
 def etype_from_pyobj(value):
     """guess yams type from python value"""
     # note:
-    # * Password is not selectable so no problem)
-    # * use type(value) and not value.__class__ since mx instances have no
+    # * Password is not selectable so no problem
+    # * use type(value) and not value.__class__ since C instances may have no
     #   __class__ attribute
     return ETYPE_PYOBJ_MAP[type(value)]
 
@@ -104,6 +105,19 @@
     relation.append(cmpop)
     return relation
 
+def make_constant_restriction(var, rtype, value, ctype, operator='='):
+    if ctype is None:
+        ctype = etype_from_pyobj(value)
+    if isinstance(value, (set, frozenset, tuple, list, dict)):
+        if len(value) > 1:
+            rel = make_relation(var, rtype, ('IN',), Function, operator)
+            infunc = rel.children[1].children[0]
+            for atype in sorted(value):
+                infunc.append(Constant(atype, ctype))
+            return rel
+        value = iter(value).next()
+    return make_relation(var, rtype, (value, ctype), Constant, operator)
+
 
 class EditableMixIn(object):
     """mixin class to add edition functionalities to some nodes, eg root nodes
@@ -128,14 +142,17 @@
         handling
         """
         # unregister variable references in the removed subtree
+        parent = node.parent
+        stmt = parent.stmt
         for varref in node.iget_nodes(VariableRef):
             varref.unregister_reference()
             if undefine and not varref.variable.stinfo['references']:
-                node.stmt.undefine_variable(varref.variable)
+                stmt.undefine_variable(varref.variable)
+        # remove return actually removed node and its parent
+        node, parent, index = parent.remove(node)
         if self.should_register_op:
             from rql.undo import RemoveNodeOperation
-            self.undo_manager.add_operation(RemoveNodeOperation(node))
-        node.parent.remove(node)
+            self.undo_manager.add_operation(RemoveNodeOperation(node, parent, stmt, index))
 
     def add_restriction(self, relation):
         """add a restriction relation"""
@@ -159,18 +176,8 @@
 
         variable rtype = value
         """
-        if ctype is None:
-            ctype = etype_from_pyobj(value)
-        if isinstance(value, (set, frozenset, tuple, list, dict)):
-            if len(value) > 1:
-                rel = make_relation(var, rtype, ('IN',), Function, operator=operator)
-                infunc = rel.children[1].children[0]
-                for atype in sorted(value):
-                    infunc.append(Constant(atype, ctype))
-                return self.add_restriction(rel)
-            value = iter(value).next()
-        return self.add_restriction(make_relation(var, rtype, (value, ctype),
-                                                  Constant, operator))
+        restr = make_constant_restriction(var, rtype, value, ctype, operator)
+        return self.add_restriction(restr)
 
     def add_relation(self, lhsvar, rtype, rhsvar):
         """builds a restriction node to express '<var> eid <eid>'"""
@@ -258,6 +265,10 @@
 class Not(Node):
     """a logical NOT node (unary)"""
     __slots__ = ()
+    def __init__(self, expr=None):
+        Node.__init__(self)
+        if expr is not None:
+            self.append(expr)
 
     def as_string(self, encoding=None, kwargs=None):
         if isinstance(self.children[0], (Exists, Relation)):
@@ -267,10 +278,6 @@
     def __repr__(self, encoding=None, kwargs=None):
         return 'NOT (%s)' % repr(self.children[0])
 
-    @property
-    def sqlscope(self):
-        return self
-
     def ored(self, traverse_scope=False, _fromnode=None):
         # XXX consider traverse_scope ?
         return self.parent.ored(traverse_scope, _fromnode or self)
@@ -278,6 +285,9 @@
     def neged(self, traverse_scope=False, _fromnode=None, strict=False):
         return self
 
+    def remove(self, child):
+        return self.parent.remove(self)
+
 # def parent_scope_property(attr):
 #     def _get_parent_attr(self, attr=attr):
 #         return getattr(self.parent.scope, attr)
@@ -333,11 +343,14 @@
         assert oldnode is self.query
         self.query = newnode
         newnode.parent = self
+        return oldnode, self, None
+
+    def remove(self, child):
+        return self.parent.remove(self)
 
     @property
     def scope(self):
         return self
-    sqlscope = scope
 
     def ored(self, traverse_scope=False, _fromnode=None):
         if not traverse_scope:
@@ -427,7 +440,12 @@
             return False
         rhs = self.children[1]
         if isinstance(rhs, Comparison):
-            rhs = rhs.children[0]
+            try:
+                rhs = rhs.children[0]
+            except:
+                print 'opppp', rhs
+                print rhs.root
+                raise
         # else: relation used in SET OR DELETE selection
         return ((isinstance(rhs, Constant) and rhs.type == 'etype')
                 or (isinstance(rhs, Function) and rhs.name == 'IN'))
@@ -466,6 +484,8 @@
         self.optional= value
 
 
+OPERATORS = frozenset(('=', '!=', '<', '<=', '>=', '>', 'ILIKE', 'LIKE'))
+
 class Comparison(HSMixin, Node):
     """handle comparisons:
 
@@ -477,10 +497,7 @@
         Node.__init__(self)
         if operator == '~=':
             operator = 'ILIKE'
-        elif operator == '=' and isinstance(value, Constant) and \
-                 value.type is None:
-            operator = 'IS'
-        assert operator in ('<', '<=', '=', '>=', '>', 'ILIKE', 'LIKE', 'IS'), operator
+        assert operator in OPERATORS, operator
         self.operator = operator.encode()
         if value is not None:
             self.append(value)
@@ -502,7 +519,7 @@
             return '%s %s %s' % (self.children[0].as_string(encoding, kwargs),
                                  self.operator.encode(),
                                  self.children[1].as_string(encoding, kwargs))
-        if self.operator in ('=', 'IS'):
+        if self.operator == '=':
             return self.children[0].as_string(encoding, kwargs)
         return '%s %s' % (self.operator.encode(),
                           self.children[0].as_string(encoding, kwargs))
@@ -849,33 +866,35 @@
             # relations where this variable is used on the lhs/rhs
             'relations': set(),
             'rhsrelations': set(),
-            'optrelations': set(),
-            # empty if this variable may be simplified (eg not used in optional
-            # relations and no final relations where this variable is used on
-            # the lhs)
-            'blocsimplification': set(),
-            # type relations (e.g. "is") where this variable is used on the lhs
-            'typerels': set(),
-            # uid relations (e.g. "eid") where this variable is used on the lhs
-            'uidrels': set(),
             # selection indexes if any
             'selected': set(),
-            # if this variable is an attribute variable (ie final entity),
-            # link to the (prefered) attribute owner variable
+            # type restriction (e.g. "is" / "is_instance_of") where this
+            # variable is used on the lhs
+            'typerel': None,
+            # uid relations (e.g. "eid") where this variable is used on the lhs
+            'uidrel': None,
+            # if this variable is an attribute variable (ie final entity), link
+            # to the (prefered) attribute owner variable
             'attrvar': None,
-            # set of couple (lhs variable name, relation name) where this
-            # attribute variable is used
-            'attrvars': set(),
             # constant node linked to an uid variable if any
             'constnode': None,
             })
+        # remove optional st infos
+        for key in ('optrelations', 'blocsimplification', 'ftirels'):
+            self.stinfo.pop(key, None)
+
+    def add_optional_relation(self, relation):
+        try:
+            self.stinfo['optrelations'].add(relation)
+        except KeyError:
+            self.stinfo['optrelations'] = set((relation,))
 
     def get_type(self, solution=None, kwargs=None):
         """return entity type of this object, 'Any' if not found"""
         if solution:
             return solution[self.name]
-        for rel in self.stinfo['typerels']:
-            return str(rel.children[1].children[0].value)
+        if self.stinfo['typerel']:
+            return str(self.stinfo['typerel'].children[1].children[0].value)
         schema = self.schema
         if schema is not None:
             for rel in self.stinfo['rhsrelations']:
@@ -912,14 +931,14 @@
             rtype = rel.r_type
             lhs, rhs = rel.get_variable_parts()
             # use getattr, may not be a variable ref (rewritten, constant...)
-            lhsvar = getattr(lhs, 'variable', None)
             rhsvar = getattr(rhs, 'variable', None)
             if mainindex is not None:
                 # relation to the main variable, stop searching
-                if mainindex in lhsvar.stinfo['selected']:
+                lhsvar = getattr(lhs, 'variable', None)
+                if lhsvar is not None and mainindex in lhsvar.stinfo['selected']:
                     return tr(rtype)
-                if mainindex in rhsvar.stinfo['selected']:
-                    if schema is not None and rschema.symetric:
+                if rhsvar is not None and mainindex in rhsvar.stinfo['selected']:
+                    if schema is not None and rschema.symmetric:
                         return tr(rtype)
                     return tr(rtype + '_object')
             if rhsvar is self:
@@ -958,7 +977,7 @@
 
 class ColumnAlias(Referenceable):
     __slots__ = ('colnum', 'query',
-                 '_q_sql', '_q_sqltable') # XXX ginco specific
+                 '_q_sql', '_q_sqltable') # XXX cubicweb specific
     def __init__(self, alias, colnum, query=None):
         super(ColumnAlias, self).__init__(alias)
         self.colnum = int(colnum)
@@ -1000,8 +1019,6 @@
     def get_scope(self):
         return self.query
     scope = property(get_scope, set_scope)
-    sqlscope = scope
-    set_sqlscope = set_scope
 
 
 class Variable(Referenceable):
@@ -1029,7 +1046,6 @@
     def prepare_annotation(self):
         super(Variable, self).prepare_annotation()
         self.stinfo['scope'] = None
-        self.stinfo['sqlscope'] = None
 
     def _set_scope(self, key, scopenode):
         if scopenode is self.stmt or self.stinfo[key] is None:
@@ -1043,12 +1059,6 @@
         return self.stinfo['scope']
     scope = property(get_scope, set_scope)
 
-    def set_sqlscope(self, sqlscopenode):
-        self._set_scope('sqlscope', sqlscopenode)
-    def get_sqlscope(self):
-        return self.stinfo['sqlscope']
-    sqlscope = property(get_sqlscope, set_sqlscope)
-
     def valuable_references(self):
         """return the number of "valuable" references :
         references is in selection or in a non type (is) relations
--- a/parser.g	Thu Oct 15 20:20:29 2009 +0200
+++ b/parser.g	Thu May 05 10:50:51 2011 +0200
@@ -88,11 +88,11 @@
     token FALSE:       r'(?i)FALSE'
     token NULL:        r'(?i)NULL'
     token EXISTS:      r'(?i)EXISTS'
-    token CMP_OP:      r'(?i)<=|<|>=|>|~=|=|LIKE|ILIKE|IS'
+    token CMP_OP:      r'(?i)<=|<|>=|>|!=|=|~=|LIKE|ILIKE'
     token ADD_OP:      r'\+|-'
     token MUL_OP:      r'\*|/'
     token FUNCTION:    r'[A-Za-z_]+\s*(?=\()'
-    token R_TYPE:      r'[a-z][a-z0-9_]*'
+    token R_TYPE:      r'[a-z_][a-z0-9_]*'
     token E_TYPE:      r'[A-Z][A-Za-z0-9]*[a-z]+[0-9]*'
     token VARIABLE:    r'[A-Z][A-Z0-9_]*'
     token COLALIAS:    r'[A-Z][A-Z0-9_]*\.\d+'
@@ -176,10 +176,7 @@
 rule groupby<<S>>: GROUPBY variables<<S>> {{ S.set_groupby(variables); return True }}
                  |
 
-rule having<<S>>: HAVING               {{ nodes = [] }}
-                   expr_cmp<<S>>       {{ nodes.append(expr_cmp) }}
-                   ( ',' expr_cmp<<S>> {{ nodes.append(expr_cmp) }}
-                   )*                  {{ S.set_having(nodes) }}
+rule having<<S>>: HAVING logical_expr<<S>> {{ S.set_having([logical_expr]) }}
                 |
 
 rule orderby<<S>>: ORDERBY              {{ nodes = [] }}
@@ -198,11 +195,6 @@
                     BEING r"\(" union<<Union()>> r"\)" {{ node.set_query(union); return node }}
 
 
-rule expr_cmp<<S>>: expr_add<<S>>  {{ c1 = expr_add }}
-                    CMP_OP         {{ cmp = Comparison(CMP_OP.upper(), c1) }}
-                    expr_add<<S>>  {{ cmp.append(expr_add); return cmp }}
-
-
 rule sort_term<<S>>: expr_add<<S>> sort_meth {{ return SortTerm(expr_add, sort_meth) }}
 
 
@@ -241,7 +233,7 @@
                     (  AND rels_not<<S>> {{ node = And(node, rels_not) }}
                     )*                   {{ return node }}
 
-rule rels_not<<S>>: NOT rel<<S>> {{ node = Not(); node.append(rel); return node }}
+rule rels_not<<S>>: NOT rel<<S>> {{ return Not(rel) }}
                   | rel<<S>>     {{ return rel }}
 
 rule rel<<S>>: rel_base<<S>>                {{ return rel_base }}
@@ -259,6 +251,39 @@
 rule opt_right<<S>>: QMARK  {{ return 'right' }}
                    |
 
+#// restriction expressions ####################################################
+
+rule logical_expr<<S>>: exprs_or<<S>>       {{ node = exprs_or }}
+                        ( ',' exprs_or<<S>> {{ node = And(node, exprs_or) }}
+                        )*                  {{ return node }}
+
+rule exprs_or<<S>>: exprs_and<<S>>      {{ node = exprs_and }}
+                    ( OR exprs_and<<S>> {{ node = Or(node, exprs_and) }}
+                    )*                  {{ return node }}
+
+rule exprs_and<<S>>: exprs_not<<S>>        {{ node = exprs_not }}
+                     (  AND exprs_not<<S>> {{ node = And(node, exprs_not) }}
+                     )*                    {{ return node }}
+
+rule exprs_not<<S>>: NOT balanced_expr<<S>> {{ return Not(balanced_expr) }}
+                   | balanced_expr<<S>>     {{ return balanced_expr }}
+
+#// XXX ambiguity, expr_add may also have '(' as first token. Hence
+#// put "(" logical_expr<<S>> ")" rule first. We can then parse:
+#//
+#//   Any T2 WHERE T1 relation T2 HAVING (1 < COUNT(T1));
+#//
+#// but not
+#//
+#//   Any T2 WHERE T1 relation T2 HAVING (1+2) < COUNT(T1);
+rule balanced_expr<<S>>: r"\(" logical_expr<<S>> r"\)" {{ return logical_expr }}
+                       | expr_add<<S>> expr_op<<S>>    {{ expr_op.insert(0, expr_add); return expr_op }}
+
+# // cant use expr<<S>> without introducing some ambiguities
+rule expr_op<<S>>: CMP_OP expr_add<<S>> {{ return Comparison(CMP_OP.upper(), expr_add) }}
+                 | in_expr<<S>>      {{ return Comparison('=', in_expr) }}
+
+
 #// common statements ###########################################################
 
 rule variables<<S>>:                   {{ vars = [] }}
@@ -307,6 +332,13 @@
                    )?
                 r"\)"                 {{ return F }}
 
+rule in_expr<<S>>: 'IN' r"\("        {{ F = Function('IN') }}
+                   ( expr_add<<S>> (     {{ F.append(expr_add) }}
+                      ',' expr_add<<S>>
+                     )*                  {{ F.append(expr_add) }}
+                   )?
+                r"\)"                 {{ return F }}
+
 
 rule var<<S>>: VARIABLE {{ return VariableRef(S.get_variable(VARIABLE)) }}
 
--- a/parser.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/parser.py	Thu May 05 10:50:51 2011 +0200
@@ -1,8 +1,22 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 """yapps input grammar for RQL.
 
-:organization: Logilab
-:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 
 
 Select statement grammar
@@ -63,6 +77,7 @@
 
 class HerculeScanner(runtime.Scanner):
     patterns = [
+        ("'IN'", re.compile('IN')),
         ("','", re.compile(',')),
         ('r"\\)"', re.compile('\\)')),
         ('r"\\("', re.compile('\\(')),
@@ -94,11 +109,11 @@
         ('FALSE', re.compile('(?i)FALSE')),
         ('NULL', re.compile('(?i)NULL')),
         ('EXISTS', re.compile('(?i)EXISTS')),
-        ('CMP_OP', re.compile('(?i)<=|<|>=|>|~=|=|LIKE|ILIKE|IS')),
+        ('CMP_OP', re.compile('(?i)<=|<|>=|>|!=|=|~=|LIKE|ILIKE')),
         ('ADD_OP', re.compile('\\+|-')),
         ('MUL_OP', re.compile('\\*|/')),
         ('FUNCTION', re.compile('[A-Za-z_]+\\s*(?=\\()')),
-        ('R_TYPE', re.compile('[a-z][a-z0-9_]*')),
+        ('R_TYPE', re.compile('[a-z_][a-z0-9_]*')),
         ('E_TYPE', re.compile('[A-Z][A-Za-z0-9]*[a-z]+[0-9]*')),
         ('VARIABLE', re.compile('[A-Z][A-Z0-9_]*')),
         ('COLALIAS', re.compile('[A-Z][A-Z0-9_]*\\.\\d+')),
@@ -257,14 +272,8 @@
         _token = self._peek('HAVING', 'WITH', 'GROUPBY', 'ORDERBY', 'LIMIT', 'OFFSET', 'WHERE', "';'", 'r"\\)"', context=_context)
         if _token == 'HAVING':
             HAVING = self._scan('HAVING', context=_context)
-            nodes = []
-            expr_cmp = self.expr_cmp(S, _context)
-            nodes.append(expr_cmp)
-            while self._peek("','", 'WITH', 'GROUPBY', 'ORDERBY', 'LIMIT', 'OFFSET', 'WHERE', 'HAVING', "';'", 'r"\\)"', context=_context) == "','":
-                self._scan("','", context=_context)
-                expr_cmp = self.expr_cmp(S, _context)
-                nodes.append(expr_cmp)
-            S.set_having(nodes)
+            logical_expr = self.logical_expr(S, _context)
+            S.set_having([logical_expr])
         elif 1:
             pass
         else:
@@ -316,15 +325,6 @@
         self._scan('r"\\)"', context=_context)
         node.set_query(union); return node
 
-    def expr_cmp(self, S, _parent=None):
-        _context = self.Context(_parent, self._scanner, 'expr_cmp', [S])
-        expr_add = self.expr_add(S, _context)
-        c1 = expr_add
-        CMP_OP = self._scan('CMP_OP', context=_context)
-        cmp = Comparison(CMP_OP.upper(), c1)
-        expr_add = self.expr_add(S, _context)
-        cmp.append(expr_add); return cmp
-
     def sort_term(self, S, _parent=None):
         _context = self.Context(_parent, self._scanner, 'sort_term', [S])
         expr_add = self.expr_add(S, _context)
@@ -417,7 +417,7 @@
         if _token == 'NOT':
             NOT = self._scan('NOT', context=_context)
             rel = self.rel(S, _context)
-            node = Not(); node.append(rel); return node
+            return Not(rel)
         else: # in ['r"\\("', 'EXISTS', 'VARIABLE']
             rel = self.rel(S, _context)
             return rel
@@ -475,6 +475,73 @@
         else:
             pass
 
+    def logical_expr(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'logical_expr', [S])
+        exprs_or = self.exprs_or(S, _context)
+        node = exprs_or
+        while self._peek("','", 'r"\\)"', 'WITH', 'GROUPBY', 'ORDERBY', 'LIMIT', 'OFFSET', 'WHERE', 'HAVING', "';'", context=_context) == "','":
+            self._scan("','", context=_context)
+            exprs_or = self.exprs_or(S, _context)
+            node = And(node, exprs_or)
+        return node
+
+    def exprs_or(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'exprs_or', [S])
+        exprs_and = self.exprs_and(S, _context)
+        node = exprs_and
+        while self._peek('OR', "','", 'r"\\)"', 'WITH', 'GROUPBY', 'ORDERBY', 'LIMIT', 'OFFSET', 'WHERE', 'HAVING', "';'", context=_context) == 'OR':
+            OR = self._scan('OR', context=_context)
+            exprs_and = self.exprs_and(S, _context)
+            node = Or(node, exprs_and)
+        return node
+
+    def exprs_and(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'exprs_and', [S])
+        exprs_not = self.exprs_not(S, _context)
+        node = exprs_not
+        while self._peek('AND', 'OR', "','", 'r"\\)"', 'WITH', 'GROUPBY', 'ORDERBY', 'LIMIT', 'OFFSET', 'WHERE', 'HAVING', "';'", context=_context) == 'AND':
+            AND = self._scan('AND', context=_context)
+            exprs_not = self.exprs_not(S, _context)
+            node = And(node, exprs_not)
+        return node
+
+    def exprs_not(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'exprs_not', [S])
+        _token = self._peek('NOT', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
+        if _token == 'NOT':
+            NOT = self._scan('NOT', context=_context)
+            balanced_expr = self.balanced_expr(S, _context)
+            return Not(balanced_expr)
+        else:
+            balanced_expr = self.balanced_expr(S, _context)
+            return balanced_expr
+
+    def balanced_expr(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'balanced_expr', [S])
+        _token = self._peek('r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context)
+        if _token == 'r"\\("':
+            self._scan('r"\\("', context=_context)
+            logical_expr = self.logical_expr(S, _context)
+            self._scan('r"\\)"', context=_context)
+            return logical_expr
+        elif 1:
+            expr_add = self.expr_add(S, _context)
+            expr_op = self.expr_op(S, _context)
+            expr_op.insert(0, expr_add); return expr_op
+        else:
+            raise runtime.SyntaxError(_token[0], 'Could not match balanced_expr')
+
+    def expr_op(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'expr_op', [S])
+        _token = self._peek('CMP_OP', "'IN'", context=_context)
+        if _token == 'CMP_OP':
+            CMP_OP = self._scan('CMP_OP', context=_context)
+            expr_add = self.expr_add(S, _context)
+            return Comparison(CMP_OP.upper(), expr_add)
+        else: # == "'IN'"
+            in_expr = self.in_expr(S, _context)
+            return Comparison('=', in_expr)
+
     def variables(self, S, _parent=None):
         _context = self.Context(_parent, self._scanner, 'variables', [S])
         vars = []
@@ -490,7 +557,7 @@
         _context = self.Context(_parent, self._scanner, 'decl_vars', [R])
         E_TYPE = self._scan('E_TYPE', context=_context)
         var = self.var(R, _context)
-        while self._peek("','", 'R_TYPE', 'QMARK', 'WHERE', '":"', 'HAVING', "';'", 'MUL_OP', 'BEING', 'WITH', 'GROUPBY', 'ORDERBY', 'ADD_OP', 'LIMIT', 'OFFSET', 'r"\\)"', 'CMP_OP', 'SORT_DESC', 'SORT_ASC', 'AND', 'OR', context=_context) == "','":
+        while self._peek("','", 'R_TYPE', 'QMARK', 'WHERE', '":"', 'HAVING', "';'", 'MUL_OP', 'BEING', 'WITH', 'GROUPBY', 'ORDERBY', 'ADD_OP', 'LIMIT', 'OFFSET', 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', "'IN'", 'AND', 'OR', context=_context) == "','":
             R.add_main_variable(E_TYPE, var)
             self._scan("','", context=_context)
             E_TYPE = self._scan('E_TYPE', context=_context)
@@ -529,7 +596,7 @@
         _context = self.Context(_parent, self._scanner, 'expr_add', [S])
         expr_mul = self.expr_mul(S, _context)
         node = expr_mul
-        while self._peek('ADD_OP', 'r"\\)"', "','", 'CMP_OP', 'SORT_DESC', 'SORT_ASC', 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'ADD_OP':
+        while self._peek('ADD_OP', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', "'IN'", 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', "';'", 'WITH', 'AND', 'OR', context=_context) == 'ADD_OP':
             ADD_OP = self._scan('ADD_OP', context=_context)
             expr_mul = self.expr_mul(S, _context)
             node = MathExpression( ADD_OP, node, expr_mul )
@@ -539,7 +606,7 @@
         _context = self.Context(_parent, self._scanner, 'expr_mul', [S])
         expr_base = self.expr_base(S, _context)
         node = expr_base
-        while self._peek('MUL_OP', 'ADD_OP', 'r"\\)"', "','", 'CMP_OP', 'SORT_DESC', 'SORT_ASC', 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == 'MUL_OP':
+        while self._peek('MUL_OP', 'ADD_OP', 'r"\\)"', "','", 'SORT_DESC', 'SORT_ASC', 'CMP_OP', "'IN'", 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', "';'", 'WITH', 'AND', 'OR', context=_context) == 'MUL_OP':
             MUL_OP = self._scan('MUL_OP', context=_context)
             expr_base = self.expr_base(S, _context)
             node = MathExpression( MUL_OP, node, expr_base)
@@ -573,7 +640,22 @@
         F = Function(FUNCTION)
         if self._peek('r"\\)"', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) != 'r"\\)"':
             expr_add = self.expr_add(S, _context)
-            while self._peek("','", 'r"\\)"', 'CMP_OP', 'SORT_DESC', 'SORT_ASC', 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', 'WITH', "';'", 'AND', 'OR', context=_context) == "','":
+            while self._peek("','", 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', "'IN'", 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', "';'", 'WITH', 'AND', 'OR', context=_context) == "','":
+                F.append(expr_add)
+                self._scan("','", context=_context)
+                expr_add = self.expr_add(S, _context)
+            F.append(expr_add)
+        self._scan('r"\\)"', context=_context)
+        return F
+
+    def in_expr(self, S, _parent=None):
+        _context = self.Context(_parent, self._scanner, 'in_expr', [S])
+        self._scan("'IN'", context=_context)
+        self._scan('r"\\("', context=_context)
+        F = Function('IN')
+        if self._peek('r"\\)"', 'r"\\("', 'NULL', 'DATE', 'DATETIME', 'TRUE', 'FALSE', 'FLOAT', 'INT', 'STRING', 'SUBSTITUTE', 'VARIABLE', 'E_TYPE', 'FUNCTION', context=_context) != 'r"\\)"':
+            expr_add = self.expr_add(S, _context)
+            while self._peek("','", 'r"\\)"', 'SORT_DESC', 'SORT_ASC', 'CMP_OP', "'IN'", 'GROUPBY', 'QMARK', 'ORDERBY', 'WHERE', 'LIMIT', 'OFFSET', 'HAVING', "';'", 'WITH', 'AND', 'OR', context=_context) == "','":
                 F.append(expr_add)
                 self._scan("','", context=_context)
                 expr_add = self.expr_add(S, _context)
@@ -639,9 +721,6 @@
 # End -- grammar generated by Yapps
 """Main parser command.
 
-:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/parser_main.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/parser_main.py	Thu May 05 10:50:51 2011 +0200
@@ -1,8 +1,22 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 """Main parser command.
 
-:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/rqlgen.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/rqlgen.py	Thu May 05 10:50:51 2011 +0200
@@ -1,8 +1,22 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 """Generation of RQL strings.
 
-:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/setup.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/setup.py	Thu May 05 10:50:51 2011 +0200
@@ -1,50 +1,62 @@
 #!/usr/bin/env python
 # pylint: disable-msg=W0404,W0622,W0704,W0613,E0611,C0103
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 """Generic Setup script, takes package info from __pkginfo__.py file.
-
-:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
 import os
 import sys
 import shutil
-from distutils.core import setup
 from os.path import isdir, exists, join, walk
 
+try:
+    if os.environ.get('NO_SETUPTOOLS'):
+        raise ImportError()
+    from setuptools import setup
+    from setuptools.command import install_lib, build_ext
+    USE_SETUPTOOLS = 1
+except ImportError:
+    from distutils.core import setup
+    from distutils.command import install_lib, build_ext
+    USE_SETUPTOOLS = 0
+
+
+sys.modules.pop('__pkginfo__', None)
 # import required features
-from __pkginfo__ import modname, version, license, short_desc, long_desc, \
+from __pkginfo__ import modname, version, license, description, long_desc, \
      web, author, author_email
 # import optional features
-try:
-    from __pkginfo__ import distname
-except ImportError:
-    distname = modname
-try:
-    from __pkginfo__ import scripts
-except ImportError:
-    scripts = []
-try:
-    from __pkginfo__ import data_files
-except ImportError:
-    data_files = None
-try:
-    from __pkginfo__ import subpackage_of
-except ImportError:
-    subpackage_of = None
-try:
-    from __pkginfo__ import include_dirs
-except ImportError:
-    include_dirs = []
-try:
-    from __pkginfo__ import ext_modules
-except ImportError:
-    ext_modules = None
+import __pkginfo__
+distname = getattr(__pkginfo__, 'distname', modname)
+scripts = getattr(__pkginfo__, 'scripts', [])
+data_files = getattr(__pkginfo__, 'data_files', None)
+subpackage_of = getattr(__pkginfo__, 'subpackage_of', None)
+include_dirs = getattr(__pkginfo__, 'include_dirs', [])
+ext_modules = getattr(__pkginfo__, 'ext_modules', None)
+install_requires = getattr(__pkginfo__, 'install_requires', None)
+dependency_links = getattr(__pkginfo__, 'dependency_links', [])
 
-BASE_BLACKLIST = ('CVS', 'debian', 'dist', 'build', '__buildlog')
-IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc')
+STD_BLACKLIST = ('CVS', '.svn', '.hg', 'debian', 'dist', 'build')
+
+IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc', '~')
+
 
 
 def ensure_scripts(linux_scripts):
@@ -77,8 +89,9 @@
     return result
 
 def export(from_dir, to_dir,
-           blacklist=BASE_BLACKLIST,
-           ignore_ext=IGNORED_EXTENSIONS):
+           blacklist=STD_BLACKLIST,
+           ignore_ext=IGNORED_EXTENSIONS,
+           verbose=True):
     """make a mirror of from_dir in to_dir, omitting directories and files
     listed in the black list
     """
@@ -95,9 +108,10 @@
                 continue
             if filename[-1] == '~':
                 continue
-            src = '%s/%s' % (directory, filename)
+            src = join(directory, filename)
             dest = to_dir + src[len(from_dir):]
-            print >> sys.stderr, src, '->', dest
+            if verbose:
+                print >> sys.stderr, src, '->', dest
             if os.path.isdir(src):
                 if not exists(dest):
                     os.mkdir(dest)
@@ -115,43 +129,28 @@
     walk(from_dir, make_mirror, None)
 
 
-EMPTY_FILE = '"""generated file, don\'t modify or your data will be lost"""\n'
+EMPTY_FILE = '''"""generated file, don\'t modify or your data will be lost"""
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+    pass
+'''
 
-def install(**kwargs):
-    """setup entry point"""
-    if subpackage_of:
-        package = subpackage_of + '.' + modname
-        kwargs['package_dir'] = {package : '.'}
-        packages = [package] + get_packages(os.getcwd(), package)
-    else:
-        kwargs['package_dir'] = {modname : '.'}
-        packages = [modname] + get_packages(os.getcwd(), modname)
-    kwargs['packages'] = packages
-    dist = setup(name = distname,
-                 version = version,
-                 license =license,
-                 description = short_desc,
-                 long_description = long_desc,
-                 author = author,
-                 author_email = author_email,
-                 url = web,
-                 scripts = ensure_scripts(scripts),
-                 data_files=data_files,
-                 ext_modules=ext_modules,
-                 **kwargs
-                 )
-
-    if dist.have_run.get('install_lib'):
-        _install = dist.get_command_obj('install_lib')
+class MyInstallLib(install_lib.install_lib):
+    """extend install_lib command to handle  package __init__.py and
+    include_dirs variable if necessary
+    """
+    def run(self):
+        """overridden from install_lib class"""
+        install_lib.install_lib.run(self)
+        # create Products.__init__.py if needed
         if subpackage_of:
-            # create Products.__init__.py if needed
-            product_init = join(_install.install_dir, subpackage_of,
-                                '__init__.py')
+            product_init = join(self.install_dir, subpackage_of, '__init__.py')
             if not exists(product_init):
+                self.announce('creating %s' % product_init)
                 stream = open(product_init, 'w')
                 stream.write(EMPTY_FILE)
                 stream.close()
-
         # manually install included directories if any
         if include_dirs:
             if subpackage_of:
@@ -159,9 +158,66 @@
             else:
                 base = modname
             for directory in include_dirs:
-                dest = join(_install.install_dir, base, directory)
-                export(directory, dest)
-    return dist
+                dest = join(self.install_dir, base, directory)
+                export(directory, dest, verbose=False)
+
+class MyBuildExt(build_ext.build_ext):
+    """Extend build_ext command to pass through compilation error.
+    In fact, if gecode extension fail, rql will use logilab.constraint
+    """
+    def run(self):
+        from distutils.errors import CompileError
+        try:
+            build_ext.build_ext.run(self)
+        except CompileError:
+            import traceback
+            traceback.print_exc()
+            sys.stderr.write('================================\n'
+                             'The compilation of the gecode C extension failed. '
+                             'rql will use logilab.constraint which is a pure '
+                             'python implementation. '
+                             'Please note that the C extension run faster. '
+                             'So, install a compiler then install rql again with'
+                             ' the "force" option for better performance.\n'
+                             '================================\n')
+            pass
+
+def install(**kwargs):
+    """setup entry point"""
+    if USE_SETUPTOOLS:
+        if '--force-manifest' in sys.argv:
+            sys.argv.remove('--force-manifest')
+    # install-layout option was introduced in 2.5.3-1~exp1
+    elif sys.version_info < (2, 5, 4) and '--install-layout=deb' in sys.argv:
+        sys.argv.remove('--install-layout=deb')
+    if subpackage_of:
+        package = subpackage_of + '.' + modname
+        kwargs['package_dir'] = {package : '.'}
+        packages = [package] + get_packages(os.getcwd(), package)
+        if USE_SETUPTOOLS:
+            kwargs['namespace_packages'] = [subpackage_of]
+    else:
+        kwargs['package_dir'] = {modname : '.'}
+        packages = [modname] + get_packages(os.getcwd(), modname)
+    if USE_SETUPTOOLS and install_requires:
+        kwargs['install_requires'] = install_requires
+        kwargs['dependency_links'] = dependency_links
+    kwargs['packages'] = packages
+    return setup(name = distname,
+                 version = version,
+                 license = license,
+                 description = description,
+                 long_description = long_desc,
+                 author = author,
+                 author_email = author_email,
+                 url = web,
+                 scripts = ensure_scripts(scripts),
+                 data_files = data_files,
+                 ext_modules = ext_modules,
+                 cmdclass = {'install_lib': MyInstallLib,
+                             'build_ext':MyBuildExt},
+                 **kwargs
+                 )
 
 if __name__ == '__main__' :
     install()
--- a/stcheck.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/stcheck.py	Thu May 05 10:50:51 2011 +0200
@@ -1,19 +1,33 @@
-"""RQL Syntax tree annotator.
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""RQL Syntax tree annotator"""
 
-:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
-"""
 __docformat__ = "restructuredtext en"
 
 from itertools import chain
 from logilab.common.compat import any
 from logilab.common.graph import has_path
+from logilab.database import UnknownFunction
 
 from rql._exceptions import BadRQLQuery
 from rql.utils import function_description
-from rql.nodes import (VariableRef, Constant, Not, Exists, Function,
-                       Variable, variable_refs)
+from rql.nodes import (Relation, VariableRef, Constant, Not, Exists, Function,
+                       And, Variable, variable_refs, make_relation)
 from rql.stmts import Union
 
 
@@ -23,55 +37,82 @@
     except KeyError:
         return subvarname + str(id(select))
 
+def bloc_simplification(variable, term):
+    try:
+        variable.stinfo['blocsimplification'].add(term)
+    except KeyError:
+        variable.stinfo['blocsimplification'] = set((term,))
+
 
 class GoTo(Exception):
     """Exception used to control the visit of the tree."""
     def __init__(self, node):
         self.node = node
 
+VAR_SELECTED = 1
+VAR_HAS_TYPE_REL = 2
+VAR_HAS_UID_REL = 4
+VAR_HAS_REL = 8
+
+class STCheckState(object):
+    def __init__(self):
+        self.errors = []
+        self.under_not = []
+        self.var_info = {}
+
+    def error(self, msg):
+        self.errors.append(msg)
+
+    def add_var_info(self, var, vi):
+        try:
+            self.var_info[var] |= vi
+        except KeyError:
+            self.var_info[var] = vi
 
 class RQLSTChecker(object):
     """Check a RQL syntax tree for errors not detected on parsing.
 
     Some simple rewriting of the tree may be done too:
-    * if a OR is used on a symetric relation
+    * if a OR is used on a symmetric relation
     * IN function with a single child
 
     use assertions for internal error but specific `BadRQLQuery` exception for
     errors due to a bad rql input
     """
 
-    def __init__(self, schema):
+    def __init__(self, schema, special_relations=None, backend=None):
         self.schema = schema
+        self.special_relations = special_relations or {}
+        self.backend = backend
 
     def check(self, node):
-        errors = []
-        self._visit(node, errors)
-        if errors:
-            raise BadRQLQuery('%s\n** %s' % (node, '\n** '.join(errors)))
+        state = STCheckState()
+        self._visit(node, state)
+        if state.errors:
+            raise BadRQLQuery('%s\n** %s' % (node, '\n** '.join(state.errors)))
         #if node.TYPE == 'select' and \
         #       not node.defined_vars and not node.get_restriction():
         #    result = []
         #    for term in node.selected_terms():
         #        result.append(term.eval(kwargs))
 
-    def _visit(self, node, errors):
+    def _visit(self, node, state):
         try:
-            node.accept(self, errors)
+            node.accept(self, state)
         except GoTo, ex:
-            self._visit(ex.node, errors)
+            self._visit(ex.node, state)
         else:
             for c in node.children:
-                self._visit(c, errors)
-            node.leave(self, errors)
+                self._visit(c, state)
+            node.leave(self, state)
 
-    def _visit_selectedterm(self, node, errors):
+    def _visit_selectedterm(self, node, state):
         for i, term in enumerate(node.selection):
             # selected terms are not included by the default visit,
             # accept manually each of them
-            self._visit(term, errors)
+            self._visit(term, state)
 
-    def _check_selected(self, term, termtype, errors):
+    def _check_selected(self, term, termtype, state):
         """check that variables referenced in the given term are selected"""
         for vref in variable_refs(term):
             # no stinfo yet, use references
@@ -81,41 +122,49 @@
                     break
             else:
                 msg = 'variable %s used in %s is not referenced by any relation'
-                errors.append(msg % (vref.name, termtype))
+                state.error(msg % (vref.name, termtype))
 
     # statement nodes #########################################################
 
-    def visit_union(self, node, errors):
+    def visit_union(self, node, state):
         nbselected = len(node.children[0].selection)
         for select in node.children[1:]:
             if not len(select.selection) == nbselected:
-                errors.append('when using union, all subqueries should have '
+                state.error('when using union, all subqueries should have '
                               'the same number of selected terms')
-    def leave_union(self, node, errors):
+    def leave_union(self, node, state):
         pass
 
-    def visit_select(self, node, errors):
+    def visit_select(self, node, state):
         node.vargraph = {} # graph representing links between variable
         node.aggregated = set()
-        self._visit_selectedterm(node, errors)
+        self._visit_selectedterm(node, state)
 
-    def leave_select(self, node, errors):
+    def leave_select(self, node, state):
         selected = node.selection
         # check selected variable are used in restriction
         if node.where is not None or len(selected) > 1:
             for term in selected:
-                self._check_selected(term, 'selection', errors)
+                self._check_selected(term, 'selection', state)
+                for vref in term.iget_nodes(VariableRef):
+                    state.add_var_info(vref.variable, VAR_SELECTED)
+        for var in node.defined_vars.itervalues():
+            vinfo = state.var_info.get(var, 0)
+            if not (vinfo & VAR_HAS_REL) and (vinfo & VAR_HAS_TYPE_REL) \
+                   and not (vinfo & VAR_SELECTED):
+                raise BadRQLQuery('unbound variable %s (%s)' % (var.name, selected))
         if node.groupby:
             # check that selected variables are used in groups
             for var in node.selection:
                 if isinstance(var, VariableRef) and not var in node.groupby:
-                    errors.append('variable %s should be grouped' % var)
+                    state.error('variable %s should be grouped' % var)
             for group in node.groupby:
-                self._check_selected(group, 'group', errors)
+                self._check_selected(group, 'group', state)
         if node.distinct and node.orderby:
             # check that variables referenced in the given term are reachable from
-            # a selected variable with only ?1 cardinalityselected
-            selectidx = frozenset(vref.name for term in selected for vref in term.get_nodes(VariableRef))
+            # a selected variable with only ?1 cardinality selected
+            selectidx = frozenset(vref.name for term in selected
+                                  for vref in term.get_nodes(VariableRef))
             schema = self.schema
             for sortterm in node.orderby:
                 for vref in sortterm.term.get_nodes(VariableRef):
@@ -131,56 +180,57 @@
                         msg = ('can\'t sort on variable %s which is linked to a'
                                ' variable in the selection but may have different'
                                ' values for a resulting row')
-                        errors.append(msg % vref.name)
+                        state.error(msg % vref.name)
 
     def has_unique_value_path(self, select, fromvar, tovar):
         graph = select.vargraph
         path = has_path(graph, fromvar, tovar)
         if path is None:
             return False
-        for tovar in path:
+        for var in path:
             try:
-                rtype = graph[(fromvar, tovar)]
+                rtype = graph[(fromvar, var)]
                 cardidx = 0
             except KeyError:
-                rtype = graph[(tovar, fromvar)]
+                rtype = graph[(var, fromvar)]
                 cardidx = 1
             rschema = self.schema.rschema(rtype)
-            for rdef in rschema.iter_rdefs():
+            for rdef in rschema.rdefs.itervalues():
                 # XXX aggregats handling needs much probably some enhancements...
-                if not (tovar in select.aggregated
-                        or rschema.rproperty(rdef[0], rdef[1], 'cardinality')[cardidx] in '?1'):
+                if not (var in select.aggregated
+                        or (rdef.cardinality[cardidx] in '?1' and
+                            (var == tovar or not rschema.final))):
                     return False
-            fromvar = tovar
+            fromvar = var
         return True
 
 
-    def visit_insert(self, insert, errors):
-        self._visit_selectedterm(insert, errors)
-    def leave_insert(self, node, errors):
+    def visit_insert(self, insert, state):
+        self._visit_selectedterm(insert, state)
+    def leave_insert(self, node, state):
         pass
 
-    def visit_delete(self, delete, errors):
-        self._visit_selectedterm(delete, errors)
-    def leave_delete(self, node, errors):
+    def visit_delete(self, delete, state):
+        self._visit_selectedterm(delete, state)
+    def leave_delete(self, node, state):
         pass
 
-    def visit_set(self, update, errors):
-        self._visit_selectedterm(update, errors)
-    def leave_set(self, node, errors):
+    def visit_set(self, update, state):
+        self._visit_selectedterm(update, state)
+    def leave_set(self, node, state):
         pass
 
     # tree nodes ##############################################################
 
-    def visit_exists(self, node, errors):
+    def visit_exists(self, node, state):
         pass
-    def leave_exists(self, node, errors):
+    def leave_exists(self, node, state):
         pass
 
-    def visit_subquery(self, node, errors):
+    def visit_subquery(self, node, state):
         pass
 
-    def leave_subquery(self, node, errors):
+    def leave_subquery(self, node, state):
         # copy graph information we're interested in
         pgraph = node.parent.vargraph
         for select in node.query.children:
@@ -190,7 +240,7 @@
                 try:
                     subvref = select.selection[i]
                 except IndexError:
-                    errors.append('subquery "%s" has only %s selected terms, needs %s'
+                    state.error('subquery "%s" has only %s selected terms, needs %s'
                                   % (select, len(select.selection), len(node.aliases)))
                     continue
                 if isinstance(subvref, VariableRef):
@@ -210,12 +260,12 @@
                     values = pgraph.setdefault(_var_graphid(key, trmap, select), [])
                     values += [_var_graphid(v, trmap, select) for v in val]
 
-    def visit_sortterm(self, sortterm, errors):
+    def visit_sortterm(self, sortterm, state):
         term = sortterm.term
         if isinstance(term, Constant):
             for select in sortterm.root.children:
                 if len(select.selection) < term.value:
-                    errors.append('order column out of bound %s' % term.value)
+                    state.error('order column out of bound %s' % term.value)
         else:
             stmt = term.stmt
             for tvref in variable_refs(term):
@@ -224,26 +274,26 @@
                         break
                 else:
                     msg = 'sort variable %s is not referenced any where else'
-                    errors.append(msg % tvref.name)
+                    state.error(msg % tvref.name)
 
-    def leave_sortterm(self, node, errors):
+    def leave_sortterm(self, node, state):
         pass
 
-    def visit_and(self, et, errors):
+    def visit_and(self, et, state):
         pass #assert len(et.children) == 2, len(et.children)
-    def leave_and(self, node, errors):
+    def leave_and(self, node, state):
         pass
 
-    def visit_or(self, ou, errors):
+    def visit_or(self, ou, state):
         #assert len(ou.children) == 2, len(ou.children)
-        # simplify Ored expression of a symetric relation
+        # simplify Ored expression of a symmetric relation
         r1, r2 = ou.children[0], ou.children[1]
         try:
             r1type = r1.r_type
             r2type = r2.r_type
         except AttributeError:
             return # can't be
-        if r1type == r2type and self.schema.rschema(r1type).symetric:
+        if r1type == r2type and self.schema.rschema(r1type).symmetric:
             lhs1, rhs1 = r1.get_variable_parts()
             lhs2, rhs2 = r2.get_variable_parts()
             try:
@@ -255,73 +305,129 @@
                     raise GoTo(r1)
             except AttributeError:
                 pass
-    def leave_or(self, node, errors):
-        pass
-
-    def visit_not(self, not_, errors):
-        pass
-    def leave_not(self, not_, errors):
+    def leave_or(self, node, state):
         pass
 
-    def visit_relation(self, relation, errors):
-        if relation.optional and relation.neged():
-                errors.append("can use optional relation under NOT (%s)"
-                              % relation.as_string())
-        # special case "X identity Y"
-        if relation.r_type == 'identity':
-            lhs, rhs = relation.children
-            #assert not isinstance(relation.parent, Not)
-            #assert rhs.operator == '='
-        elif relation.r_type == 'is':
+    def visit_not(self, not_, state):
+        state.under_not.append(True)
+    def leave_not(self, not_, state):
+        state.under_not.pop()
+        # NOT normalization
+        child = not_.children[0]
+        if self._should_wrap_by_exists(child):
+            not_.replace(child, Exists(child))
+
+    def _should_wrap_by_exists(self, child):
+        if isinstance(child, Exists):
+            return False
+        if not isinstance(child, Relation):
+            return True
+        if child.r_type == 'identity':
+            return False
+        rschema = self.schema.rschema(child.r_type)
+        if rschema.final:
+            return False
+        # XXX no exists for `inlined` relation (allow IS NULL optimization)
+        # unless the lhs variable is only referenced from this neged relation,
+        # in which case it's *not* in the statement's scope, hence EXISTS should
+        # be added anyway
+        if rschema.inlined:
+            references = child.children[0].variable.references()
+            valuable = 0
+            for vref in references:
+                rel = vref.relation()
+                if rel is None or not rel.is_types_restriction():
+                    if valuable:
+                        return False
+                    valuable = 1
+            return True
+        return not child.is_types_restriction()
+
+    def visit_relation(self, relation, state):
+        if relation.optional and state.under_not:
+            state.error("can't use optional relation under NOT (%s)"
+                        % relation.as_string())
+        lhsvar = relation.children[0].variable
+        if relation.is_types_restriction():
+            if relation.optional:
+                state.error('can\'t use optional relation on "%s"'
+                            % relation.as_string())
+            if state.var_info.get(lhsvar, 0) & VAR_HAS_TYPE_REL:
+                state.error('can only one type restriction per variable (use '
+                            'IN for %s if desired)' % lhsvar.name)
+            else:
+                state.add_var_info(lhsvar, VAR_HAS_TYPE_REL)
             # special case "C is NULL"
-            if relation.children[1].operator == 'IS':
-                lhs, rhs = relation.children
-                #assert isinstance(lhs, VariableRef), lhs
-                #assert isinstance(rhs.children[0], Constant)
-                #assert rhs.operator == 'IS', rhs.operator
-                #assert rhs.children[0].type == None
-        elif not relation.r_type in self.schema:
-            errors.append('unknown relation `%s`' % relation.r_type)
+            # if relation.children[1].operator == 'IS':
+            #     lhs, rhs = relation.children
+            #     #assert isinstance(lhs, VariableRef), lhs
+            #     #assert isinstance(rhs.children[0], Constant)
+            #     #assert rhs.operator == 'IS', rhs.operator
+            #     #assert rhs.children[0].type == None
+        else:
+            state.add_var_info(lhsvar, VAR_HAS_REL)
+            rtype = relation.r_type
+            try:
+                rschema = self.schema.rschema(rtype)
+            except KeyError:
+                state.error('unknown relation `%s`' % rtype)
+            else:
+                if relation.optional and rschema.final:
+                    state.error("shouldn't use optional on final relation `%s`"
+                                % relation.r_type)
+                if self.special_relations.get(rtype) == 'uid':
+                    if state.var_info.get(lhsvar, 0) & VAR_HAS_UID_REL:
+                        state.error('can only one uid restriction per variable '
+                                    '(use IN for %s if desired)' % lhsvar.name)
+                    else:
+                        state.add_var_info(lhsvar, VAR_HAS_UID_REL)
+
+            for vref in relation.children[1].get_nodes(VariableRef):
+                state.add_var_info(vref.variable, VAR_HAS_REL)
         try:
             vargraph = relation.stmt.vargraph
             rhsvarname = relation.children[1].children[0].variable.name
-            lhsvarname = relation.children[0].name
         except AttributeError:
             pass
         else:
-            vargraph.setdefault(lhsvarname, []).append(rhsvarname)
-            vargraph.setdefault(rhsvarname, []).append(lhsvarname)
-            vargraph[(lhsvarname, rhsvarname)] = relation.r_type
+            vargraph.setdefault(lhsvar.name, []).append(rhsvarname)
+            vargraph.setdefault(rhsvarname, []).append(lhsvar.name)
+            vargraph[(lhsvar.name, rhsvarname)] = relation.r_type
 
-    def leave_relation(self, relation, errors):
+    def leave_relation(self, relation, state):
         pass
         #assert isinstance(lhs, VariableRef), '%s: %s' % (lhs.__class__,
         #                                                       relation)
 
-    def visit_comparison(self, comparison, errors):
+    def visit_comparison(self, comparison, state):
         pass #assert len(comparison.children) in (1,2), len(comparison.children)
-    def leave_comparison(self, node, errors):
+    def leave_comparison(self, node, state):
         pass
 
-    def visit_mathexpression(self, mathexpr, errors):
+    def visit_mathexpression(self, mathexpr, state):
         pass #assert len(mathexpr.children) == 2, len(mathexpr.children)
-    def leave_mathexpression(self, node, errors):
+    def leave_mathexpression(self, node, state):
         pass
 
-    def visit_function(self, function, errors):
+    def visit_function(self, function, state):
         try:
             funcdescr = function_description(function.name)
-        except KeyError:
-            errors.append('unknown function "%s"' % function.name)
+        except UnknownFunction:
+            state.error('unknown function "%s"' % function.name)
         else:
             try:
                 funcdescr.check_nbargs(len(function.children))
             except BadRQLQuery, ex:
-                errors.append(str(ex))
+                state.error(str(ex))
+            if self.backend is not None:
+                try:
+                    funcdescr.st_check_backend(self.backend, function)
+                except BadRQLQuery, ex:
+                    state.error(str(ex))
             if funcdescr.aggregat:
                 if isinstance(function.children[0], Function) and \
                        function.children[0].descr().aggregat:
-                    errors.append('can\'t nest aggregat functions')
+                    state.error('can\'t nest aggregat functions')
             if funcdescr.name == 'IN':
                 #assert function.parent.operator == '='
                 if len(function.children) == 1:
@@ -329,10 +435,11 @@
                     function.parent.remove(function)
                 #else:
                 #    assert len(function.children) >= 1
-    def leave_function(self, node, errors):
+
+    def leave_function(self, node, state):
         pass
 
-    def visit_variableref(self, variableref, errors):
+    def visit_variableref(self, variableref, state):
         #assert len(variableref.children)==0
         #assert not variableref.parent is variableref
 ##         try:
@@ -342,23 +449,22 @@
 ##             raise Exception((variableref.root(), variableref.variable))
         pass
 
-    def leave_variableref(self, node, errors):
+    def leave_variableref(self, node, state):
         pass
 
-    def visit_constant(self, constant, errors):
+    def visit_constant(self, constant, state):
         #assert len(constant.children)==0
         if constant.type == 'etype':
             if constant.relation().r_type not in ('is', 'is_instance_of'):
                 msg ='using an entity type in only allowed with "is" relation'
-                errors.append(msg)
+                state.error(msg)
             if not constant.value in self.schema:
-                errors.append('unknown entity type %s' % constant.value)
+                state.error('unknown entity type %s' % constant.value)
 
-    def leave_constant(self, node, errors):
+    def leave_constant(self, node, state):
         pass
 
 
-
 class RQLSTAnnotator(object):
     """Annotate RQL syntax tree to ease further code generation from it.
 
@@ -387,9 +493,8 @@
             for vref in term.get_nodes(VariableRef):
                 vref.variable.stinfo['selected'].add(i)
                 vref.variable.set_scope(node)
-                vref.variable.set_sqlscope(node)
         if node.where is not None:
-            node.where.accept(self, node, node)
+            node.where.accept(self, node)
 
     visit_insert = visit_delete = visit_set = _visit_stmt
 
@@ -410,154 +515,158 @@
             # if there is a having clause, bloc simplification of variables used in GROUPBY
             for term in node.groupby:
                 for vref in term.get_nodes(VariableRef):
-                    vref.variable.stinfo['blocsimplification'].add(term)
-        for var in node.defined_vars.itervalues():
-            if not var.stinfo['relations'] and var.stinfo['typerels'] and not var.stinfo['selected']:
-                raise BadRQLQuery('unbound variable %s (%s)' % (var.name, var.stmt.root))
-            if len(var.stinfo['uidrels']) > 1:
-                uidrels = iter(var.stinfo['uidrels'])
-                val = getattr(uidrels.next().get_variable_parts()[1], 'value', object())
-                for uidrel in uidrels:
-                    if getattr(uidrel.get_variable_parts()[1], 'value', None) != val:
-                        # XXX should check OR branch and check simplify in that case as well
-                        raise BadRQLQuery('conflicting eid values for %s' % var.name)
+                    bloc_simplification(vref.variable, term)
 
-    def rewrite_shared_optional(self, exists, var):
+    def rewrite_shared_optional(self, exists, var, identity_rel_scope=None):
         """if variable is shared across multiple scopes, need some tree
         rewriting
         """
-        if var.scope is var.stmt:
-            # allocate a new variable
-            newvar = var.stmt.make_variable()
-            newvar.prepare_annotation()
-            for vref in var.references():
-                if vref.scope is exists:
-                    rel = vref.relation()
-                    vref.unregister_reference()
-                    newvref = VariableRef(newvar)
-                    vref.parent.replace(vref, newvref)
-                    # update stinfo structure which may have already been
-                    # partially processed
-                    if rel in var.stinfo['rhsrelations']:
-                        lhs, rhs = rel.get_parts()
-                        if vref is rhs.children[0] and \
-                               self.schema.rschema(rel.r_type).final:
-                            update_attrvars(newvar, rel, lhs)
-                            lhsvar = getattr(lhs, 'variable', None)
-                            var.stinfo['attrvars'].remove( (lhsvar, rel.r_type) )
-                            if var.stinfo['attrvar'] is lhsvar:
-                                if var.stinfo['attrvars']:
-                                    var.stinfo['attrvar'] = iter(var.stinfo['attrvars']).next()
-                                else:
-                                    var.stinfo['attrvar'] = None
-                        var.stinfo['rhsrelations'].remove(rel)
-                        newvar.stinfo['rhsrelations'].add(rel)
-                    for stinfokey in ('blocsimplification','typerels', 'uidrels',
-                                      'relations', 'optrelations'):
-                        try:
-                            var.stinfo[stinfokey].remove(rel)
-                            newvar.stinfo[stinfokey].add(rel)
-                        except KeyError:
-                            continue
-            # shared references
-            newvar.stinfo['constnode'] = var.stinfo['constnode']
-            if newvar.stmt.solutions: # solutions already computed
-                newvar.stinfo['possibletypes'] = var.stinfo['possibletypes']
-                for sol in newvar.stmt.solutions:
-                    sol[newvar.name] = sol[var.name]
+        # allocate a new variable
+        newvar = var.stmt.make_variable()
+        newvar.prepare_annotation()
+        for vref in var.references():
+            if vref.scope is exists:
+                rel = vref.relation()
+                vref.unregister_reference()
+                newvref = VariableRef(newvar)
+                vref.parent.replace(vref, newvref)
+                stinfo = var.stinfo
+                # update stinfo structure which may have already been
+                # partially processed
+                if rel in stinfo['rhsrelations']:
+                    lhs, rhs = rel.get_parts()
+                    if vref is rhs.children[0] and \
+                           self.schema.rschema(rel.r_type).final:
+                        update_attrvars(newvar, rel, lhs)
+                        lhsvar = getattr(lhs, 'variable', None)
+                        stinfo['attrvars'].remove( (lhsvar, rel.r_type) )
+                        if stinfo['attrvar'] is lhsvar:
+                            if stinfo['attrvars']:
+                                stinfo['attrvar'] = iter(stinfo['attrvars']).next()
+                            else:
+                                stinfo['attrvar'] = None
+                    stinfo['rhsrelations'].remove(rel)
+                    newvar.stinfo['rhsrelations'].add(rel)
+                try:
+                    stinfo['relations'].remove(rel)
+                    newvar.stinfo['relations'].add(rel)
+                except KeyError:
+                    pass
+                try:
+                    stinfo['optrelations'].remove(rel)
+                    newvar.add_optional_relation(rel)
+                except KeyError:
+                    pass
+                try:
+                    stinfo['blocsimplification'].remove(rel)
+                    bloc_simplification(newvar, rel)
+                except KeyError:
+                    pass
+                if stinfo['uidrel'] is rel:
+                    newvar.stinfo['uidrel'] = rel
+                    stinfo['uidrel'] = None
+                if stinfo['typerel'] is rel:
+                    newvar.stinfo['typerel'] = rel
+                    stinfo['typerel'] = None
+        # shared references
+        newvar.stinfo['constnode'] = var.stinfo['constnode']
+        if newvar.stmt.solutions: # solutions already computed
+            newvar.stinfo['possibletypes'] = var.stinfo['possibletypes']
+            for sol in newvar.stmt.solutions:
+                sol[newvar.name] = sol[var.name]
+        if identity_rel_scope is None:
             rel = exists.add_relation(var, 'identity', newvar)
-            # we have to force visit of the introduced relation
-            self.visit_relation(rel, exists, exists)
-            return newvar
-        return None
+            identity_rel_scope = exists
+        else:
+            rel = make_relation(var, 'identity', (newvar,), VariableRef)
+            exists.parent.replace(exists, And(exists, Exists(rel)))
+        # we have to force visit of the introduced relation
+        self.visit_relation(rel, identity_rel_scope)
+        return newvar
 
     # tree nodes ##############################################################
 
-    def visit_exists(self, node, scope, sqlscope):
-        node.children[0].accept(self, node, node)
+    def visit_exists(self, node, scope):
+        node.children[0].accept(self, node)
 
-    def visit_not(self, node, scope, sqlscope):
-        node.children[0].accept(self, scope, node)
+    def visit_not(self, node, scope):
+        node.children[0].accept(self, scope)
 
-    def visit_and(self, node, scope, sqlscope):
-        node.children[0].accept(self, scope, sqlscope)
-        node.children[1].accept(self, scope, sqlscope)
+    def visit_and(self, node, scope):
+        node.children[0].accept(self, scope)
+        node.children[1].accept(self, scope)
     visit_or = visit_and
 
-    def visit_relation(self, relation, scope, sqlscope):
+    def visit_relation(self, relation, scope):
         #assert relation.parent, repr(relation)
         lhs, rhs = relation.get_parts()
         # may be a constant once rqlst has been simplified
         lhsvar = getattr(lhs, 'variable', None)
         if relation.is_types_restriction():
-            #assert rhs.operator == '='
-            #assert not relation.optional
             if lhsvar is not None:
-                lhsvar.stinfo['typerels'].add(relation)
+                lhsvar.stinfo['typerel'] = relation
             return
         if relation.optional is not None:
             exists = relation.scope
             if not isinstance(exists, Exists):
                 exists = None
             if lhsvar is not None:
-                if exists is not None:
-                    newvar = self.rewrite_shared_optional(exists, lhsvar)
-                    if newvar is not None:
-                        lhsvar = newvar
-                lhsvar.stinfo['blocsimplification'].add(relation)
+                if exists is not None and lhsvar.scope is lhsvar.stmt:
+                    lhsvar = self.rewrite_shared_optional(exists, lhsvar)
+                bloc_simplification(lhsvar, relation)
                 if relation.optional == 'both':
-                    lhsvar.stinfo['optrelations'].add(relation)
+                    lhsvar.add_optional_relation(relation)
                 elif relation.optional == 'left':
-                    lhsvar.stinfo['optrelations'].add(relation)
+                    lhsvar.add_optional_relation(relation)
             try:
                 rhsvar = rhs.children[0].variable
-                if exists is not None:
-                    newvar = self.rewrite_shared_optional(exists, rhsvar)
-                    if newvar is not None:
-                        rhsvar = newvar
-                rhsvar.stinfo['blocsimplification'].add(relation)
+                if exists is not None and rhsvar.scope is rhsvar.stmt:
+                    rhsvar = self.rewrite_shared_optional(exists, rhsvar)
+                bloc_simplification(rhsvar, relation)
                 if relation.optional == 'right':
-                    rhsvar.stinfo['optrelations'].add(relation)
+                    rhsvar.add_optional_relation(relation)
                 elif relation.optional == 'both':
-                    rhsvar.stinfo['optrelations'].add(relation)
+                    rhsvar.add_optional_relation(relation)
             except AttributeError:
                 # may have been rewritten as well
                 pass
         rtype = relation.r_type
-        try:
-            rschema = self.schema.rschema(rtype)
-        except KeyError:
-            raise BadRQLQuery('no relation %s' % rtype)
+        rschema = self.schema.rschema(rtype)
         if lhsvar is not None:
             lhsvar.set_scope(scope)
-            lhsvar.set_sqlscope(sqlscope)
             lhsvar.stinfo['relations'].add(relation)
             if rtype in self.special_relations:
                 key = '%srels' % self.special_relations[rtype]
                 if key == 'uidrels':
                     constnode = relation.get_variable_parts()[1]
                     if not (relation.operator() != '=' or
-                            isinstance(relation.parent, Not)):
+                            # XXX use state to detect relation under NOT/OR
+                            # + check variable's scope
+                            isinstance(relation.parent, Not) or
+                            relation.parent.ored()):
                         if isinstance(constnode, Constant):
                             lhsvar.stinfo['constnode'] = constnode
-                        lhsvar.stinfo.setdefault(key, set()).add(relation)
+                        lhsvar.stinfo['uidrel'] = relation
                 else:
                     lhsvar.stinfo.setdefault(key, set()).add(relation)
             elif rschema.final or rschema.inlined:
-                lhsvar.stinfo['blocsimplification'].add(relation)
+                bloc_simplification(lhsvar, relation)
         for vref in rhs.get_nodes(VariableRef):
             var = vref.variable
             var.set_scope(scope)
-            var.set_sqlscope(sqlscope)
             var.stinfo['relations'].add(relation)
             var.stinfo['rhsrelations'].add(relation)
             if vref is rhs.children[0] and rschema.final:
                 update_attrvars(var, relation, lhs)
 
-
 def update_attrvars(var, relation, lhs):
+    # stinfo['attrvars'] is set of couple (lhs variable name, relation name)
+    # where the `var` attribute variable is used
     lhsvar = getattr(lhs, 'variable', None)
-    var.stinfo['attrvars'].add( (lhsvar, relation.r_type) )
+    try:
+        var.stinfo['attrvars'].add( (lhsvar, relation.r_type) )
+    except KeyError:
+        var.stinfo['attrvars'] = set([(lhsvar, relation.r_type)])
     # give priority to variable which is not in an EXISTS as
     # "main" attribute variable
     if var.stinfo['attrvar'] is None or not isinstance(relation.scope, Exists):
--- a/stmts.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/stmts.py	Thu May 05 10:50:51 2011 +0200
@@ -1,12 +1,26 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 """Construction and manipulation of RQL syntax trees.
 
 This module defines only first level nodes (i.e. statements). Child nodes are
 defined in the nodes module
+"""
 
-:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
-"""
 __docformat__ = "restructuredtext en"
 
 from copy import deepcopy
@@ -87,10 +101,7 @@
             self._varmaker = rqlvar_maker(defined=self.defined_vars,
                                           # XXX only on Select node
                                           aliases=getattr(self, 'aliases', None))
-        name =  self._varmaker.next()
-        while name in self.defined_vars:
-            name =  self._varmaker.next()
-        return name
+        return self._varmaker.next()
 
     def make_variable(self):
         """create a new variable with an unique name for this tree"""
@@ -132,6 +143,7 @@
             raise
         return True
 
+
 class Statement(object):
     """base class for statement nodes"""
 
@@ -154,7 +166,6 @@
     @property
     def scope(self):
         return self
-    sqlscope = scope
 
     def ored(self, traverse_scope=False, _fromnode=None):
         return None
@@ -258,14 +269,27 @@
 
     # union specific methods ##################################################
 
+    # XXX for bw compat, should now use get_variable_indices (cw > 3.8.4)
     def get_variable_variables(self):
-        """return the set of variable names which take different type according
-        to the solutions
+        change = set()
+        for idx in self.get_variable_indices():
+            for vref in self.children[0].selection[idx].iget_nodes(nodes.VariableRef):
+                change.add(vref.name)
+        return change
+
+    def get_variable_indices(self):
+        """return the set of selection indexes which take different types
+        according to the solutions
         """
         change = set()
         values = {}
         for select in self.children:
-            change.update(select.get_variable_variables(values))
+            for descr in select.get_selection_solutions():
+                for i, etype in enumerate(descr):
+                    values.setdefault(i, set()).add(etype)
+        for idx, etypes in values.iteritems():
+            if len(etypes) > 1:
+                change.add(idx)
         return change
 
     def _locate_subquery(self, col, etype, kwargs):
@@ -301,14 +325,16 @@
         return self._subq_cache[(col, etype)]
 
     def subquery_selection_index(self, subselect, col):
-        """given a select sub-query and a column index in this sub-query, return
-        the selection index for this column in the root query
+        """given a select sub-query and a column index in the root query, return
+        the selection index for this column in the sub-query
         """
-        while col is not None and subselect.parent.parent:
+        selectpath = []
+        while subselect.parent.parent is not None:
             subq = subselect.parent.parent
             subselect = subq.parent
-            termvar = subselect.aliases[subq.aliases[col].name]
-            col = termvar.selected_index()
+            selectpath.insert(0, subselect)
+        for select in selectpath:
+            col = select.selection[col].variable.colnum
         return col
 
     # recoverable modification methods ########################################
@@ -372,7 +398,7 @@
     # select clauses
     groupby = ()
     orderby = ()
-    having = ()
+    having = () # XXX now a single node
     with_ = ()
     # set by the annotator
     has_aggregat = False
@@ -598,7 +624,7 @@
             solutions = self.solutions
         # this may occurs with rql optimization, for instance on
         # 'Any X WHERE X eid 12' query
-        if not self.defined_vars:
+        if not (self.defined_vars or self.aliases):
             self.solutions = [{}]
         else:
             newsolutions = []
@@ -606,24 +632,26 @@
                 asol = {}
                 for var in self.defined_vars:
                     asol[var] = origsol[var]
+                for var in self.aliases:
+                    asol[var] = origsol[var]
                 if not asol in newsolutions:
                     newsolutions.append(asol)
             self.solutions = newsolutions
 
-    def get_variable_variables(self, _values=None):
+    def get_selection_solutions(self):
         """return the set of variable names which take different type according
         to the solutions
         """
-        change = set()
-        if _values is None:
-            _values = {}
+        descriptions = set()
         for solution in self.solutions:
-            for vname, etype in solution.iteritems():
-                if not vname in _values:
-                    _values[vname] = etype
-                elif _values[vname] != etype:
-                    change.add(vname)
-        return change
+            descr = []
+            for term in self.selection:
+                try:
+                    descr.append(term.get_type(solution=solution))
+                except CoercionError:
+                    pass
+            descriptions.add(tuple(descr))
+        return descriptions
 
     # quick accessors #########################################################
 
@@ -651,17 +679,28 @@
         term.parent = self
         self.selection.append(term)
 
+    # XXX proprify edition, we should specify if we want:
+    # * undo support
+    # * references handling
     def replace(self, oldnode, newnode):
-        assert oldnode is self.where
-        self.where = newnode
+        if oldnode is self.where:
+            self.where = newnode
+        elif oldnode in self.selection:
+            self.selection[self.selection.index(oldnode)] = newnode
+        elif oldnode in self.orderby:
+            self.orderby[self.orderby.index(oldnode)] = newnode
+        elif oldnode in self.groupby:
+            self.groupby[self.groupby.index(oldnode)] = newnode
+        elif oldnode in self.having:
+            self.having[self.having.index(oldnode)] = newnode
+        else:
+            raise Exception('duh XXX %s' % oldnode)
+        # XXX no undo/reference support 'by design' (eg breaks things if you add
+        # it...)
+        # XXX resetting oldnode parent cause pb with cw.test_views (w/ facets)
+        #oldnode.parent = None
         newnode.parent = self
-#         # XXX no vref handling ?
-#         try:
-#             Statement.replace(self, oldnode, newnode)
-#         except ValueError:
-#             i = self.selection.index(oldnode)
-#             self.selection.pop(i)
-#             self.selection.insert(i, newnode)
+        return oldnode, self, None
 
     def remove(self, node):
         if node is self.where:
@@ -670,9 +709,13 @@
             self.remove_sort_term(node)
         elif node in self.groupby:
             self.remove_group_var(node)
+        elif node in self.having:
+            self.having.remove(node)
+        # XXX selection
         else:
             raise Exception('duh XXX')
         node.parent = None
+        return node, self, None
 
     def undefine_variable(self, var):
         """undefine the given variable and remove all relations where it appears"""
@@ -693,7 +736,10 @@
         # effective undefine operation
         if self.should_register_op:
             from rql.undo import UndefineVarOperation
-            self.undo_manager.add_operation(UndefineVarOperation(var))
+            solutions = [d.copy() for d in self.solutions]
+            self.undo_manager.add_operation(UndefineVarOperation(var, self, solutions))
+        for sol in self.solutions:
+            sol.pop(var.name, None)
         del self.defined_vars[var.name]
 
     def _var_index(self, var):
--- a/test/unittest_analyze.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/test/unittest_analyze.py	Thu May 05 10:50:51 2011 +0200
@@ -1,4 +1,21 @@
-from logilab.common.testlib import TestCase, unittest_main
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+from logilab.common.testlib import TestCase, unittest_main, mock_object as mock
 
 from rql import RQLHelper, TypeResolverException
 
@@ -18,25 +35,27 @@
 
 
 class RelationSchema(ERSchema):
-    def __init__(self, assoc_types, symetric=False, card=None):
+    def __init__(self, assoc_types, symmetric=False, card=None):
         self.assoc_types = assoc_types
         self.subj_types = [e_type[0] for e_type in assoc_types]
-        self.final = False
         d = {}
         for e_type, dest_types in assoc_types:
             for e_type in dest_types:
                 d[e_type] = 1
-                if e_type in ('Int', 'Datetime', 'String'):
-                    self.final = True
         self.obj_types = d.keys()
-        self.symetric = symetric
+        self.symmetric = symmetric
         self.inlined = False
         if card is None:
             if self.final:
-                card = '?*'
+                card = '?1'
             else:
                 card = '**'
         self.card = card
+        self.rdefs = {}
+        for subjtype, dest_types in self.assoc_types:
+            for objtype in dest_types:
+                self.rdefs[(subjtype, objtype)] = mock(subject=subjtype, object=objtype, cardinality=self.card)
+
 
     def associations(self):
         return self.assoc_types
@@ -51,14 +70,6 @@
     def final(self):
         return self.obj_types[0] in FINAL_ETYPES
 
-    def iter_rdefs(self):
-        for subjtype, dest_types in self.assoc_types:
-            for objtype in dest_types:
-                yield subjtype, objtype
-
-    def rproperty(self, subj, obj, rprop):
-        assert rprop == 'cardinality'
-        return self.card
 
 class EntitySchema(ERSchema):
     def __init__(self, type, specialized_by=None):
@@ -124,7 +135,7 @@
                                          ('Student', ('Student',) ),
                                          ('Person', ('Student',) ),
                                          ),
-                                        symetric=True),
+                                        symmetric=True),
             'located' : RelationSchema( ( ('Person', ('Address',) ),
                                           ('Student', ('Address',) ),
                                           ('Company', ('Address',) ),
@@ -290,7 +301,7 @@
                                 {'X': 'Student', 'T': 'Eetype'}])
 
     def test_not(self):
-        node = self.helper.parse('Any X WHERE not X is Person')
+        node = self.helper.parse('Any X WHERE NOT X is Person')
         self.helper.compute_solutions(node, debug=DEBUG)
         sols = sorted(node.children[0].solutions)
         expected = ALL_SOLS[:]
@@ -300,19 +311,19 @@
     def test_uid_func_mapping(self):
         h = self.helper
         def type_from_uid(name):
-            self.assertEquals(name, "Logilab")
+            self.assertEqual(name, "Logilab")
             return 'Company'
         uid_func_mapping = {'name': type_from_uid}
         # constant as rhs of the uid relation
         node = h.parse('Any X WHERE X name "Logilab"')
         h.compute_solutions(node, uid_func_mapping, debug=DEBUG)
         sols = sorted(node.children[0].solutions)
-        self.assertEquals(sols, [{'X': 'Company'}])
+        self.assertEqual(sols, [{'X': 'Company'}])
         # variable as rhs of the uid relation
         node = h.parse('Any N WHERE X name N')
         h.compute_solutions(node, uid_func_mapping, debug=DEBUG)
         sols = sorted(node.children[0].solutions)
-        self.assertEquals(sols, [{'X': 'Company', 'N': 'String'},
+        self.assertEqual(sols, [{'X': 'Company', 'N': 'String'},
                                  {'X': 'Person', 'N': 'String'},
                                  {'X': 'Student', 'N': 'String'}])
         # substitute as rhs of the uid relation
@@ -320,19 +331,19 @@
         h.compute_solutions(node, uid_func_mapping, {'company': 'Logilab'},
                         debug=DEBUG)
         sols = sorted(node.children[0].solutions)
-        self.assertEquals(sols, [{'X': 'Company'}])
+        self.assertEqual(sols, [{'X': 'Company'}])
 
     def test_non_regr_subjobj1(self):
         h = self.helper
         def type_from_uid(name):
-            self.assertEquals(name, "Societe")
+            self.assertEqual(name, "Societe")
             return 'Eetype'
         uid_func_mapping = {'name': type_from_uid}
         # constant as rhs of the uid relation
         node = h.parse('Any X WHERE X name "Societe", X is ISOBJ, ISSIBJ is X')
         h.compute_solutions(node, uid_func_mapping, debug=DEBUG)
         sols = sorted(node.children[0].solutions)
-        self.assertEquals(sols, [{'X': 'Eetype', 'ISOBJ': 'Eetype', 'ISSIBJ': 'Address'},
+        self.assertEqual(sols, [{'X': 'Eetype', 'ISOBJ': 'Eetype', 'ISSIBJ': 'Address'},
                                  {'X': 'Eetype', 'ISOBJ': 'Eetype', 'ISSIBJ': 'Company'},
                                  {'X': 'Eetype', 'ISOBJ': 'Eetype', 'ISSIBJ': 'Eetype'},
                                  {'X': 'Eetype', 'ISOBJ': 'Eetype', 'ISSIBJ': 'Person'},
@@ -341,46 +352,46 @@
     def test_non_regr_subjobj2(self):
         h = self.helper
         def type_from_uid(name):
-            self.assertEquals(name, "Societe")
+            self.assertEqual(name, "Societe")
             return 'Eetype'
         uid_func_mapping = {'name': type_from_uid}
         node = h.parse('Any X WHERE X name "Societe", X is ISOBJ, ISSUBJ is X, X is_instance_of ISIOOBJ, ISIOSUBJ is_instance_of X')
         h.compute_solutions(node, uid_func_mapping, debug=DEBUG)
         select = node.children[0]
         sols = sorted(select.solutions)
-        self.assertEquals(len(sols), 25)
+        self.assertEqual(len(sols), 25)
         def var_sols(var):
             s = set()
             for sol in sols:
                 s.add(sol.get(var))
             return s
-        self.assertEquals(var_sols('X'), set(('Eetype',)))
-        self.assertEquals(var_sols('X'), select.defined_vars['X'].stinfo['possibletypes'])
-        self.assertEquals(var_sols('ISSUBJ'), set(('Address', 'Company', 'Eetype', 'Person', 'Student')))
-        self.assertEquals(var_sols('ISSUBJ'), select.defined_vars['ISSUBJ'].stinfo['possibletypes'])
-        self.assertEquals(var_sols('ISOBJ'), set(('Eetype',)))
-        self.assertEquals(var_sols('ISOBJ'), select.defined_vars['ISOBJ'].stinfo['possibletypes'])
-        self.assertEquals(var_sols('ISIOSUBJ'), set(('Address', 'Company', 'Eetype', 'Person', 'Student')))
-        self.assertEquals(var_sols('ISIOSUBJ'), select.defined_vars['ISIOSUBJ'].stinfo['possibletypes'])
-        self.assertEquals(var_sols('ISIOOBJ'), set(('Eetype',)))
-        self.assertEquals(var_sols('ISIOOBJ'), select.defined_vars['ISIOOBJ'].stinfo['possibletypes'])
+        self.assertEqual(var_sols('X'), set(('Eetype',)))
+        self.assertEqual(var_sols('X'), select.defined_vars['X'].stinfo['possibletypes'])
+        self.assertEqual(var_sols('ISSUBJ'), set(('Address', 'Company', 'Eetype', 'Person', 'Student')))
+        self.assertEqual(var_sols('ISSUBJ'), select.defined_vars['ISSUBJ'].stinfo['possibletypes'])
+        self.assertEqual(var_sols('ISOBJ'), set(('Eetype',)))
+        self.assertEqual(var_sols('ISOBJ'), select.defined_vars['ISOBJ'].stinfo['possibletypes'])
+        self.assertEqual(var_sols('ISIOSUBJ'), set(('Address', 'Company', 'Eetype', 'Person', 'Student')))
+        self.assertEqual(var_sols('ISIOSUBJ'), select.defined_vars['ISIOSUBJ'].stinfo['possibletypes'])
+        self.assertEqual(var_sols('ISIOOBJ'), set(('Eetype',)))
+        self.assertEqual(var_sols('ISIOOBJ'), select.defined_vars['ISIOOBJ'].stinfo['possibletypes'])
 
     def test_unusableuid_func_mapping(self):
         h = self.helper
         def type_from_uid(name):
-            self.assertEquals(name, "Logilab")
+            self.assertEqual(name, "Logilab")
             return 'Company'
         uid_func_mapping = {'name': type_from_uid}
         node = h.parse('Any X WHERE NOT X name %(company)s')
         h.compute_solutions(node, uid_func_mapping, {'company': 'Logilab'},
                             debug=DEBUG)
         sols = sorted(node.children[0].solutions)
-        self.assertEquals(sols, ALL_SOLS)
+        self.assertEqual(sols, ALL_SOLS)
         node = h.parse('Any X WHERE X name > %(company)s')
         h.compute_solutions(node, uid_func_mapping, {'company': 'Logilab'},
                             debug=DEBUG)
         sols = sorted(node.children[0].solutions)
-        self.assertEquals(sols, ALL_SOLS)
+        self.assertEqual(sols, ALL_SOLS)
 
 
     def test_base_guess_3(self):
@@ -416,7 +427,7 @@
         sols = sorted(node.children[0].solutions)
         self.assertEqual(sols, [{'E1': 'Company'}])
 
-    def test_not_symetric_relation_eid(self):
+    def test_not_symmetric_relation_eid(self):
         node = self.helper.parse('Any P WHERE X eid 0, NOT X connait P')
         self.helper.compute_solutions(node, debug=DEBUG)
         sols = sorted(node.children[0].solutions)
@@ -489,7 +500,7 @@
                          [{'X': 'Person', 'F': 'String'}])
         # auto-simplification
         self.assertEqual(len(node.children[0].with_[0].query.children), 1)
-        self.assertEquals(node.as_string(), 'Any L,Y,F WHERE Y located L, Y is Person WITH Y,F BEING (Any X,F WHERE X is Person, X firstname F)')
+        self.assertEqual(node.as_string(), 'Any L,Y,F WHERE Y located L, Y is Person WITH Y,F BEING (Any X,F WHERE X is Person, X firstname F)')
         self.assertEqual(node.children[0].with_[0].query.children[0].solutions,
                          [{'X': 'Person', 'F': 'String'}])
 
--- a/test/unittest_compare.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/test/unittest_compare.py	Thu May 05 10:50:51 2011 +0200
@@ -1,4 +1,21 @@
-from logilab.common.testlib import TestCase, unittest_main
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+from logilab.common.testlib import TestCase, SkipTest, unittest_main
 
 from rql import RQLHelper
 from unittest_analyze import RelationSchema, EntitySchema, DummySchema as BaseSchema
@@ -19,13 +36,16 @@
 
 class RQLCompareClassTest(TestCase):
     """ Compare RQL strings """
-    
+    @classmethod
+    def setUpClass(cls):
+        raise SkipTest('broken')
+
     def setUp(self):
         self.h = RQLHelper(DummySchema(), None)
-        
+
     def _compareEquivalent(self,r1,r2):
         """fails if the RQL strings r1 and r2 are equivalent"""
-        self.skip('broken')
+        self.skipTest('broken')
         self.failUnless(self.h.compare(r1, r2),
                         'r1: %s\nr2: %s' % (r1, r2))
 
@@ -37,7 +57,7 @@
     # equivalent queries ##################################################
 
     def test_same_request_simple(self):
-        r = "Any X where X is Note ;"
+        r = "Any X WHERE X is Note ;"
         self._compareEquivalent(r, r)
 
     def test_same_request_diff_names(self):
@@ -46,42 +66,42 @@
         self._compareEquivalent(r1, r2)
 
     def test_same_request_diff_names_simple(self):
-        r1 = "Any X where X is Note ;"
-        r2 = "Any Y where Y is Note ;"
+        r1 = "Any X WHERE X is Note ;"
+        r2 = "Any Y WHERE Y is Note ;"
         self._compareEquivalent(r1, r2)
 
     def test_same_request_any(self):
-        r1 = "Any X where X is Note ;"
+        r1 = "Any X WHERE X is Note ;"
         r2 = "Note X ;"
         self._compareEquivalent(r1, r2)
 
     def test_same_request_any_diff_names(self):
-        r1 = "Any X where X is Note ;"
+        r1 = "Any X WHERE X is Note ;"
         r2 = "Note Y ;"
         self._compareEquivalent(r1, r2)
 
     def test_same_request_complex(self):
-        r = "Any N, N2 where N is Note, N2 is Note, N a_faire_par P1, P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
+        r = "Any N, N2 WHERE N is Note, N2 is Note, N a_faire_par P1, P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
         self._compareEquivalent(r, r)
 
     def test_same_request_comma_and(self):
-        r1 = "Any N, N2 where N is Note, N2 is Note, N a_faire_par P1, P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
-        r2 = "Any N, N2 where N is Note AND N2 is Note AND N a_faire_par P1 AND P1 nom 'jphc' AND N2 a_faire_par P2 AND P2 nom 'ocy' ;"
+        r1 = "Any N, N2 WHERE N is Note, N2 is Note, N a_faire_par P1, P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
+        r2 = "Any N, N2 WHERE N is Note AND N2 is Note AND N a_faire_par P1 AND P1 nom 'jphc' AND N2 a_faire_par P2 AND P2 nom 'ocy' ;"
         self._compareEquivalent(r1, r2)
 
     def test_same_request_diff_names_complex(self):
-        r1 = "Any N, N2 where N is Note, N2 is Note, N a_faire_par P1, P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
-        r2 = "Any Y, X  where X is Note, Y is Note,  X a_faire_par A1, A1 nom 'ocy',  Y a_faire_par A2,  A2 nom 'jphc' ;"
+        r1 = "Any N, N2 WHERE N is Note, N2 is Note, N a_faire_par P1, P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
+        r2 = "Any Y, X  WHERE X is Note, Y is Note,  X a_faire_par A1, A1 nom 'ocy',  Y a_faire_par A2,  A2 nom 'jphc' ;"
         self._compareEquivalent(r1, r2)
 
     def test_same_request_diff_order(self):
-        r1 = "Any N, N2 where N is Note, N2 is Note, N a_faire_par P1, P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
-        r2 = "Any N, N2 where N2 is Note, N is Note, N a_faire_par P1, N2 a_faire_par P2, P2 nom 'ocy', P1 nom 'jphc' ;"
+        r1 = "Any N, N2 WHERE N is Note, N2 is Note, N a_faire_par P1, P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
+        r2 = "Any N, N2 WHERE N2 is Note, N is Note, N a_faire_par P1, N2 a_faire_par P2, P2 nom 'ocy', P1 nom 'jphc' ;"
         self._compareEquivalent(r1, r2)
 
     def test_same_request_diff_order_diff_names(self):
-        r1 = "Any N, N2 where N is Note, N2 is Note, N a_faire_par P1, P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
-        r2 = "Any Y, X  where X is Note, X a_faire_par P1, P1 nom 'ocy', Y is Note,    Y a_faire_par P2,  P2 nom 'jphc' ;"
+        r1 = "Any N, N2 WHERE N is Note, N2 is Note, N a_faire_par P1, P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
+        r2 = "Any Y, X  WHERE X is Note, X a_faire_par P1, P1 nom 'ocy', Y is Note,    Y a_faire_par P2,  P2 nom 'jphc' ;"
         self._compareEquivalent(r1, r2)
 
     def test_same_request_with_comparison(self):
@@ -117,8 +137,8 @@
     # non equivalent queries ##################################################
 
     def test_diff_request(self):
-        r1 = "Any N,  N2 where N is Note, N2 is Note, N a_faire_par P1, P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
-        r2 = "Any X where X is Note ;"
+        r1 = "Any N, N2 WHERE N is Note, N2 is Note, N a_faire_par P1, P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
+        r2 = "Any X WHERE X is Note ;"
         self._compareNotEquivalent(r1,r2)
 
     def test_diff_request_and_or(self):
@@ -132,28 +152,28 @@
         self._compareNotEquivalent(r1, r2)
 
     def test_diff_request_non_selected_var(self):
-        r1 = "Any X, D where X is Note, X creation_date D ;"
-        r2 = "Any X where X is Note, X creation_date D ;"
+        r1 = "Any X, D WHERE X is Note, X creation_date D ;"
+        r2 = "Any X WHERE X is Note, X creation_date D ;"
         self._compareNotEquivalent(r1, r2)
 
     def test_diff_request_aggregat(self):
-        r1 = "Any X, D where X is Note, X creation_date D ;"
-        r2 = "Any X, MAX(D) where X is Note, X creation_date D ;"
+        r1 = "Any X, D WHERE X is Note, X creation_date D ;"
+        r2 = "Any X, MAX(D) WHERE X is Note, X creation_date D ;"
         self._compareNotEquivalent(r1, r2)
 
     def test_diff_request_group(self):
-        r1 = "Any X where X is Note GROUPBY X ;"
-        r2 = "Any X where X is Note;"
+        r1 = "Any X GROUPBY X WHERE X is Note;"
+        r2 = "Any X WHERE X is Note;"
         self._compareNotEquivalent(r1, r2)
 
     def test_diff_request_sort(self):
-        r1 = "Any X where X is Note ORDERBY X ;"
-        r2 = "Any X where X is Note;"
+        r1 = "Any X ORDERBY X WHERE X is Note;"
+        r2 = "Any X WHERE X is Note;"
         self._compareNotEquivalent(r1, r2)
 
     def test_diff_request_not(self):
-        r1 = "Any X where not X is Note ;"
-        r2 = "Any X where X is Note;"
+        r1 = "Any X WHERE NOT X is Note ;"
+        r2 = "Any X WHERE X is Note;"
         self._compareNotEquivalent(r1, r2)
 
     def test_diff_request_not_in_or(self):
--- a/test/unittest_editextensions.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/test/unittest_editextensions.py	Thu May 05 10:50:51 2011 +0200
@@ -1,3 +1,20 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 
 from logilab.common.testlib import TestCase, unittest_main
 
@@ -15,12 +32,12 @@
         select.remove_selected(select.selection[0])
         select.add_selected(var)
         # check operations
-        self.assertEquals(rqlst.as_string(), 'Any %s WHERE X is Person' % var.name)
+        self.assertEqual(rqlst.as_string(), 'Any %s WHERE X is Person' % var.name)
         # check references before recovering
         rqlst.check_references()
         rqlst.recover()
         # check equivalence after recovering
-        self.assertEquals(rqlst.as_string(), orig)
+        self.assertEqual(rqlst.as_string(), orig)
         # check references after recovering
         rqlst.check_references()
     
@@ -33,12 +50,12 @@
         select.remove_selected(select.selection[0])
         select.add_selected(var)
         # check operations
-        self.assertEquals(rqlst.as_string(), 'Any %s WHERE X is Person, X name N' % var.name)
+        self.assertEqual(rqlst.as_string(), 'Any %s WHERE X is Person, X name N' % var.name)
         # check references before recovering
         rqlst.check_references()
         rqlst.recover()
         # check equivalence after recovering
-        self.assertEquals(rqlst.as_string(), orig)
+        self.assertEqual(rqlst.as_string(), orig)
         # check references after recovering
         rqlst.check_references()
         
@@ -48,12 +65,12 @@
         rqlst.save_state()
         rqlst.children[0].undefine_variable(rqlst.children[0].defined_vars['Y'])
         # check operations
-        self.assertEquals(rqlst.as_string(), 'Any X WHERE X is Person')
+        self.assertEqual(rqlst.as_string(), 'Any X WHERE X is Person')
         # check references before recovering
         rqlst.check_references()
         rqlst.recover()
         # check equivalence
-        self.assertEquals(rqlst.as_string(), orig)
+        self.assertEqual(rqlst.as_string(), orig)
         # check references after recovering
         rqlst.check_references()
         
@@ -65,12 +82,12 @@
         var = rqlst.children[0].make_variable()
         rqlst.children[0].add_selected(var)
         # check operations
-        self.assertEquals(rqlst.as_string(), 'Any A')
+        self.assertEqual(rqlst.as_string(), 'Any A')
         # check references before recovering
         rqlst.check_references()
         rqlst.recover()
         # check equivalence
-        self.assertEquals(rqlst.as_string(), orig)
+        self.assertEqual(rqlst.as_string(), orig)
         # check references after recovering
         rqlst.check_references()
         
--- a/test/unittest_nodes.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/test/unittest_nodes.py	Thu May 05 10:50:51 2011 +0200
@@ -1,4 +1,23 @@
 # -*- coding: iso-8859-1 -*-
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+
+from datetime import date, datetime
 
 from logilab.common.testlib import TestCase, unittest_main
 
@@ -17,23 +36,23 @@
 
 class EtypeFromPyobjTC(TestCase):
     def test_bool(self):
-        self.assertEquals(nodes.etype_from_pyobj(True), 'Boolean')
-        self.assertEquals(nodes.etype_from_pyobj(False), 'Boolean')
+        self.assertEqual(nodes.etype_from_pyobj(True), 'Boolean')
+        self.assertEqual(nodes.etype_from_pyobj(False), 'Boolean')
 
     def test_int(self):
-        self.assertEquals(nodes.etype_from_pyobj(0), 'Int')
-        self.assertEquals(nodes.etype_from_pyobj(1L), 'Int')
+        self.assertEqual(nodes.etype_from_pyobj(0), 'Int')
+        self.assertEqual(nodes.etype_from_pyobj(1L), 'Int')
 
     def test_float(self):
-        self.assertEquals(nodes.etype_from_pyobj(0.), 'Float')
+        self.assertEqual(nodes.etype_from_pyobj(0.), 'Float')
 
     def test_datetime(self):
-        self.assertEquals(nodes.etype_from_pyobj(nodes.now()), 'Datetime')
-        self.assertEquals(nodes.etype_from_pyobj(nodes.today()), 'Datetime')
+        self.assertEqual(nodes.etype_from_pyobj(datetime.now()), 'Datetime')
+        self.assertEqual(nodes.etype_from_pyobj(date.today()), 'Date')
 
     def test_string(self):
-        self.assertEquals(nodes.etype_from_pyobj('hop'), 'String')
-        self.assertEquals(nodes.etype_from_pyobj(u'hop'), 'String')
+        self.assertEqual(nodes.etype_from_pyobj('hop'), 'String')
+        self.assertEqual(nodes.etype_from_pyobj(u'hop'), 'String')
 
 
 class NodesTest(TestCase):
@@ -42,11 +61,11 @@
         tree.check_references()
         if normrql is None:
             normrql = rql
-        self.assertEquals(tree.as_string(), normrql)
+        self.assertEqual(tree.as_string(), normrql)
         # just check repr() doesn't raise an exception
         repr(tree)
         copy = tree.copy()
-        self.assertEquals(copy.as_string(), normrql)
+        self.assertEqual(copy.as_string(), normrql)
         copy.check_references()
         return tree
 
@@ -58,9 +77,9 @@
         #del d1['parent']; del d1['children'] # parent and children are slots now
         #d2 = tree2.__dict__.copy()
         #del d2['parent']; del d2['children']
-        self.assertNotEquals(id(tree1), id(tree2))
+        self.assertNotEqual(id(tree1), id(tree2))
         self.assert_(tree1.is_equivalent(tree2))
-        #self.assertEquals(len(tree1.children), len(tree2.children))
+        #self.assertEqual(len(tree1.children), len(tree2.children))
         #for i in range(len(tree1.children)):
         #    self.check_equal_but_not_same(tree1.children[i], tree2.children[i])
 
@@ -74,22 +93,22 @@
         self.assertRaises(BadRQLQuery, tree.set_limit, '1')
         tree.save_state()
         tree.set_limit(10)
-        self.assertEquals(select.limit, 10)
-        self.assertEquals(tree.as_string(), 'Any X LIMIT 10 WHERE X is Person')
+        self.assertEqual(select.limit, 10)
+        self.assertEqual(tree.as_string(), 'Any X LIMIT 10 WHERE X is Person')
         tree.recover()
-        self.assertEquals(select.limit, None)
-        self.assertEquals(tree.as_string(), 'Any X WHERE X is Person')
+        self.assertEqual(select.limit, None)
+        self.assertEqual(tree.as_string(), 'Any X WHERE X is Person')
 
     def test_union_set_limit_2(self):
         # not undoable set_limit since a new root has to be introduced
         tree = self._parse("(Any X WHERE X is Person) UNION (Any X WHERE X is Company)")
         tree.save_state()
         tree.set_limit(10)
-        self.assertEquals(tree.as_string(), 'Any A LIMIT 10 WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
+        self.assertEqual(tree.as_string(), 'Any A LIMIT 10 WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
         select = tree.children[0]
-        self.assertEquals(select.limit, 10)
+        self.assertEqual(select.limit, 10)
         tree.recover()
-        self.assertEquals(tree.as_string(), '(Any X WHERE X is Person) UNION (Any X WHERE X is Company)')
+        self.assertEqual(tree.as_string(), '(Any X WHERE X is Person) UNION (Any X WHERE X is Company)')
 
     def test_union_set_offset_1(self):
         tree = self._parse("Any X WHERE X is Person")
@@ -98,10 +117,10 @@
         self.assertRaises(BadRQLQuery, tree.set_offset, '1')
         tree.save_state()
         tree.set_offset(10)
-        self.assertEquals(select.offset, 10)
+        self.assertEqual(select.offset, 10)
         tree.recover()
-        self.assertEquals(select.offset, 0)
-        self.assertEquals(tree.as_string(), 'Any X WHERE X is Person')
+        self.assertEqual(select.offset, 0)
+        self.assertEqual(tree.as_string(), 'Any X WHERE X is Person')
 
     def test_union_set_offset_2(self):
         # not undoable set_offset since a new root has to be introduced
@@ -109,10 +128,10 @@
         tree.save_state()
         tree.set_offset(10)
         select = tree.children[0]
-        self.assertEquals(tree.as_string(), 'Any A OFFSET 10 WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
-        self.assertEquals(select.offset, 10)
+        self.assertEqual(tree.as_string(), 'Any A OFFSET 10 WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
+        self.assertEqual(select.offset, 10)
         tree.recover()
-        self.assertEquals(tree.as_string(), '(Any X WHERE X is Person) UNION (Any X WHERE X is Company)')
+        self.assertEqual(tree.as_string(), '(Any X WHERE X is Person) UNION (Any X WHERE X is Company)')
 
     def test_union_undo_add_rel(self):
         tree = self._parse("Any A WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))")
@@ -121,34 +140,34 @@
         var = select.make_variable()
         mainvar = select.selection[0].variable
         select.add_relation(mainvar, 'name', var)
-        self.assertEquals(tree.as_string(), 'Any A WHERE A name B WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
+        self.assertEqual(tree.as_string(), 'Any A WHERE A name B WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
         tree.recover()
-        self.assertEquals(tree.as_string(), 'Any A WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
+        self.assertEqual(tree.as_string(), 'Any A WITH A BEING ((Any X WHERE X is Person) UNION (Any X WHERE X is Company))')
 
     def test_select_set_limit(self):
         tree = self._simpleparse("Any X WHERE X is Person")
-        self.assertEquals(tree.limit, None)
+        self.assertEqual(tree.limit, None)
         self.assertRaises(BadRQLQuery, tree.set_limit, 0)
         self.assertRaises(BadRQLQuery, tree.set_limit, -1)
         self.assertRaises(BadRQLQuery, tree.set_limit, '1')
         tree.save_state()
         tree.set_limit(10)
-        self.assertEquals(tree.limit, 10)
+        self.assertEqual(tree.limit, 10)
         tree.recover()
-        self.assertEquals(tree.limit, None)
+        self.assertEqual(tree.limit, None)
 
     def test_select_set_offset(self):
         tree = self._simpleparse("Any X WHERE X is Person")
         self.assertRaises(BadRQLQuery, tree.set_offset, -1)
         self.assertRaises(BadRQLQuery, tree.set_offset, '1')
-        self.assertEquals(tree.offset, 0)
+        self.assertEqual(tree.offset, 0)
         tree.save_state()
         tree.set_offset(0)
-        self.assertEquals(tree.offset, 0)
+        self.assertEqual(tree.offset, 0)
         tree.set_offset(10)
-        self.assertEquals(tree.offset, 10)
+        self.assertEqual(tree.offset, 10)
         tree.recover()
-        self.assertEquals(tree.offset, 0)
+        self.assertEqual(tree.offset, 0)
 
     def test_select_add_sort_var(self):
         tree = self._parse('Any X')
@@ -156,10 +175,10 @@
         select = tree.children[0]
         select.add_sort_var(select.get_variable('X'))
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X ORDERBY X')
+        self.assertEqual(tree.as_string(), 'Any X ORDERBY X')
         tree.recover()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X')
+        self.assertEqual(tree.as_string(), 'Any X')
 
     def test_select_remove_sort_terms(self):
         tree = self._parse('Any X,Y ORDERBY X,Y')
@@ -167,25 +186,40 @@
         select = tree.children[0]
         select.remove_sort_terms()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X,Y')
+        self.assertEqual(tree.as_string(), 'Any X,Y')
         tree.recover()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X,Y ORDERBY X,Y')
+        self.assertEqual(tree.as_string(), 'Any X,Y ORDERBY X,Y')
+
+    def test_select_undefine_variable(self):
+        tree = sparse('Any X,Y ORDERBY X,Y WHERE X work_for Y')
+        tree.save_state()
+        select = tree.children[0]
+        select.undefine_variable(select.defined_vars['Y'])
+        self.assertEqual(select.solutions, [{'X': 'Person'},
+                                             {'X': 'Student'}])
+        tree.check_references()
+        self.assertEqual(tree.as_string(), 'Any X ORDERBY X')
+        tree.recover()
+        tree.check_references()
+        self.assertEqual(tree.as_string(), 'Any X,Y ORDERBY X,Y WHERE X work_for Y')
+        self.assertEqual(select.solutions, [{'X': 'Person', 'Y': 'Company'},
+                                             {'X': 'Student', 'Y': 'Company'}])
 
     def test_select_set_distinct(self):
         tree = self._parse('DISTINCT Any X')
         tree.save_state()
         select = tree.children[0]
-        self.assertEquals(select.distinct, True)
+        self.assertEqual(select.distinct, True)
         tree.save_state()
         select.set_distinct(True)
-        self.assertEquals(select.distinct, True)
+        self.assertEqual(select.distinct, True)
         tree.recover()
-        self.assertEquals(select.distinct, True)
+        self.assertEqual(select.distinct, True)
         select.set_distinct(False)
-        self.assertEquals(select.distinct, False)
+        self.assertEqual(select.distinct, False)
         tree.recover()
-        self.assertEquals(select.distinct, True)
+        self.assertEqual(select.distinct, True)
 
     def test_select_add_group_var(self):
         tree = self._parse('Any X')
@@ -193,10 +227,10 @@
         select = tree.children[0]
         select.add_group_var(select.get_variable('X'))
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X GROUPBY X')
+        self.assertEqual(tree.as_string(), 'Any X GROUPBY X')
         tree.recover()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X')
+        self.assertEqual(tree.as_string(), 'Any X')
 
     def test_select_remove_group_var(self):
         tree = self._parse('Any X GROUPBY X')
@@ -204,10 +238,10 @@
         select = tree.children[0]
         select.remove_group_var(select.groupby[0])
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X')
+        self.assertEqual(tree.as_string(), 'Any X')
         tree.recover()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X GROUPBY X')
+        self.assertEqual(tree.as_string(), 'Any X GROUPBY X')
 
     def test_select_remove_groups(self):
         tree = self._parse('Any X,Y GROUPBY X,Y')
@@ -215,10 +249,10 @@
         select = tree.children[0]
         select.remove_groups()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X,Y')
+        self.assertEqual(tree.as_string(), 'Any X,Y')
         tree.recover()
         tree.check_references()
-        self.assertEquals(tree.as_string(), 'Any X,Y GROUPBY X,Y')
+        self.assertEqual(tree.as_string(), 'Any X,Y GROUPBY X,Y')
 
     def test_select_base_1(self):
         tree = self._parse("Any X WHERE X is Person")
@@ -333,15 +367,34 @@
     def test_selected_index(self):
         tree = self._simpleparse("Any X ORDERBY N DESC WHERE X is Person, X name N")
         annotator.annotate(tree)
-        self.assertEquals(tree.defined_vars['X'].selected_index(), 0)
-        self.assertEquals(tree.defined_vars['N'].selected_index(), None)
+        self.assertEqual(tree.defined_vars['X'].selected_index(), 0)
+        self.assertEqual(tree.defined_vars['N'].selected_index(), None)
+
+    def test_get_variable_indices_1(self):
+        dummy = self._parse("Any A,B,C")
+        dummy.children[0].solutions = [{'A': 'String', 'B': 'EUser', 'C': 'EGroup'},
+                                       {'A': 'String', 'B': 'Personne', 'C': 'EGroup'},
+                                       {'A': 'String', 'B': 'EUser', 'C': 'Societe'}]
+        self.assertEqual(dummy.get_variable_indices(), set([1, 2]))
 
-    def test_get_variable_variables(self):
-        dummy = self._simpleparse("Any X")
-        dummy.solutions = [{'A': 'String', 'B': 'EUser', 'C': 'EGroup'},
-                           {'A': 'String', 'B': 'Personne', 'C': 'EGroup'},
-                           {'A': 'String', 'B': 'EUser', 'C': 'Societe'}]
-        self.assertEquals(dummy.get_variable_variables(), set(['B', 'C']))
+    def test_get_variable_indices_2(self):
+        dummy = self._parse("Any A,B WHERE B relation C")
+        dummy.children[0].solutions = [{'A': 'String', 'B': 'EUser', 'C': 'EGroup'},
+                                       {'A': 'String', 'B': 'Personne', 'C': 'EGroup'},
+                                       {'A': 'String', 'B': 'EUser', 'C': 'Societe'}]
+        self.assertEqual(dummy.get_variable_indices(), set([1]))
+
+    def test_get_variable_indices_3(self):
+        dummy = self._parse("(Any X WHERE X is EGroup) UNION (Any C WHERE C is EUser)")
+        dummy.children[0].solutions = [{'X': 'EGroup'}]
+        dummy.children[1].solutions = [{'C': 'EUser'}]
+        self.assertEqual(dummy.get_variable_indices(), set([0]))
+
+    def test_get_variable_indices_4(self):
+        dummy = self._parse("(Any X,XN WHERE X is EGroup, X name XN) UNION (Any C,CL WHERE C is EUser, C login CL)")
+        dummy.children[0].solutions = [{'X': 'EGroup', 'XN': 'String'}]
+        dummy.children[1].solutions = [{'C': 'EUser', 'CL': 'String'}]
+        self.assertEqual(dummy.get_variable_indices(), set([0]))
 
     # insertion tests #########################################################
 
@@ -420,40 +473,40 @@
 
     def test_as_string(self):
         tree = parse("SET X know Y WHERE X friend Y;")
-        self.assertEquals(tree.as_string(), 'SET X know Y WHERE X friend Y')
+        self.assertEqual(tree.as_string(), 'SET X know Y WHERE X friend Y')
 
         tree = parse("Person X")
-        self.assertEquals(tree.as_string(),
+        self.assertEqual(tree.as_string(),
                           'Any X WHERE X is Person')
 
         tree = parse(u"Any X WHERE X has_text 'hh'")
-        self.assertEquals(tree.as_string('utf8'),
+        self.assertEqual(tree.as_string('utf8'),
                           u'Any X WHERE X has_text "hh"'.encode('utf8'))
         tree = parse(u"Any X WHERE X has_text %(text)s")
-        self.assertEquals(tree.as_string('utf8', {'text': u'hh'}),
+        self.assertEqual(tree.as_string('utf8', {'text': u'hh'}),
                           u'Any X WHERE X has_text "hh"'.encode('utf8'))
         tree = parse(u"Any X WHERE X has_text %(text)s")
-        self.assertEquals(tree.as_string('utf8', {'text': u'h"h'}),
+        self.assertEqual(tree.as_string('utf8', {'text': u'h"h'}),
                           u'Any X WHERE X has_text "h\\"h"'.encode('utf8'))
         tree = parse(u"Any X WHERE X has_text %(text)s")
-        self.assertEquals(tree.as_string('utf8', {'text': u'h"\'h'}),
+        self.assertEqual(tree.as_string('utf8', {'text': u'h"\'h'}),
                           u'Any X WHERE X has_text "h\\"\'h"'.encode('utf8'))
 
     def test_as_string_no_encoding(self):
         tree = parse(u"Any X WHERE X has_text 'hh'")
-        self.assertEquals(tree.as_string(),
+        self.assertEqual(tree.as_string(),
                           u'Any X WHERE X has_text "hh"')
         tree = parse(u"Any X WHERE X has_text %(text)s")
-        self.assertEquals(tree.as_string(kwargs={'text': u'hh'}),
+        self.assertEqual(tree.as_string(kwargs={'text': u'hh'}),
                           u'Any X WHERE X has_text "hh"')
 
     def test_as_string_now_today_null(self):
         tree = parse(u"Any X WHERE X name NULL")
-        self.assertEquals(tree.as_string(), 'Any X WHERE X name NULL')
+        self.assertEqual(tree.as_string(), 'Any X WHERE X name NULL')
         tree = parse(u"Any X WHERE X creation_date NOW")
-        self.assertEquals(tree.as_string(), 'Any X WHERE X creation_date NOW')
+        self.assertEqual(tree.as_string(), 'Any X WHERE X creation_date NOW')
         tree = parse(u"Any X WHERE X creation_date TODAY")
-        self.assertEquals(tree.as_string(), 'Any X WHERE X creation_date TODAY')
+        self.assertEqual(tree.as_string(), 'Any X WHERE X creation_date TODAY')
 
     # sub-queries tests #######################################################
 
@@ -466,19 +519,19 @@
         select.recover()
         X = select.get_variable('X')
         N = select.get_variable('N')
-        self.assertEquals(len(X.references()), 3)
-        self.assertEquals(len(N.references()), 2)
+        self.assertEqual(len(X.references()), 3)
+        self.assertEqual(len(N.references()), 2)
         tree.schema = schema
         #annotator.annotate(tree)
         # XXX how to choose
-        self.assertEquals(X.get_type(), 'Company')
-        self.assertEquals(X.get_type({'X': 'Person'}), 'Person')
-        #self.assertEquals(N.get_type(), 'String')
-        self.assertEquals(X.get_description(0, lambda x:x), 'Company, Person, Student')
-        self.assertEquals(N.get_description(0, lambda x:x), 'firstname, name')
-        self.assertEquals(X.selected_index(), 0)
-        self.assertEquals(N.selected_index(), None)
-        self.assertEquals(X.main_relation(), None)
+        self.assertEqual(X.get_type(), 'Company')
+        self.assertEqual(X.get_type({'X': 'Person'}), 'Person')
+        #self.assertEqual(N.get_type(), 'String')
+        self.assertEqual(X.get_description(0, lambda x:x), 'Company, Person, Student')
+        self.assertEqual(N.get_description(0, lambda x:x), 'firstname, name')
+        self.assertEqual(X.selected_index(), 0)
+        self.assertEqual(N.selected_index(), None)
+        self.assertEqual(X.main_relation(), None)
 
     # non regression tests ####################################################
 
@@ -516,33 +569,33 @@
     def test_known_values_1(self):
         tree = parse('Any X where X name "turlututu"').children[0]
         constants = tree.get_nodes(nodes.Constant)
-        self.assertEquals(len(constants), 1)
-        self.assertEquals(isinstance(constants[0], nodes.Constant), 1)
-        self.assertEquals(constants[0].value, 'turlututu')
+        self.assertEqual(len(constants), 1)
+        self.assertEqual(isinstance(constants[0], nodes.Constant), 1)
+        self.assertEqual(constants[0].value, 'turlututu')
 
     def test_known_values_2(self):
         tree = parse('Any X where X name "turlututu", Y know X, Y name "chapo pointu"').children[0]
         varrefs = tree.get_nodes(nodes.VariableRef)
-        self.assertEquals(len(varrefs), 5)
+        self.assertEqual(len(varrefs), 5)
         for varref in varrefs:
             self.assertIsInstance(varref, nodes.VariableRef)
-        self.assertEquals(sorted(x.name for x in varrefs),
+        self.assertEqual(sorted(x.name for x in varrefs),
                           ['X', 'X', 'X', 'Y', 'Y'])
 
     def test_iknown_values_1(self):
         tree = parse('Any X where X name "turlututu"').children[0]
         constants = list(tree.iget_nodes(nodes.Constant))
-        self.assertEquals(len(constants), 1)
-        self.assertEquals(isinstance(constants[0], nodes.Constant), 1)
-        self.assertEquals(constants[0].value, 'turlututu')
+        self.assertEqual(len(constants), 1)
+        self.assertEqual(isinstance(constants[0], nodes.Constant), 1)
+        self.assertEqual(constants[0].value, 'turlututu')
 
     def test_iknown_values_2(self):
         tree = parse('Any X where X name "turlututu", Y know X, Y name "chapo pointu"').children[0]
         varrefs = list(tree.iget_nodes(nodes.VariableRef))
-        self.assertEquals(len(varrefs), 5)
+        self.assertEqual(len(varrefs), 5)
         for varref in varrefs:
             self.assertIsInstance(varref, nodes.VariableRef)
-        self.assertEquals(sorted(x.name for x in varrefs),
+        self.assertEqual(sorted(x.name for x in varrefs),
                           ['X', 'X', 'X', 'Y', 'Y'])
 
 if __name__ == '__main__':
--- a/test/unittest_parser.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/test/unittest_parser.py	Thu May 05 10:50:51 2011 +0200
@@ -1,4 +1,21 @@
 # -*- coding: iso-8859-1 -*-
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 
 from logilab.common.testlib import TestCase, unittest_main
 
@@ -46,6 +63,7 @@
     'Any X WHERE X eid 53;',
     'Any X WHERE X eid -53;',
     "Document X WHERE X occurence_of F, F class C, C name 'Bande dessine', X owned_by U, U login 'syt', X available true;",
+    u"Document X WHERE X occurence_of F, F class C, C name 'Bande dessine', X owned_by U, U login 'syt', X available true;",
     "Personne P WHERE P travaille_pour S, S nom 'Eurocopter', P interesse_par T, T nom 'formation';",
     "Note N WHERE N ecrit_le D, D day > (today -10), N ecrit_par P, P nom 'jphc' or P nom 'ocy';",
     "Personne P WHERE (P interesse_par T, T nom 'formation') or (P ville 'Paris');",
@@ -99,7 +117,42 @@
     ' WITH T1,T2 BEING ('
     '      (Any X,N WHERE X name N, X transition_of E, E name %(name)s)'
     '       UNION '
-    '      (Any X,N WHERE X name N, X state_of E, E name %(name)s))',
+    '      (Any X,N WHERE X name N, X state_of E, E name %(name)s));',
+
+
+    'Any T2'
+    ' GROUPBY T2'
+    ' WHERE T1 relation T2'
+    ' HAVING COUNT(T1) IN (1,2);',
+
+    'Any T2'
+    ' GROUPBY T2'
+    ' WHERE T1 relation T2'
+    ' HAVING COUNT(T1) IN (1,2) OR COUNT(T1) IN (3,4);',
+
+    'Any T2'
+    ' GROUPBY T2'
+    ' WHERE T1 relation T2'
+    ' HAVING 1 < COUNT(T1) OR COUNT(T1) IN (3,4);',
+
+    'Any T2'
+    ' GROUPBY T2'
+    ' WHERE T1 relation T2'
+    ' HAVING (COUNT(T1) IN (1,2)) OR (COUNT(T1) IN (3,4));',
+
+    'Any T2'
+    ' GROUPBY T2'
+    ' WHERE T1 relation T2'
+    ' HAVING (1 < COUNT(T1) OR COUNT(T1) IN (3,4));',
+
+    'Any T2'
+    ' GROUPBY T2'
+    ' WHERE T1 relation T2'
+    ' HAVING 1+2 < COUNT(T1);',
+
+    'Any X,Y,A ORDERBY Y '
+    'WHERE A done_for Y, X split_into Y, A diem D '
+    'HAVING MIN(D) < "2010-07-01", MAX(D) >= "2010-07-01";',
     )
 
 class ParserHercule(TestCase):
@@ -120,6 +173,14 @@
                 print string, ex
             raise
 
+    def test_unicode_constant(self):
+        tree = self.parse(u"Any X WHERE X name 'ngstrm';")
+        base = tree.children[0].where
+        comparison = base.children[1]
+        self.failUnless(isinstance(comparison, nodes.Comparison))
+        rhs = comparison.children[0]
+        self.assertEqual(type(rhs.value), unicode)
+
     def test_precedence_1(self):
         tree = self.parse("Any X WHERE X firstname 'lulu' AND X name 'toto' OR X name 'tutu';")
         base = tree.children[0].where
@@ -211,22 +272,22 @@
         tree = self.parse("Any X WHERE X firstname %(firstname)s;")
         cste = tree.children[0].where.children[1].children[0]
         self.assert_(isinstance(cste, nodes.Constant))
-        self.assertEquals(cste.type, 'Substitute')
-        self.assertEquals(cste.value, 'firstname')
+        self.assertEqual(cste.type, 'Substitute')
+        self.assertEqual(cste.value, 'firstname')
 
     def test_optional_relation(self):
         tree = self.parse(r'Any X WHERE X related Y;')
         related = tree.children[0].where
-        self.assertEquals(related.optional, None)
+        self.assertEqual(related.optional, None)
         tree = self.parse(r'Any X WHERE X? related Y;')
         related = tree.children[0].where
-        self.assertEquals(related.optional, 'left')
+        self.assertEqual(related.optional, 'left')
         tree = self.parse(r'Any X WHERE X related Y?;')
         related = tree.children[0].where
-        self.assertEquals(related.optional, 'right')
+        self.assertEqual(related.optional, 'right')
         tree = self.parse(r'Any X WHERE X? related Y?;')
         related = tree.children[0].where
-        self.assertEquals(related.optional, 'both')
+        self.assertEqual(related.optional, 'both')
 
     def test_exists(self):
         tree = self.parse("Any X WHERE X firstname 'lulu',"
@@ -240,9 +301,9 @@
 
     def test_etype(self):
         tree = self.parse('EmailAddress X;')
-        self.assertEquals(tree.as_string(), 'Any X WHERE X is EmailAddress')
+        self.assertEqual(tree.as_string(), 'Any X WHERE X is EmailAddress')
         tree = self.parse('EUser X;')
-        self.assertEquals(tree.as_string(), 'Any X WHERE X is EUser')
+        self.assertEqual(tree.as_string(), 'Any X WHERE X is EUser')
 
     def test_spec(self):
         """test all RQL string found in the specification and test they are well parsed"""
--- a/test/unittest_rqlgen.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/test/unittest_rqlgen.py	Thu May 05 10:50:51 2011 +0200
@@ -1,3 +1,20 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 """
 Copyright (c) 2000-2008 LOGILAB S.A. (Paris, FRANCE).
 http://www.logilab.fr/ -- mailto:contact@logilab.fr
@@ -24,21 +41,21 @@
         """tests select with entity type only
         """
         rql = self.rql_generator.select('Person')
-        self.assertEquals(rql, 'Person X')
+        self.assertEqual(rql, 'Person X')
         
 
     def test_select_group(self):
         """tests select with group
         """
         rql = self.rql_generator.select('Person', groups=('X',))
-        self.assertEquals(rql, 'Person X\nGROUPBY X')
+        self.assertEqual(rql, 'Person X\nGROUPBY X')
 
 
     def test_select_sort(self):
         """tests select with sort
         """
         rql = self.rql_generator.select('Person', sorts=('X ASC',))
-        self.assertEquals(rql, 'Person X\nSORTBY X ASC')
+        self.assertEqual(rql, 'Person X\nSORTBY X ASC')
 
 
     def test_select(self):
@@ -51,7 +68,7 @@
                                           ('X','surname','S') ),
                                         ('X',),
                                         ('F ASC', 'S DESC'))
-        self.assertEquals(rql, 'Person X\nWHERE X work_for S , S name "Logilab"'
+        self.assertEqual(rql, 'Person X\nWHERE X work_for S , S name "Logilab"'
                           ' , X firstname F , X surname S\nGROUPBY X'
                           '\nSORTBY F ASC, S DESC')
                                         
@@ -63,7 +80,7 @@
                                          ('S','name','"Logilab"'),
                                          ('X','firstname','F'),
                                          ('X','surname','S') ) )
-        self.assertEquals(rql, 'WHERE X work_for S , S name "Logilab" '
+        self.assertEqual(rql, 'WHERE X work_for S , S name "Logilab" '
                           ', X firstname F , X surname S')
 
 
@@ -71,14 +88,14 @@
         """tests the groupby() method behaviour
         """
         rql = self.rql_generator.groupby(('F', 'S'))
-        self.assertEquals(rql, 'GROUPBY F, S')
+        self.assertEqual(rql, 'GROUPBY F, S')
         
 
     def test_sortby(self):
         """tests the sortby() method behaviour
         """
         rql = self.rql_generator.sortby(('F ASC', 'S DESC'))
-        self.assertEquals(rql, 'SORTBY F ASC, S DESC')
+        self.assertEqual(rql, 'SORTBY F ASC, S DESC')
         
 
     def test_insert(self):
@@ -86,7 +103,7 @@
         """
         rql = self.rql_generator.insert('Person', (('firstname', "Clark"),
                                                    ('lastname', "Kent")))
-        self.assertEquals(rql, 'INSERT Person X: X firstname "Clark",'
+        self.assertEqual(rql, 'INSERT Person X: X firstname "Clark",'
                           ' X lastname "Kent"')
         
         
@@ -98,7 +115,7 @@
                                          ('lastname', "Kent")),
                                         (('job', "superhero"),
                                          ('nick', "superman")))
-        self.assertEquals(rql, 'SET X job "superhero", X nick "superman" '
+        self.assertEqual(rql, 'SET X job "superhero", X nick "superman" '
                           'WHERE X is "Person", X firstname "Clark", X '
                           'lastname "Kent"')
 
@@ -109,7 +126,7 @@
         rql = self.rql_generator.delete('Person',
                                         (('firstname', "Clark"),
                                          ('lastname', "Kent")))
-        self.assertEquals(rql, 'DELETE Person X where X firstname "Clark", '
+        self.assertEqual(rql, 'DELETE Person X where X firstname "Clark", '
                           'X lastname "Kent"')
         
 if __name__ == '__main__':
--- a/test/unittest_stcheck.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/test/unittest_stcheck.py	Thu May 05 10:50:51 2011 +0200
@@ -1,6 +1,25 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 from logilab.common.testlib import TestCase, unittest_main
+
+from rql import RQLHelper, BadRQLQuery, stmts, nodes
+
 from unittest_analyze import DummySchema
-from rql import RQLHelper, BadRQLQuery, stmts, nodes
 
 BAD_QUERIES = (
     'Any X, Y GROUPBY X',
@@ -33,10 +52,17 @@
 
     'Any X WHERE X name "Toto", P is Person',
 
-    # BAD QUERY cant sort on y
+    "Any X WHERE X eid 0, X eid 1",
+
+    # DISTINCT+ORDERBY tests ###################################################
+    # cant sort on Y, B <- work_for X is multivalued
     'DISTINCT Any X ORDERBY Y WHERE B work_for X, B name Y',
-
-    "Any X WHERE X eid 0, X eid 1"
+    # cant sort on PN, there may be different PF values for the same PN value
+    # XXX untrue if PF or PN is marked as unique
+    'DISTINCT Any PF ORDERBY PN WHERE P firstname PF, P name PN',
+    # cant sort on XN, there may be different PF values for the same PF value
+    'DISTINCT Any PF ORDERBY X WHERE P work_for X, P firstname PF',
+    'DISTINCT Any PF ORDERBY XN WHERE P work_for X, P firstname PF, X name XN',
 
     )
 
@@ -47,12 +73,12 @@
 
     'DISTINCT Any X, MAX(Y) GROUPBY X WHERE X is Person, Y is Company',
 
+    # DISTINCT+ORDERBY tests ###################################################
     # sorting allowed since order variable reachable from a selected
     # variable with only ?1 cardinality
-    'DISTINCT Any B ORDERBY Y WHERE B work_for X, B name Y',
-    'DISTINCT Any B ORDERBY Y WHERE B work_for X, X name Y',
+    'DISTINCT Any P ORDERBY PN WHERE P work_for X, P name PN',
+    'DISTINCT Any P ORDERBY XN WHERE P work_for X, X name XN',
 
-#    'DISTINCT Any X ORDERBY SN WHERE X in_state S, S name SN',
 
 
     )
@@ -83,7 +109,7 @@
     def _test_rewrite(self, rql, expected):
         rqlst = self.parse(rql)
         self.simplify(rqlst)
-        self.assertEquals(rqlst.as_string(), expected)
+        self.assertEqual(rqlst.as_string(), expected)
 
     def test_rewrite(self):
         for rql, expected in (
@@ -108,7 +134,7 @@
 # no more supported, use outerjoin explicitly
 #            ('Any X,Y WHERE X work_for Y OR NOT X work_for Y', 'Any X,Y WHERE X? work_for Y?'),
 #            ('Any X,Y WHERE NOT X work_for Y OR X work_for Y', 'Any X,Y WHERE X? work_for Y?'),
-            # test symetric OR rewrite
+            # test symmetric OR rewrite
             ("DISTINCT Any P WHERE P connait S OR S connait P, S name 'chouette'",
              "DISTINCT Any P WHERE P connait S, S name 'chouette'"),
             # queries that should not be rewritten
@@ -151,6 +177,12 @@
             # A eid 12 can be removed since the type analyzer checked its existence
             ('Any X WHERE A eid 12, X connait Y',
              'Any X WHERE X connait Y'),
+
+            ('Any X WHERE EXISTS(X work_for Y, Y eid 12) OR X eid 12',
+             'Any X WHERE (EXISTS(X work_for 12)) OR (X eid 12)'),
+
+            ('Any X WHERE EXISTS(X work_for Y, Y eid IN (12)) OR X eid IN (12)',
+             'Any X WHERE (EXISTS(X work_for 12)) OR (X eid 12)'),
             ):
             yield self._test_rewrite, rql, expected
 
@@ -162,20 +194,20 @@
                             'VC work_for S, S name "draft" '
                             'WITH VF, VC, VCD BEING (Any VF, MAX(VC), VCD GROUPBY VF, VCD '
                             '                        WHERE VC connait VF, VC creation_date VCD)'))
-        self.assertEquals(rqlst.children[0].vargraph,
+        self.assertEqual(rqlst.children[0].vargraph,
                           {'VCD': ['VC'], 'VF': ['VC'], 'S': ['VC'], 'VC': ['S', 'VF', 'VCD'],
                            ('VC', 'S'): 'work_for',
                            ('VC', 'VF'): 'connait',
                            ('VC', 'VCD'): 'creation_date'})
-        self.assertEquals(rqlst.children[0].aggregated, set(('VC',)))
+        self.assertEqual(rqlst.children[0].aggregated, set(('VC',)))
 
 
 ##     def test_rewriten_as_string(self):
 ##         rqlst = self.parse('Any X WHERE X eid 12')
-##         self.assertEquals(rqlst.as_string(), 'Any X WHERE X eid 12')
+##         self.assertEqual(rqlst.as_string(), 'Any X WHERE X eid 12')
 ##         rqlst = rqlst.copy()
 ##         self.annotate(rqlst)
-##         self.assertEquals(rqlst.as_string(), 'Any X WHERE X eid 12')
+##         self.assertEqual(rqlst.as_string(), 'Any X WHERE X eid 12')
 
 class CopyTest(TestCase):
 
@@ -198,7 +230,7 @@
         root = self.parse('Any X,U WHERE C owned_by U, NOT X owned_by U, X eid 1, C eid 2')
         self.simplify(root)
         stmt = root.children[0]
-        self.assertEquals(stmt.defined_vars['U'].valuable_references(), 3)
+        self.assertEqual(stmt.defined_vars['U'].valuable_references(), 3)
         copy = stmts.Select()
         copy.append_selected(stmt.selection[0].copy(copy))
         copy.append_selected(stmt.selection[1].copy(copy))
@@ -207,8 +239,8 @@
         newroot.append(copy)
         self.annotate(newroot)
         self.simplify(newroot)
-        self.assertEquals(newroot.as_string(), 'Any 1,U WHERE 2 owned_by U, NOT 1 owned_by U')
-        self.assertEquals(copy.defined_vars['U'].valuable_references(), 3)
+        self.assertEqual(newroot.as_string(), 'Any 1,U WHERE 2 owned_by U, NOT EXISTS(1 owned_by U)')
+        self.assertEqual(copy.defined_vars['U'].valuable_references(), 3)
 
 
 class AnnotateTest(TestCase):
@@ -222,27 +254,64 @@
 #         self.annotate(rqlst)
 #         self.failUnless(rqlst.defined_vars['L'].stinfo['attrvar'])
 
-    def test_is_rel_no_scope(self):
-        """is relation used as type restriction should not affect variable's scope,
-        and should not be included in stinfo['relations']"""
+    def test_is_rel_no_scope_1(self):
+        """is relation used as type restriction should not affect variable's
+        scope, and should not be included in stinfo['relations']
+        """
         rqlst = self.parse('Any X WHERE C is Company, EXISTS(X work_for C)').children[0]
         C = rqlst.defined_vars['C']
         self.failIf(C.scope is rqlst, C.scope)
-        self.assertEquals(len(C.stinfo['relations']), 1)
+        self.assertEqual(len(C.stinfo['relations']), 1)
+
+    def test_is_rel_no_scope_2(self):
         rqlst = self.parse('Any X, ET WHERE C is ET, EXISTS(X work_for C)').children[0]
         C = rqlst.defined_vars['C']
         self.failUnless(C.scope is rqlst, C.scope)
-        self.assertEquals(len(C.stinfo['relations']), 2)
+        self.assertEqual(len(C.stinfo['relations']), 2)
+
+
+    def test_not_rel_normalization_1(self):
+        rqlst = self.parse('Any X WHERE C is Company, NOT X work_for C').children[0]
+        self.assertEqual(rqlst.as_string(), 'Any X WHERE C is Company, NOT EXISTS(X work_for C)')
+        C = rqlst.defined_vars['C']
+        self.failIf(C.scope is rqlst, C.scope)
+
+    def test_not_rel_normalization_2(self):
+        rqlst = self.parse('Any X, ET WHERE C is ET, NOT X work_for C').children[0]
+        self.assertEqual(rqlst.as_string(), 'Any X,ET WHERE C is ET, NOT EXISTS(X work_for C)')
+        C = rqlst.defined_vars['C']
+        self.failUnless(C.scope is rqlst, C.scope)
 
-    def test_subquery_annotation(self):
+    def test_not_rel_normalization_3(self):
+        rqlst = self.parse('Any X WHERE C is Company, X work_for C, NOT C name "World Company"').children[0]
+        self.assertEqual(rqlst.as_string(), "Any X WHERE C is Company, X work_for C, NOT C name 'World Company'")
+        C = rqlst.defined_vars['C']
+        self.failUnless(C.scope is rqlst, C.scope)
+
+    def test_not_rel_normalization_4(self):
+        rqlst = self.parse('Any X WHERE C is Company, NOT (X work_for C, C name "World Company")').children[0]
+        self.assertEqual(rqlst.as_string(), "Any X WHERE C is Company, NOT EXISTS(X work_for C, C name 'World Company')")
+        C = rqlst.defined_vars['C']
+        self.failIf(C.scope is rqlst, C.scope)
+
+    def test_not_rel_normalization_5(self):
+        rqlst = self.parse('Any X WHERE X work_for C, EXISTS(C identity D, NOT Y work_for D, D name "World Company")').children[0]
+        self.assertEqual(rqlst.as_string(), "Any X WHERE X work_for C, EXISTS(C identity D, NOT EXISTS(Y work_for D), D name 'World Company')")
+        D = rqlst.defined_vars['D']
+        self.failIf(D.scope is rqlst, D.scope)
+        self.failUnless(D.scope.parent.scope is rqlst, D.scope.parent.scope)
+
+    def test_subquery_annotation_1(self):
         rqlst = self.parse('Any X WITH X BEING (Any X WHERE C is Company, EXISTS(X work_for C))').children[0]
         C = rqlst.with_[0].query.children[0].defined_vars['C']
         self.failIf(C.scope is rqlst, C.scope)
-        self.assertEquals(len(C.stinfo['relations']), 1)
+        self.assertEqual(len(C.stinfo['relations']), 1)
+
+    def test_subquery_annotation_2(self):
         rqlst = self.parse('Any X,ET WITH X,ET BEING (Any X, ET WHERE C is ET, EXISTS(X work_for C))').children[0]
         C = rqlst.with_[0].query.children[0].defined_vars['C']
         self.failUnless(C.scope is rqlst.with_[0].query.children[0], C.scope)
-        self.assertEquals(len(C.stinfo['relations']), 2)
+        self.assertEqual(len(C.stinfo['relations']), 2)
 
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_utils.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/test/unittest_utils.py	Thu May 05 10:50:51 2011 +0200
@@ -1,3 +1,20 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 
 from logilab.common.testlib import TestCase, unittest_main
 
@@ -38,19 +55,19 @@
 
     def test_rqlvar_maker(self):
         varlist = list(utils.rqlvar_maker(27))
-        self.assertEquals(varlist, list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') + ['AA'])
+        self.assertEqual(varlist, list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') + ['AA'])
         varlist = list(utils.rqlvar_maker(27*26+1))
-        self.assertEquals(varlist[-2], 'ZZ')
-        self.assertEquals(varlist[-1], 'AAA')
+        self.assertEqual(varlist[-2], 'ZZ')
+        self.assertEqual(varlist[-1], 'AAA')
 
     def test_rqlvar_maker_dontstop(self):
         varlist = utils.rqlvar_maker()
-        self.assertEquals(varlist.next(), 'A')
-        self.assertEquals(varlist.next(), 'B')
+        self.assertEqual(varlist.next(), 'A')
+        self.assertEqual(varlist.next(), 'B')
         for i in range(24):
             varlist.next()
-        self.assertEquals(varlist.next(), 'AA')
-        self.assertEquals(varlist.next(), 'AB')
+        self.assertEqual(varlist.next(), 'AA')
+        self.assertEqual(varlist.next(), 'AB')
 
         
 if __name__ == '__main__':
--- a/tools/bench_cpprql.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/tools/bench_cpprql.py	Thu May 05 10:50:51 2011 +0200
@@ -1,3 +1,20 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 from rql.rqlparse import parse
 import sys
 
--- a/tools/bench_pyrql.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/tools/bench_pyrql.py	Thu May 05 10:50:51 2011 +0200
@@ -1,3 +1,20 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 from rql import parse
 import sys
 f = file(sys.argv[1])
--- a/tools/rql_analyze.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/tools/rql_analyze.py	Thu May 05 10:50:51 2011 +0200
@@ -1,3 +1,20 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 from ginco.server.schema_readers import load_schema
 from rql import RQLHelper
 from rql.analyze import AltETypeResolver, Alt2ETypeResolver, ETypeResolver, ETypeResolver2
--- a/tools/rql_cmp.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/tools/rql_cmp.py	Thu May 05 10:50:51 2011 +0200
@@ -1,3 +1,20 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 from rql.rqlparse import parse as cparse
 from rql import parse
 from rql.compare2 import compare_tree, RQLCanonizer, make_canon_dict
--- a/tools/rql_parse.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/tools/rql_parse.py	Thu May 05 10:50:51 2011 +0200
@@ -1,3 +1,20 @@
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
 
 
 import rql.rqlparse as rqlparse
--- a/undo.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/undo.py	Thu May 05 10:50:51 2011 +0200
@@ -1,9 +1,22 @@
-"""Manages undos on RQL syntax trees.
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""Manages undos on RQL syntax trees."""
 
-:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
-"""
 __docformat__ = "restructuredtext en"
 
 from rql.nodes import VariableRef, Variable, BinaryNode
@@ -47,9 +60,11 @@
 
 class NodeOperation(object):
     """Abstract class for node manipulation operations."""
-    def __init__(self, node):
+    def __init__(self, node, stmt=None):
         self.node = node
-        self.stmt = node.stmt
+        if stmt is None:
+            stmt = node.stmt
+        self.stmt = stmt
 
     def __str__(self):
         """undo the operation on the selection"""
@@ -59,18 +74,21 @@
 
 class MakeVarOperation(NodeOperation):
     """Defines how to undo make_variable()."""
-
     def undo(self, selection):
         """undo the operation on the selection"""
         self.stmt.undefine_variable(self.node)
 
 class UndefineVarOperation(NodeOperation):
     """Defines how to undo undefine_variable()."""
+    def __init__(self, node, stmt, solutions):
+        NodeOperation.__init__(self, node, stmt)
+        self.solutions = solutions
 
     def undo(self, selection):
         """undo the operation on the selection"""
         var = self.node
         self.stmt.defined_vars[var.name] = var
+        self.stmt.solutions = self.solutions
 
 class SelectVarOperation(NodeOperation):
     """Defines how to undo add_selected()."""
@@ -121,45 +139,36 @@
 class RemoveNodeOperation(NodeOperation):
     """Defines how to undo remove_node()."""
 
-    def __init__(self, node):
-        NodeOperation.__init__(self, node)
-        self.node_parent = node.parent
-        if isinstance(self.node_parent, Select):
-            assert self.node is self.node_parent.where
-        else:
-            self.index = node.parent.children.index(node)
+    def __init__(self, node, parent, stmt, index):
+        NodeOperation.__init__(self, node, stmt)
+        self.node_parent = parent
+        #if isinstance(parent, Select):
+        #    assert self.node is parent.where
+        self.index = index
         # XXX FIXME : find a better way to do that
-        # needed when removing a BinaryNode's child
-        self.binary_remove = isinstance(self.node_parent, BinaryNode)
-        if self.binary_remove:
-            self.gd_parent = self.node_parent.parent
-            if isinstance(self.gd_parent, Select):
-                assert self.node_parent is self.gd_parent.where
-            else:
-                self.parent_index = self.gd_parent.children.index(self.node_parent)
+        self.binary_remove = isinstance(node, BinaryNode)
 
     def undo(self, selection):
         """undo the operation on the selection"""
+        parent = self.node_parent
+        if self.index is None:
+            assert isinstance(parent, Select)
+            sibling = parent.where = self.node
+            parent.where = self.node
         if self.binary_remove:
             # if 'parent' was a BinaryNode, then first reinsert the removed node
             # at the same pos in the original 'parent' Binary Node, and then
             # reinsert this BinaryNode in its parent's children list
             # WARNING : the removed node sibling's parent is no longer the
             # 'node_parent'. We must Reparent it manually !
-            node_sibling = self.node_parent.children[0]
-            node_sibling.parent = self.node_parent
-            self.node_parent.insert(self.index, self.node)
-            if isinstance(self.gd_parent, Select):
-                self.gd_parent.where = self.node_parent
-            else:
-                self.gd_parent.children[self.parent_index] = self.node_parent
-                self.node_parent.parent = self.gd_parent
-        elif isinstance(self.node_parent, Select):
-            self.node_parent.where = self.node
-            self.node.parent = self.node_parent
-        else:
-            self.node_parent.insert(self.index, self.node)
+            if self.index is not None:
+                sibling = self.node_parent.children[self.index]
+                parent.children[self.index] = self.node
+            sibling.parent = self.node
+        elif self.index is not None:
+            parent.insert(self.index, self.node)
         # register reference from the removed node
+        self.node.parent = parent
         for varref in self.node.iget_nodes(VariableRef):
             varref.register_reference()
 
--- a/utils.py	Thu Oct 15 20:20:29 2009 +0200
+++ b/utils.py	Thu May 05 10:50:51 2011 +0200
@@ -1,11 +1,26 @@
-"""Miscellaneous utilities for RQL.
+# copyright 2004-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of rql.
+#
+# rql is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# rql is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with rql. If not, see <http://www.gnu.org/licenses/>.
+"""Miscellaneous utilities for RQL."""
 
-:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: General Public License version 2 - http://www.gnu.org/licenses
-"""
 __docformat__ = "restructuredtext en"
 
+from rql._exceptions import BadRQLQuery
+
 UPPERCASE = u'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
 def decompose_b26(index, table=UPPERCASE):
     """Return a letter (base-26) decomposition of index."""
@@ -52,16 +67,23 @@
                 'LIMIT', 'OFFSET'))
 
 
-from logilab.common.adbh import _GenericAdvFuncHelper, FunctionDescr, \
-    auto_register_function
+from logilab.common.decorators import monkeypatch
+from logilab.database import SQL_FUNCTIONS_REGISTRY, FunctionDescr
+
+RQL_FUNCTIONS_REGISTRY = SQL_FUNCTIONS_REGISTRY.copy()
 
-def st_description(cls, funcnode, mainindex, tr):
+@monkeypatch(FunctionDescr)
+def st_description(self, funcnode, mainindex, tr):
     return '%s(%s)' % (
-        tr(cls.name),
+        tr(self.name),
         ', '.join(sorted(child.get_description(mainindex, tr)
                          for child in iter_funcnode_variables(funcnode))))
 
-FunctionDescr.st_description = classmethod(st_description)
+@monkeypatch(FunctionDescr)
+def st_check_backend(self, backend, funcnode):
+    if not self.supports(backend):
+        raise BadRQLQuery("backend %s doesn't support function %s" % (backend, self.name))
+
 
 def iter_funcnode_variables(funcnode):
     for term in funcnode.children:
@@ -93,19 +115,13 @@
         node2 = node2.parent
     raise Exception('DUH!')
 
-FUNCTIONS = _GenericAdvFuncHelper.FUNCTIONS.copy()
-
 def register_function(funcdef):
-    if isinstance(funcdef, basestring) :
-        funcdef = FunctionDescr(funcdef.upper())
-    assert not funcdef.name in FUNCTIONS, \
-           '%s is already registered' % funcdef.name
-    FUNCTIONS[funcdef.name] = funcdef
-    auto_register_function(funcdef)
+    RQL_FUNCTIONS_REGISTRY.register_function(funcdef)
+    SQL_FUNCTIONS_REGISTRY.register_function(funcdef)
 
 def function_description(funcname):
     """Return the description (`FunctionDescription`) for a RQL function."""
-    return FUNCTIONS[funcname.upper()]
+    return RQL_FUNCTIONS_REGISTRY.get_function(funcname)
 
 def quote(value):
     """Quote a string value."""