diff --git a/.hgtags b/.hgtags index 4025f1f02d1da65d26eada37708409984942c432_LmhndGFncw==..549ca221e30e4ae1d0a7e968e3818ea142ebb725_LmhndGFncw== 100644 --- a/.hgtags +++ b/.hgtags @@ -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 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000000000000000000000000000000000000..549ca221e30e4ae1d0a7e968e3818ea142ebb725_Q09QWUlORw== --- /dev/null +++ b/COPYING @@ -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. diff --git a/COPYING.LESSER b/COPYING.LESSER new file mode 100644 index 0000000000000000000000000000000000000000..549ca221e30e4ae1d0a7e968e3818ea142ebb725_Q09QWUlORy5MRVNTRVI= --- /dev/null +++ b/COPYING.LESSER @@ -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! + + diff --git a/ChangeLog b/ChangeLog index 4025f1f02d1da65d26eada37708409984942c432_Q2hhbmdlTG9n..549ca221e30e4ae1d0a7e968e3818ea142ebb725_Q2hhbmdlTG9n 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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) diff --git a/DEPENDS b/DEPENDS deleted file mode 100644 index 4025f1f02d1da65d26eada37708409984942c432_REVQRU5EUw==..0000000000000000000000000000000000000000 --- a/DEPENDS +++ /dev/null @@ -1,2 +0,0 @@ -python-logilab-common -python-constraint (>= 0.2.7) diff --git a/MANIFEST.in b/MANIFEST.in index 4025f1f02d1da65d26eada37708409984942c432_TUFOSUZFU1QuaW4=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_TUFOSUZFU1QuaW4= 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -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 diff --git a/__init__.py b/__init__.py index 4025f1f02d1da65d26eada37708409984942c432_X19pbml0X18ucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_X19pbml0X18ucHk= 100644 --- a/__init__.py +++ b/__init__.py @@ -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,5 +138,6 @@ 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 @@ -131,8 +142,7 @@ 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] @@ -136,7 +146,5 @@ 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() @@ -141,6 +149,5 @@ for vref in var.references(): rel = vref.relation() - #assert vref.parent if rel is None: term = vref while not term.parent is select: @@ -163,6 +170,5 @@ 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(): @@ -168,11 +174,7 @@ 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] @@ -174,7 +176,9 @@ 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 @@ -179,6 +183,6 @@ 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): diff --git a/__pkginfo__.py b/__pkginfo__.py index 4025f1f02d1da65d26eada37708409984942c432_X19wa2dpbmZvX18ucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_X19wa2dpbmZvX18ucHk= 100644 --- a/__pkginfo__.py +++ b/__pkginfo__.py @@ -1,10 +1,22 @@ # 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" @@ -8,7 +20,7 @@ __docformat__ = "restructuredtext en" modname = "rql" -numversion = (0, 23, 0) +numversion = (0, 28, 0) version = '.'.join(str(num) for num in numversion) license = 'LGPL' @@ -12,6 +24,4 @@ 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''' @@ -17,4 +27,4 @@ -author = "Sylvain Thenault" +author = "Logilab" author_email = "contact@logilab.fr" @@ -19,6 +29,6 @@ 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,11 +62,10 @@ 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'], @@ -63,12 +66,15 @@ 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'], ) ] @@ -73,2 +79,14 @@ ) ] + +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", + ] diff --git a/_exceptions.py b/_exceptions.py index 4025f1f02d1da65d26eada37708409984942c432_X2V4Y2VwdGlvbnMucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_X2V4Y2VwdGlvbnMucHk= 100644 --- a/_exceptions.py +++ b/_exceptions.py @@ -1,2 +1,19 @@ -"""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.""" @@ -2,8 +19,4 @@ -: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): diff --git a/analyze.py b/analyze.py index 4025f1f02d1da65d26eada37708409984942c432_YW5hbHl6ZS5weQ==..549ca221e30e4ae1d0a7e968e3818ea142ebb725_YW5hbHl6ZS5weQ== 100644 --- a/analyze.py +++ b/analyze.py @@ -1,2 +1,19 @@ +# 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. @@ -1,9 +18,6 @@ """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 @@ -6,9 +20,7 @@ """ __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,5 +354,6 @@ assert cst.type if cst.type == 'Substitute': eid = self.kwargs[cst.value] + self.deambiguifiers.add(cst.value) else: eid = cst.value @@ -343,6 +358,6 @@ 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,9 +377,9 @@ 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 @@ -367,9 +382,9 @@ 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 @@ -372,5 +387,6 @@ 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) @@ -376,4 +392,8 @@ 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,5 +509,6 @@ samevar = True else: rhsvars.append(v.name) + lhsdomain = constraints.domains[lhsvar] if rhsvars: s2 = '=='.join(rhsvars) @@ -492,4 +513,6 @@ 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(): @@ -494,5 +517,8 @@ 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: @@ -497,6 +523,8 @@ 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(): @@ -500,7 +528,7 @@ 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 ) diff --git a/base.py b/base.py index 4025f1f02d1da65d26eada37708409984942c432_YmFzZS5weQ==..549ca221e30e4ae1d0a7e968e3818ea142ebb725_YmFzZS5weQ== 100644 --- a/base.py +++ b/base.py @@ -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/>. """Base classes for RQL syntax tree nodes. Note: this module uses __slots__ to limit memory usage. @@ -1,4 +18,5 @@ """Base classes for RQL syntax tree nodes. Note: this module uses __slots__ to limit memory usage. +""" @@ -4,8 +22,4 @@ -: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,6 +140,10 @@ 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 @@ -138,4 +149,5 @@ 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.""" diff --git a/compare.py b/compare.py index 4025f1f02d1da65d26eada37708409984942c432_Y29tcGFyZS5weQ==..549ca221e30e4ae1d0a7e968e3818ea142ebb725_Y29tcGFyZS5weQ== 100644 --- a/compare.py +++ b/compare.py @@ -1,2 +1,19 @@ +# 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. @@ -1,8 +18,5 @@ """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" diff --git a/debian.hardy/control b/debian.hardy/control index 4025f1f02d1da65d26eada37708409984942c432_ZGViaWFuLmhhcmR5L2NvbnRyb2w=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_ZGViaWFuLmhhcmR5L2NvbnRyb2w= 100644 --- a/debian.hardy/control +++ b/debian.hardy/control @@ -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, diff --git a/debian.lenny/control b/debian.lenny/control index 4025f1f02d1da65d26eada37708409984942c432_ZGViaWFuLmxlbm55L2NvbnRyb2w=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_ZGViaWFuLmxlbm55L2NvbnRyb2w= 100644 --- a/debian.lenny/control +++ b/debian.lenny/control @@ -2,7 +2,6 @@ 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 @@ -8,7 +7,7 @@ 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} @@ -10,9 +9,10 @@ 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, diff --git a/debian/changelog b/debian/changelog index 4025f1f02d1da65d26eada37708409984942c432_ZGViaWFuL2NoYW5nZWxvZw==..549ca221e30e4ae1d0a7e968e3818ea142ebb725_ZGViaWFuL2NoYW5nZWxvZw== 100644 --- a/debian/changelog +++ b/debian/changelog @@ -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 diff --git a/debian/control b/debian/control index 4025f1f02d1da65d26eada37708409984942c432_ZGViaWFuL2NvbnRyb2w=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_ZGViaWFuL2NvbnRyb2w= 100644 --- a/debian/control +++ b/debian/control @@ -2,13 +2,12 @@ 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} @@ -10,9 +9,10 @@ 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, diff --git a/debian/copyright b/debian/copyright index 4025f1f02d1da65d26eada37708409984942c432_ZGViaWFuL2NvcHlyaWdodA==..549ca221e30e4ae1d0a7e968e3818ea142ebb725_ZGViaWFuL2NvcHlyaWdodA== 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,8 +1,8 @@ This package was debianized by Logilab <contact@logilab.fr>. -Upstream Author: +Upstream Author: Logilab <contact@logilab.fr> Copyright: @@ -4,8 +4,10 @@ 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: @@ -11,2 +13,6 @@ + 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. @@ -12,5 +18,6 @@ -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. @@ -16,1 +23,8 @@ + 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'. + diff --git a/debian/rules b/debian/rules index 4025f1f02d1da65d26eada37708409984942c432_ZGViaWFuL3J1bGVz..549ca221e30e4ae1d0a7e968e3818ea142ebb725_ZGViaWFuL3J1bGVz 100755 --- a/debian/rules +++ b/debian/rules @@ -12,6 +12,6 @@ #export DH_VERBOSE=1 build: build-stamp -build-stamp: +build-stamp: dh_testdir (for PYTHON in `pyversions -r`; do \ @@ -16,5 +16,5 @@ 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 @@ -19,6 +19,6 @@ ${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,6 +45,6 @@ # Build architecture-dependent files here. binary-arch: build install - dh_testdir - dh_testroot + dh_testdir + dh_testroot dh_install -a @@ -50,8 +50,8 @@ 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 @@ -52,8 +52,9 @@ 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 @@ -59,3 +60,4 @@ dh_fixperms -a + dh_strip dh_shlibdeps -a dh_installdeb -a @@ -60,6 +62,6 @@ dh_shlibdeps -a dh_installdeb -a - dh_gencontrol -a + dh_gencontrol -a dh_md5sums -a dh_builddeb -a diff --git a/doc/conf.py b/doc/conf.py index 4025f1f02d1da65d26eada37708409984942c432_ZG9jL2NvbmYucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_ZG9jL2NvbmYucHk= 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -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 diff --git a/editextensions.py b/editextensions.py index 4025f1f02d1da65d26eada37708409984942c432_ZWRpdGV4dGVuc2lvbnMucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_ZWRpdGV4dGVuc2lvbnMucHk= 100644 --- a/editextensions.py +++ b/editextensions.py @@ -1,2 +1,19 @@ -"""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.""" @@ -2,8 +19,4 @@ -: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 diff --git a/gecode_solver.cpp b/gecode_solver.cpp index 4025f1f02d1da65d26eada37708409984942c432_Z2Vjb2RlX3NvbHZlci5jcHA=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_Z2Vjb2RlX3NvbHZlci5jcHA= 100644 --- a/gecode_solver.cpp +++ b/gecode_solver.cpp @@ -365,4 +365,5 @@ 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(); @@ -368,4 +369,7 @@ n_b = s->branchings(); +#else + n_b = s->branchers(); +#endif } #if GE_VERSION<PM_VERSION(2,0,0) Engine<RqlSolver> e(s); diff --git a/interfaces.py b/interfaces.py index 4025f1f02d1da65d26eada37708409984942c432_aW50ZXJmYWNlcy5weQ==..549ca221e30e4ae1d0a7e968e3818ea142ebb725_aW50ZXJmYWNlcy5weQ== 100644 --- a/interfaces.py +++ b/interfaces.py @@ -1,2 +1,19 @@ +# 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. @@ -1,8 +18,5 @@ """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" diff --git a/nodes.py b/nodes.py index 4025f1f02d1da65d26eada37708409984942c432_bm9kZXMucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_bm9kZXMucHk= 100644 --- a/nodes.py +++ b/nodes.py @@ -1,4 +1,21 @@ +# 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. @@ -1,5 +18,6 @@ """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. +""" @@ -5,8 +23,4 @@ -: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,16 +50,9 @@ 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: @@ -54,9 +55,9 @@ 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,6 +142,8 @@ 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']: @@ -131,6 +147,8 @@ 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 @@ -135,7 +153,6 @@ 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,7 +343,11 @@ 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 @@ -336,8 +350,7 @@ @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,14 +866,5 @@ # 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(), @@ -861,5 +869,10 @@ # 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, @@ -865,7 +878,4 @@ '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, }) @@ -869,8 +879,17 @@ # 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] @@ -872,10 +891,10 @@ 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,7 +931,6 @@ 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 @@ -916,5 +934,6 @@ 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) @@ -920,6 +939,6 @@ 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 diff --git a/parser.g b/parser.g index 4025f1f02d1da65d26eada37708409984942c432_cGFyc2VyLmc=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_cGFyc2VyLmc= 100644 --- a/parser.g +++ b/parser.g @@ -88,7 +88,7 @@ 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*(?=\()' @@ -92,7 +92,7 @@ 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)) }} diff --git a/parser.py b/parser.py index 4025f1f02d1da65d26eada37708409984942c432_cGFyc2VyLnB5..549ca221e30e4ae1d0a7e968e3818ea142ebb725_cGFyc2VyLnB5 100644 --- a/parser.py +++ b/parser.py @@ -1,2 +1,19 @@ +# 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. @@ -1,8 +18,5 @@ """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,7 +109,7 @@ ('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*(?=\\()')), @@ -98,7 +113,7 @@ ('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" diff --git a/parser_main.py b/parser_main.py index 4025f1f02d1da65d26eada37708409984942c432_cGFyc2VyX21haW4ucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_cGFyc2VyX21haW4ucHk= 100644 --- a/parser_main.py +++ b/parser_main.py @@ -1,2 +1,19 @@ +# 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. @@ -1,8 +18,5 @@ """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" diff --git a/rqlgen.py b/rqlgen.py index 4025f1f02d1da65d26eada37708409984942c432_cnFsZ2VuLnB5..549ca221e30e4ae1d0a7e968e3818ea142ebb725_cnFsZ2VuLnB5 100644 --- a/rqlgen.py +++ b/rqlgen.py @@ -1,2 +1,19 @@ +# 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. @@ -1,8 +18,5 @@ """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" diff --git a/setup.py b/setup.py index 4025f1f02d1da65d26eada37708409984942c432_c2V0dXAucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_c2V0dXAucHk= 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,20 @@ #!/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. @@ -3,11 +20,7 @@ """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 @@ -8,9 +21,8 @@ """ __docformat__ = "restructuredtext en" import os import sys import shutil -from distutils.core import setup from os.path import isdir, exists, join, walk @@ -15,3 +27,16 @@ 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 @@ -17,4 +42,4 @@ # 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 @@ -19,27 +44,12 @@ 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', []) @@ -45,6 +55,8 @@ -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,5 +108,5 @@ continue if filename[-1] == '~': continue - src = '%s/%s' % (directory, filename) + src = join(directory, filename) dest = to_dir + src[len(from_dir):] @@ -99,5 +112,6 @@ 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,5 +129,10 @@ 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 +''' @@ -119,28 +138,10 @@ -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: @@ -146,5 +147,3 @@ 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): @@ -150,4 +149,5 @@ if not exists(product_init): + self.announce('creating %s' % product_init) stream = open(product_init, 'w') stream.write(EMPTY_FILE) stream.close() @@ -151,7 +151,6 @@ 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() diff --git a/stcheck.py b/stcheck.py index 4025f1f02d1da65d26eada37708409984942c432_c3RjaGVjay5weQ==..549ca221e30e4ae1d0a7e968e3818ea142ebb725_c3RjaGVjay5weQ== 100644 --- a/stcheck.py +++ b/stcheck.py @@ -1,2 +1,19 @@ -"""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""" @@ -2,10 +19,6 @@ -: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 @@ -7,8 +20,9 @@ __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 @@ -12,8 +26,8 @@ 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,9 +37,15 @@ 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 @@ -26,11 +46,30 @@ 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: @@ -32,12 +71,12 @@ 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 """ @@ -38,8 +77,8 @@ * 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 @@ -45,3 +84,5 @@ self.schema = schema + self.special_relations = special_relations or {} + self.backend = backend def check(self, node): @@ -46,12 +87,12 @@ 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)) @@ -52,8 +93,8 @@ #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: @@ -59,3 +100,3 @@ try: - node.accept(self, errors) + node.accept(self, state) except GoTo, ex: @@ -61,4 +102,4 @@ except GoTo, ex: - self._visit(ex.node, errors) + self._visit(ex.node, state) else: for c in node.children: @@ -63,5 +104,5 @@ else: for c in node.children: - self._visit(c, errors) - node.leave(self, errors) + self._visit(c, state) + node.leave(self, state) @@ -67,5 +108,5 @@ - 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 @@ -69,5 +110,5 @@ 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) @@ -73,5 +114,5 @@ - 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,7 +122,7 @@ 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 ######################################################### @@ -85,7 +126,7 @@ # 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: @@ -89,5 +130,5 @@ 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') @@ -93,4 +134,4 @@ 'the same number of selected terms') - def leave_union(self, node, errors): + def leave_union(self, node, state): pass @@ -95,5 +136,5 @@ pass - def visit_select(self, node, errors): + def visit_select(self, node, state): node.vargraph = {} # graph representing links between variable node.aggregated = set() @@ -98,4 +139,4 @@ node.vargraph = {} # graph representing links between variable node.aggregated = set() - self._visit_selectedterm(node, errors) + self._visit_selectedterm(node, state) @@ -101,6 +142,6 @@ - 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: @@ -103,9 +144,16 @@ 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: @@ -108,6 +156,6 @@ 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: @@ -113,4 +161,4 @@ 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 @@ -115,7 +163,8 @@ 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,10 +180,10 @@ 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 @@ -135,8 +184,8 @@ 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: @@ -142,4 +191,4 @@ try: - rtype = graph[(fromvar, tovar)] + rtype = graph[(fromvar, var)] cardidx = 0 except KeyError: @@ -144,5 +193,5 @@ cardidx = 0 except KeyError: - rtype = graph[(tovar, fromvar)] + rtype = graph[(var, fromvar)] cardidx = 1 rschema = self.schema.rschema(rtype) @@ -147,4 +196,4 @@ 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... @@ -150,4 +199,5 @@ # 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 @@ -153,5 +203,5 @@ return False - fromvar = tovar + fromvar = var return True @@ -155,8 +205,8 @@ 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 @@ -161,7 +211,7 @@ 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 @@ -166,9 +216,9 @@ 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 ############################################################## @@ -171,6 +221,6 @@ pass # tree nodes ############################################################## - def visit_exists(self, node, errors): + def visit_exists(self, node, state): pass @@ -176,4 +226,4 @@ pass - def leave_exists(self, node, errors): + def leave_exists(self, node, state): pass @@ -178,5 +228,5 @@ pass - def visit_subquery(self, node, errors): + def visit_subquery(self, node, state): pass @@ -181,6 +231,6 @@ 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,8 +260,8 @@ 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: @@ -214,8 +264,8 @@ 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,5 +274,5 @@ break else: msg = 'sort variable %s is not referenced any where else' - errors.append(msg % tvref.name) + state.error(msg % tvref.name) @@ -228,4 +278,4 @@ - def leave_sortterm(self, node, errors): + def leave_sortterm(self, node, state): pass @@ -230,4 +280,4 @@ pass - def visit_and(self, et, errors): + def visit_and(self, et, state): pass #assert len(et.children) == 2, len(et.children) @@ -233,4 +283,4 @@ pass #assert len(et.children) == 2, len(et.children) - def leave_and(self, node, errors): + def leave_and(self, node, state): pass @@ -235,4 +285,4 @@ pass - def visit_or(self, ou, errors): + def visit_or(self, ou, state): #assert len(ou.children) == 2, len(ou.children) @@ -238,8 +288,8 @@ #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 @@ -240,10 +290,10 @@ 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,11 +305,6 @@ 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 @@ -264,13 +309,52 @@ 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" @@ -276,12 +360,30 @@ # 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 @@ -285,7 +387,6 @@ try: vargraph = relation.stmt.vargraph rhsvarname = relation.children[1].children[0].variable.name - lhsvarname = relation.children[0].name except AttributeError: pass else: @@ -289,7 +390,7 @@ 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 @@ -295,6 +396,6 @@ - def leave_relation(self, relation, errors): + def leave_relation(self, relation, state): pass #assert isinstance(lhs, VariableRef), '%s: %s' % (lhs.__class__, # relation) @@ -297,6 +398,6 @@ 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) @@ -302,4 +403,4 @@ pass #assert len(comparison.children) in (1,2), len(comparison.children) - def leave_comparison(self, node, errors): + def leave_comparison(self, node, state): pass @@ -304,4 +405,4 @@ pass - def visit_mathexpression(self, mathexpr, errors): + def visit_mathexpression(self, mathexpr, state): pass #assert len(mathexpr.children) == 2, len(mathexpr.children) @@ -307,4 +408,4 @@ pass #assert len(mathexpr.children) == 2, len(mathexpr.children) - def leave_mathexpression(self, node, errors): + def leave_mathexpression(self, node, state): pass @@ -309,5 +410,5 @@ pass - def visit_function(self, function, errors): + def visit_function(self, function, state): try: funcdescr = function_description(function.name) @@ -312,8 +413,8 @@ 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: @@ -316,8 +417,13 @@ 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: @@ -321,7 +427,7 @@ 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,6 +435,7 @@ function.parent.remove(function) #else: # assert len(function.children) >= 1 - def leave_function(self, node, errors): + + def leave_function(self, node, state): pass @@ -333,6 +440,6 @@ 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,6 +449,6 @@ ## raise Exception((variableref.root(), variableref.variable)) pass - def leave_variableref(self, node, errors): + def leave_variableref(self, node, state): pass @@ -346,7 +453,7 @@ 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' @@ -349,6 +456,6 @@ #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: @@ -354,3 +461,3 @@ if not constant.value in self.schema: - errors.append('unknown entity type %s' % constant.value) + state.error('unknown entity type %s' % constant.value) @@ -356,5 +463,5 @@ - def leave_constant(self, node, errors): + def leave_constant(self, node, state): pass @@ -358,7 +465,6 @@ pass - class RQLSTAnnotator(object): """Annotate RQL syntax tree to ease further code generation from it. @@ -387,5 +493,4 @@ 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: @@ -391,5 +496,5 @@ 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,15 +515,5 @@ # 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) @@ -424,5 +519,5 @@ - 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 """ @@ -426,43 +521,58 @@ """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) @@ -468,8 +578,11 @@ 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 ############################################################## @@ -473,6 +586,6 @@ # 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) @@ -478,4 +591,4 @@ - 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) @@ -481,6 +594,6 @@ - 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 @@ -485,8 +598,8 @@ 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(): @@ -488,8 +601,6 @@ #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: @@ -495,8 +606,8 @@ 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: @@ -497,12 +608,10 @@ 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': @@ -508,3 +617,3 @@ if relation.optional == 'both': - lhsvar.stinfo['optrelations'].add(relation) + lhsvar.add_optional_relation(relation) elif relation.optional == 'left': @@ -510,4 +619,4 @@ elif relation.optional == 'left': - lhsvar.stinfo['optrelations'].add(relation) + lhsvar.add_optional_relation(relation) try: rhsvar = rhs.children[0].variable @@ -512,8 +621,6 @@ 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': @@ -519,3 +626,3 @@ if relation.optional == 'right': - rhsvar.stinfo['optrelations'].add(relation) + rhsvar.add_optional_relation(relation) elif relation.optional == 'both': @@ -521,6 +628,6 @@ 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 @@ -523,10 +630,7 @@ 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) @@ -531,9 +635,8 @@ 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 @@ -534,9 +637,12 @@ 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 @@ -541,6 +647,6 @@ 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: @@ -544,7 +650,7 @@ 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) @@ -548,9 +654,8 @@ 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) @@ -552,7 +657,6 @@ 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): @@ -558,2 +662,4 @@ 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) @@ -559,5 +665,8 @@ 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): diff --git a/stmts.py b/stmts.py index 4025f1f02d1da65d26eada37708409984942c432_c3RtdHMucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_c3RtdHMucHk= 100644 --- a/stmts.py +++ b/stmts.py @@ -1,4 +1,21 @@ +# 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 @@ -1,5 +18,6 @@ """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 +""" @@ -5,8 +23,4 @@ -: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,4 +269,5 @@ # union specific methods ################################################## + # XXX for bw compat, should now use get_variable_indices (cw > 3.8.4) def get_variable_variables(self): @@ -261,7 +273,14 @@ 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: @@ -264,8 +283,13 @@ """ 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,6 +325,6 @@ 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 """ @@ -306,4 +330,5 @@ """ - while col is not None and subselect.parent.parent: + selectpath = [] + while subselect.parent.parent is not None: subq = subselect.parent.parent subselect = subq.parent @@ -308,7 +333,8 @@ 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,7 +632,9 @@ 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 @@ -609,8 +637,8 @@ 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 """ @@ -614,7 +642,5 @@ """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: @@ -620,10 +646,12 @@ 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,4 +679,7 @@ 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): @@ -654,4 +685,18 @@ 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 @@ -657,11 +702,5 @@ 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,6 +709,9 @@ 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 @@ -673,6 +715,7 @@ 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): diff --git a/test/unittest_analyze.py b/test/unittest_analyze.py index 4025f1f02d1da65d26eada37708409984942c432_dGVzdC91bml0dGVzdF9hbmFseXplLnB5..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dGVzdC91bml0dGVzdF9hbmFseXplLnB5 100644 --- a/test/unittest_analyze.py +++ b/test/unittest_analyze.py @@ -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,6 +35,6 @@ 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] @@ -22,7 +39,6 @@ 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 @@ -25,7 +41,5 @@ 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() @@ -31,5 +45,5 @@ self.obj_types = d.keys() - self.symetric = symetric + self.symmetric = symmetric self.inlined = False if card is None: if self.final: @@ -33,7 +47,7 @@ self.inlined = False if card is None: if self.final: - card = '?*' + card = '?1' else: card = '**' self.card = card @@ -37,6 +51,11 @@ 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,10 +311,10 @@ 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) @@ -304,11 +315,11 @@ 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) @@ -311,8 +322,8 @@ # 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,8 +331,8 @@ 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): @@ -324,11 +335,11 @@ 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) @@ -329,10 +340,10 @@ 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,10 +352,10 @@ 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) @@ -345,12 +356,12 @@ 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 @@ -352,19 +363,19 @@ 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): @@ -367,11 +378,11 @@ 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) @@ -372,11 +383,11 @@ 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) @@ -379,8 +390,8 @@ 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'}]) diff --git a/test/unittest_compare.py b/test/unittest_compare.py index 4025f1f02d1da65d26eada37708409984942c432_dGVzdC91bml0dGVzdF9jb21wYXJlLnB5..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dGVzdC91bml0dGVzdF9jb21wYXJlLnB5 100644 --- a/test/unittest_compare.py +++ b/test/unittest_compare.py @@ -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,6 +36,9 @@ class RQLCompareClassTest(TestCase): """ Compare RQL strings """ - + @classmethod + def setUpClass(cls): + raise SkipTest('broken') + def setUp(self): self.h = RQLHelper(DummySchema(), None) @@ -23,5 +43,5 @@ def setUp(self): self.h = RQLHelper(DummySchema(), None) - + def _compareEquivalent(self,r1,r2): """fails if the RQL strings r1 and r2 are equivalent""" @@ -26,6 +46,6 @@ 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,8 +66,8 @@ 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): @@ -51,8 +71,8 @@ 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): @@ -55,9 +75,9 @@ 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): @@ -60,8 +80,8 @@ 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): @@ -65,8 +85,8 @@ 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): @@ -70,8 +90,8 @@ 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): @@ -75,8 +95,8 @@ 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): @@ -80,8 +100,8 @@ 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,8 +152,8 @@ 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): @@ -137,8 +157,8 @@ 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): @@ -142,8 +162,8 @@ 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): @@ -147,8 +167,8 @@ 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): @@ -152,8 +172,8 @@ 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): diff --git a/test/unittest_editextensions.py b/test/unittest_editextensions.py index 4025f1f02d1da65d26eada37708409984942c432_dGVzdC91bml0dGVzdF9lZGl0ZXh0ZW5zaW9ucy5weQ==..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dGVzdC91bml0dGVzdF9lZGl0ZXh0ZW5zaW9ucy5weQ== 100644 --- a/test/unittest_editextensions.py +++ b/test/unittest_editextensions.py @@ -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,8 +32,8 @@ 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 @@ -19,8 +36,8 @@ # 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,8 +50,8 @@ 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 @@ -37,8 +54,8 @@ # 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,8 +65,8 @@ 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 @@ -52,8 +69,8 @@ # 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,8 +82,8 @@ 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 @@ -69,8 +86,8 @@ # 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() diff --git a/test/unittest_nodes.py b/test/unittest_nodes.py index 4025f1f02d1da65d26eada37708409984942c432_dGVzdC91bml0dGVzdF9ub2Rlcy5weQ==..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dGVzdC91bml0dGVzdF9ub2Rlcy5weQ== 100644 --- a/test/unittest_nodes.py +++ b/test/unittest_nodes.py @@ -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,7 +36,7 @@ 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): @@ -22,6 +41,6 @@ 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): @@ -26,5 +45,5 @@ def test_float(self): - self.assertEquals(nodes.etype_from_pyobj(0.), 'Float') + self.assertEqual(nodes.etype_from_pyobj(0.), 'Float') def test_datetime(self): @@ -29,6 +48,6 @@ 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): @@ -33,7 +52,7 @@ 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,7 +61,7 @@ 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() @@ -46,7 +65,7 @@ # 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,5 +77,5 @@ #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)) @@ -62,5 +81,5 @@ 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,6 +93,6 @@ 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() @@ -79,9 +98,9 @@ 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) @@ -82,8 +101,8 @@ 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] @@ -89,3 +108,3 @@ select = tree.children[0] - self.assertEquals(select.limit, 10) + self.assertEqual(select.limit, 10) tree.recover() @@ -91,5 +110,5 @@ 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,5 +117,5 @@ 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() @@ -102,6 +121,6 @@ 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,6 +128,6 @@ 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() @@ -114,5 +133,5 @@ 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,5 +140,5 @@ 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() @@ -125,5 +144,5 @@ 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") @@ -127,9 +146,9 @@ 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) @@ -131,7 +150,7 @@ 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() @@ -137,7 +156,7 @@ 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') @@ -139,8 +158,8 @@ 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) @@ -145,4 +164,4 @@ tree.save_state() tree.set_offset(0) - self.assertEquals(tree.offset, 0) + self.assertEqual(tree.offset, 0) tree.set_offset(10) @@ -148,3 +167,3 @@ tree.set_offset(10) - self.assertEquals(tree.offset, 10) + self.assertEqual(tree.offset, 10) tree.recover() @@ -150,5 +169,5 @@ 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,6 +175,6 @@ 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() @@ -160,6 +179,6 @@ 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,6 +186,6 @@ 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() @@ -171,8 +190,23 @@ 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] @@ -174,8 +208,8 @@ 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) @@ -180,4 +214,4 @@ tree.save_state() select.set_distinct(True) - self.assertEquals(select.distinct, True) + self.assertEqual(select.distinct, True) tree.recover() @@ -183,3 +217,3 @@ tree.recover() - self.assertEquals(select.distinct, True) + self.assertEqual(select.distinct, True) select.set_distinct(False) @@ -185,3 +219,3 @@ select.set_distinct(False) - self.assertEquals(select.distinct, False) + self.assertEqual(select.distinct, False) tree.recover() @@ -187,5 +221,5 @@ 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,6 +227,6 @@ 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() @@ -197,6 +231,6 @@ 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,6 +238,6 @@ 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() @@ -208,6 +242,6 @@ 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,6 +249,6 @@ 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() @@ -219,6 +253,6 @@ 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,6 +367,13 @@ 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])) @@ -338,10 +379,22 @@ - 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,6 +473,6 @@ 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") @@ -424,6 +477,6 @@ 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é'") @@ -427,6 +480,6 @@ '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") @@ -431,5 +484,5 @@ 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") @@ -434,5 +487,5 @@ 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") @@ -437,7 +490,7 @@ 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é'") @@ -440,7 +493,7 @@ 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") @@ -445,7 +498,7 @@ 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") @@ -448,6 +501,6 @@ 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") @@ -453,3 +506,3 @@ 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") @@ -455,5 +508,5 @@ 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,8 +519,8 @@ 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 @@ -471,14 +524,14 @@ 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,10 +569,10 @@ 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) @@ -522,7 +575,7 @@ 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) @@ -527,8 +580,8 @@ 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)) @@ -530,12 +583,12 @@ ['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)) @@ -538,7 +591,7 @@ 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) @@ -543,6 +596,6 @@ 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__': diff --git a/test/unittest_parser.py b/test/unittest_parser.py index 4025f1f02d1da65d26eada37708409984942c432_dGVzdC91bml0dGVzdF9wYXJzZXIucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dGVzdC91bml0dGVzdF9wYXJzZXIucHk= 100644 --- a/test/unittest_parser.py +++ b/test/unittest_parser.py @@ -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,9 +272,9 @@ 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 @@ -216,7 +277,7 @@ 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 @@ -221,5 +282,5 @@ 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 @@ -224,5 +285,5 @@ 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 @@ -227,6 +288,6 @@ 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,5 +301,5 @@ 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;') @@ -244,5 +305,5 @@ 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""" diff --git a/test/unittest_rqlgen.py b/test/unittest_rqlgen.py index 4025f1f02d1da65d26eada37708409984942c432_dGVzdC91bml0dGVzdF9ycWxnZW4ucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dGVzdC91bml0dGVzdF9ycWxnZW4ucHk= 100644 --- a/test/unittest_rqlgen.py +++ b/test/unittest_rqlgen.py @@ -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,10 +41,10 @@ """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',)) @@ -28,13 +45,13 @@ 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',)) @@ -35,10 +52,10 @@ 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,10 +88,10 @@ """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')) @@ -75,10 +92,10 @@ 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__': diff --git a/test/unittest_stcheck.py b/test/unittest_stcheck.py index 4025f1f02d1da65d26eada37708409984942c432_dGVzdC91bml0dGVzdF9zdGNoZWNrLnB5..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dGVzdC91bml0dGVzdF9zdGNoZWNrLnB5 100644 --- a/test/unittest_stcheck.py +++ b/test/unittest_stcheck.py @@ -1,1 +1,18 @@ +# 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 @@ -1,2 +18,5 @@ from logilab.common.testlib import TestCase, unittest_main + +from rql import RQLHelper, BadRQLQuery, stmts, nodes + from unittest_analyze import DummySchema @@ -2,5 +22,4 @@ from unittest_analyze import DummySchema -from rql import RQLHelper, BadRQLQuery, stmts, nodes BAD_QUERIES = ( 'Any X, Y GROUPBY X', @@ -33,5 +52,8 @@ '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', @@ -37,6 +59,10 @@ '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,5 +73,6 @@ '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 @@ -50,5 +77,5 @@ # 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', @@ -54,5 +81,4 @@ -# '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,8 +194,8 @@ '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'}) @@ -166,9 +198,9 @@ {'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') @@ -171,7 +203,7 @@ ## 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) @@ -176,6 +208,6 @@ ## 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,9 +254,10 @@ # 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) @@ -228,7 +261,9 @@ 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) @@ -232,5 +267,18 @@ 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) @@ -236,5 +284,24 @@ - 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) @@ -238,7 +305,9 @@ 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) @@ -242,7 +311,7 @@ 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() diff --git a/test/unittest_utils.py b/test/unittest_utils.py index 4025f1f02d1da65d26eada37708409984942c432_dGVzdC91bml0dGVzdF91dGlscy5weQ==..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dGVzdC91bml0dGVzdF91dGlscy5weQ== 100644 --- a/test/unittest_utils.py +++ b/test/unittest_utils.py @@ -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,5 +55,5 @@ 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)) @@ -42,6 +59,6 @@ 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() @@ -45,7 +62,7 @@ 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() @@ -50,7 +67,7 @@ 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__': diff --git a/tools/bench_cpprql.py b/tools/bench_cpprql.py index 4025f1f02d1da65d26eada37708409984942c432_dG9vbHMvYmVuY2hfY3BwcnFsLnB5..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dG9vbHMvYmVuY2hfY3BwcnFsLnB5 100644 --- a/tools/bench_cpprql.py +++ b/tools/bench_cpprql.py @@ -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 diff --git a/tools/bench_pyrql.py b/tools/bench_pyrql.py index 4025f1f02d1da65d26eada37708409984942c432_dG9vbHMvYmVuY2hfcHlycWwucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dG9vbHMvYmVuY2hfcHlycWwucHk= 100644 --- a/tools/bench_pyrql.py +++ b/tools/bench_pyrql.py @@ -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]) diff --git a/tools/rql_analyze.py b/tools/rql_analyze.py index 4025f1f02d1da65d26eada37708409984942c432_dG9vbHMvcnFsX2FuYWx5emUucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dG9vbHMvcnFsX2FuYWx5emUucHk= 100644 --- a/tools/rql_analyze.py +++ b/tools/rql_analyze.py @@ -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 diff --git a/tools/rql_cmp.py b/tools/rql_cmp.py index 4025f1f02d1da65d26eada37708409984942c432_dG9vbHMvcnFsX2NtcC5weQ==..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dG9vbHMvcnFsX2NtcC5weQ== 100644 --- a/tools/rql_cmp.py +++ b/tools/rql_cmp.py @@ -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 diff --git a/tools/rql_parse.py b/tools/rql_parse.py index 4025f1f02d1da65d26eada37708409984942c432_dG9vbHMvcnFsX3BhcnNlLnB5..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dG9vbHMvcnFsX3BhcnNlLnB5 100644 --- a/tools/rql_parse.py +++ b/tools/rql_parse.py @@ -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 diff --git a/undo.py b/undo.py index 4025f1f02d1da65d26eada37708409984942c432_dW5kby5weQ==..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dW5kby5weQ== 100644 --- a/undo.py +++ b/undo.py @@ -1,2 +1,19 @@ -"""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.""" @@ -2,8 +19,4 @@ -: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,5 +60,5 @@ class NodeOperation(object): """Abstract class for node manipulation operations.""" - def __init__(self, node): + def __init__(self, node, stmt=None): self.node = node @@ -51,5 +64,7 @@ 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,10 +74,9 @@ 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().""" @@ -63,11 +77,14 @@ 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 @@ -69,8 +86,9 @@ 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,11 +139,10 @@ 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 @@ -131,12 +148,5 @@ # 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""" @@ -140,9 +150,14 @@ 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 ! @@ -143,20 +158,13 @@ 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 @@ -162,4 +170,5 @@ # register reference from the removed node + self.node.parent = parent for varref in self.node.iget_nodes(VariableRef): varref.register_reference() diff --git a/utils.py b/utils.py index 4025f1f02d1da65d26eada37708409984942c432_dXRpbHMucHk=..549ca221e30e4ae1d0a7e968e3818ea142ebb725_dXRpbHMucHk= 100644 --- a/utils.py +++ b/utils.py @@ -1,2 +1,19 @@ -"""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.""" @@ -2,7 +19,3 @@ -: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" @@ -7,5 +20,7 @@ __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,6 +67,8 @@ '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() @@ -57,3 +74,4 @@ -def st_description(cls, funcnode, mainindex, tr): +@monkeypatch(FunctionDescr) +def st_description(self, funcnode, mainindex, tr): return '%s(%s)' % ( @@ -59,5 +77,5 @@ return '%s(%s)' % ( - tr(cls.name), + tr(self.name), ', '.join(sorted(child.get_description(mainindex, tr) for child in iter_funcnode_variables(funcnode)))) @@ -61,7 +79,11 @@ ', '.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,6 +115,4 @@ node2 = node2.parent raise Exception('DUH!') -FUNCTIONS = _GenericAdvFuncHelper.FUNCTIONS.copy() - def register_function(funcdef): @@ -98,10 +118,6 @@ 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.""" @@ -105,7 +121,7 @@ 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."""