--- a/.hgtags Thu Oct 15 20:20:29 2009 +0200
+++ b/.hgtags Thu May 05 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 'héhé'")
- self.assertEquals(tree.as_string('utf8'),
+ self.assertEqual(tree.as_string('utf8'),
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'))
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 'héhé'")
- self.assertEquals(tree.as_string(),
+ self.assertEqual(tree.as_string(),
u'Any X WHERE X has_text "héhé"')
tree = parse(u"Any X WHERE X has_text %(text)s")
- self.assertEquals(tree.as_string(kwargs={'text': u'héhé'}),
+ self.assertEqual(tree.as_string(kwargs={'text': u'héhé'}),
u'Any X WHERE X has_text "héhé"')
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 11:20:15 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 dessinée', X owned_by U, U login 'syt', X available true;",
+ u"Document X WHERE X occurence_of F, F class C, C name 'Bande dessinée', 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 'Ångström';")
+ 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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 11:20:15 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."""